Removed old block system
This commit is contained in:
parent
e99056afac
commit
1d22b43ef9
@ -70,7 +70,6 @@ createdirs.create_dirs()
|
|||||||
import bigbrother # noqa
|
import bigbrother # noqa
|
||||||
from onionrcommands import parser # noqa
|
from onionrcommands import parser # noqa
|
||||||
from onionrplugins import onionrevents as events # noqa
|
from onionrplugins import onionrevents as events # noqa
|
||||||
from onionrblocks.deleteplaintext import delete_plaintext_no_blacklist # noqa
|
|
||||||
|
|
||||||
setup.setup_config()
|
setup.setup_config()
|
||||||
|
|
||||||
@ -84,8 +83,6 @@ if config.get('advanced.security_auditing', True):
|
|||||||
except onionrexceptions.PythonVersion:
|
except onionrexceptions.PythonVersion:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not config.get('general.store_plaintext_blocks', True):
|
|
||||||
delete_plaintext_no_blacklist()
|
|
||||||
|
|
||||||
setup.setup_default_plugins()
|
setup.setup_default_plugins()
|
||||||
|
|
||||||
|
@ -68,7 +68,6 @@ class PrivateAPI:
|
|||||||
self.httpServer = ''
|
self.httpServer = ''
|
||||||
|
|
||||||
self.queueResponse = {}
|
self.queueResponse = {}
|
||||||
self.get_block_data = httpapi.apiutils.GetBlockData(self)
|
|
||||||
register_private_blueprints.register_private_blueprints(self, app)
|
register_private_blueprints.register_private_blueprints(self, app)
|
||||||
httpapi.load_plugin_blueprints(app)
|
httpapi.load_plugin_blueprints(app)
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -6,7 +6,7 @@ from threading import Thread
|
|||||||
from gevent import sleep
|
from gevent import sleep
|
||||||
|
|
||||||
from httpapi import security, friendsapi, configapi
|
from httpapi import security, friendsapi, configapi
|
||||||
from httpapi import miscclientapi, onionrsitesapi, apiutils
|
from httpapi import miscclientapi, apiutils
|
||||||
from httpapi import themeapi
|
from httpapi import themeapi
|
||||||
from httpapi import fileoffsetreader
|
from httpapi import fileoffsetreader
|
||||||
from httpapi.sse.private import private_sse_blueprint
|
from httpapi.sse.private import private_sse_blueprint
|
||||||
@ -35,8 +35,6 @@ def register_private_blueprints(private_api, app):
|
|||||||
app.register_blueprint(configapi.config_BP)
|
app.register_blueprint(configapi.config_BP)
|
||||||
app.register_blueprint(miscclientapi.endpoints.PrivateEndpoints(
|
app.register_blueprint(miscclientapi.endpoints.PrivateEndpoints(
|
||||||
private_api).private_endpoints_bp)
|
private_api).private_endpoints_bp)
|
||||||
app.register_blueprint(miscclientapi.motd.bp)
|
|
||||||
app.register_blueprint(onionrsitesapi.site_api)
|
|
||||||
app.register_blueprint(apiutils.shutdown.shutdown_bp)
|
app.register_blueprint(apiutils.shutdown.shutdown_bp)
|
||||||
app.register_blueprint(miscclientapi.staticfiles.static_files_bp)
|
app.register_blueprint(miscclientapi.staticfiles.static_files_bp)
|
||||||
app.register_blueprint(themeapi.theme_blueprint)
|
app.register_blueprint(themeapi.theme_blueprint)
|
||||||
|
@ -1 +1 @@
|
|||||||
from . import keydb, blockmetadb
|
from . import keydb
|
@ -1,84 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Work with information relating to blocks stored on the node
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
from etc import onionrvalues
|
|
||||||
from . import expiredblocks, updateblockinfo, add
|
|
||||||
from .. import dbfiles
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
update_block_info = updateblockinfo.update_block_info
|
|
||||||
add_to_block_DB = add.add_to_block_DB
|
|
||||||
|
|
||||||
|
|
||||||
def get_block_list(date_rec=None, unsaved=False):
|
|
||||||
"""Get list of our blocks."""
|
|
||||||
if date_rec is None:
|
|
||||||
date_rec = 0
|
|
||||||
|
|
||||||
conn = sqlite3.connect(
|
|
||||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
|
|
||||||
execute = 'SELECT hash FROM hashes WHERE dateReceived' + \
|
|
||||||
' >= ? ORDER BY dateReceived ASC;'
|
|
||||||
args = (date_rec,)
|
|
||||||
rows = list()
|
|
||||||
for row in c.execute(execute, args):
|
|
||||||
for i in row:
|
|
||||||
rows.append(i)
|
|
||||||
conn.close()
|
|
||||||
return rows
|
|
||||||
|
|
||||||
|
|
||||||
def get_block_date(blockHash):
|
|
||||||
"""Return the date a block was received."""
|
|
||||||
conn = sqlite3.connect(
|
|
||||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
|
|
||||||
execute = 'SELECT dateReceived FROM hashes WHERE hash=?;'
|
|
||||||
args = (blockHash,)
|
|
||||||
for row in c.execute(execute, args):
|
|
||||||
for i in row:
|
|
||||||
return int(i)
|
|
||||||
conn.close()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_blocks_by_type(blockType, orderDate=True):
|
|
||||||
"""Return a list of blocks by the type."""
|
|
||||||
|
|
||||||
conn = sqlite3.connect(
|
|
||||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
|
|
||||||
if orderDate:
|
|
||||||
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
|
|
||||||
else:
|
|
||||||
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
|
|
||||||
|
|
||||||
args = (blockType,)
|
|
||||||
rows = list()
|
|
||||||
|
|
||||||
for row in c.execute(execute, args):
|
|
||||||
for i in row:
|
|
||||||
rows.append(i)
|
|
||||||
conn.close()
|
|
||||||
return rows
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Add an entry to the block metadata database
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
import secrets
|
|
||||||
from onionrutils import epoch
|
|
||||||
from onionrblocks import blockmetadata
|
|
||||||
from etc import onionrvalues
|
|
||||||
from .. import dbfiles
|
|
||||||
from onionrexceptions import BlockMetaEntryExists
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def add_to_block_DB(newHash, selfInsert=False, dataSaved=False):
|
|
||||||
"""
|
|
||||||
Add a hash value to the block db
|
|
||||||
|
|
||||||
Should be in hex format!
|
|
||||||
"""
|
|
||||||
|
|
||||||
if blockmetadata.has_block(newHash):
|
|
||||||
raise BlockMetaEntryExists
|
|
||||||
conn = sqlite3.connect(
|
|
||||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
currentTime = epoch.get_epoch() + secrets.randbelow(61)
|
|
||||||
if selfInsert or dataSaved:
|
|
||||||
selfInsert = 1
|
|
||||||
else:
|
|
||||||
selfInsert = 0
|
|
||||||
data = (newHash, currentTime, '', selfInsert)
|
|
||||||
c.execute(
|
|
||||||
'INSERT INTO hashes (hash, dateReceived, dataType, dataSaved) VALUES(?, ?, ?, ?);', data)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
@ -1,41 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Get a list of expired blocks still stored
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
from onionrutils import epoch
|
|
||||||
from .. import dbfiles
|
|
||||||
from etc import onionrvalues
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def get_expired_blocks():
|
|
||||||
"""Return a list of expired blocks."""
|
|
||||||
conn = sqlite3.connect(
|
|
||||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
date = int(epoch.get_epoch())
|
|
||||||
|
|
||||||
compiled = (date,)
|
|
||||||
execute = 'SELECT hash FROM hashes WHERE ' + \
|
|
||||||
'expire <= ? ORDER BY dateReceived;'
|
|
||||||
|
|
||||||
rows = list()
|
|
||||||
for row in c.execute(execute, compiled):
|
|
||||||
for i in row:
|
|
||||||
rows.append(i)
|
|
||||||
conn.close()
|
|
||||||
return rows
|
|
@ -1,52 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Update block information in the metadata database by a field name
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
from .. import dbfiles
|
|
||||||
from etc import onionrvalues
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def update_block_info(hash, key, data):
|
|
||||||
"""set info associated with a block
|
|
||||||
|
|
||||||
hash - the hash of a block
|
|
||||||
dateReceived - the date the block was recieved, not necessarily when it was created
|
|
||||||
decrypted - if we can successfully decrypt the block
|
|
||||||
dataType - data type of the block
|
|
||||||
dataFound - if the data has been found for the block
|
|
||||||
dataSaved - if the data has been saved for the block
|
|
||||||
sig - defunct
|
|
||||||
author - defunct
|
|
||||||
dateClaimed - timestamp claimed inside the block, only as trustworthy as the block author is
|
|
||||||
expire - expire date for a block
|
|
||||||
"""
|
|
||||||
if key not in ('dateReceived', 'decrypted', 'dataType', 'dataFound',
|
|
||||||
'dataSaved', 'sig', 'author', 'dateClaimed', 'expire'):
|
|
||||||
raise ValueError('Key must be in the allowed list')
|
|
||||||
|
|
||||||
conn = sqlite3.connect(dbfiles.block_meta_db,
|
|
||||||
timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
args = (data, hash)
|
|
||||||
# Unfortunately, not really possible to prepare this statement
|
|
||||||
c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return True
|
|
@ -3,8 +3,6 @@ import filepaths
|
|||||||
home = identifyhome.identify_home()
|
home = identifyhome.identify_home()
|
||||||
if not home.endswith('/'): home += '/'
|
if not home.endswith('/'): home += '/'
|
||||||
|
|
||||||
block_meta_db = '%sblock-metadata.db' % (home)
|
|
||||||
block_data_db = '%s/block-data.db' % (filepaths.block_data_location,)
|
|
||||||
address_info_db = '%saddress.db' % (home,)
|
address_info_db = '%saddress.db' % (home,)
|
||||||
user_id_info_db = '%susers.db' % (home,)
|
user_id_info_db = '%susers.db' % (home,)
|
||||||
forward_keys_db = '%sforward-keys.db' % (home,)
|
forward_keys_db = '%sforward-keys.db' % (home,)
|
||||||
|
@ -1,3 +1 @@
|
|||||||
from . import shutdown, setbindip, getblockdata
|
from . import shutdown, setbindip
|
||||||
|
|
||||||
GetBlockData = getblockdata.GetBlockData
|
|
@ -1,38 +0,0 @@
|
|||||||
import ujson as json
|
|
||||||
|
|
||||||
from onionrblocks import onionrblockapi
|
|
||||||
from onionrutils import bytesconverter, stringvalidators
|
|
||||||
import onionrexceptions
|
|
||||||
class GetBlockData:
|
|
||||||
def __init__(self, client_api_inst=None):
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_block_data(self, bHash, decrypt=False, raw=False, headerOnly=False):
|
|
||||||
if not stringvalidators.validate_hash(bHash):
|
|
||||||
raise onionrexceptions.InvalidHexHash(
|
|
||||||
"block hash not valid hash format")
|
|
||||||
bl = onionrblockapi.Block(bHash)
|
|
||||||
if decrypt:
|
|
||||||
bl.decrypt()
|
|
||||||
if bl.isEncrypted and not bl.decrypted:
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
if not raw:
|
|
||||||
if not headerOnly:
|
|
||||||
retData = {'meta':bl.bheader, 'metadata': bl.bmetadata, 'content': bl.bcontent}
|
|
||||||
for x in list(retData.keys()):
|
|
||||||
try:
|
|
||||||
retData[x] = retData[x].decode()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
validSig = False
|
|
||||||
signer = bytesconverter.bytes_to_str(bl.signer)
|
|
||||||
if bl.isSigned() and stringvalidators.validate_pub_key(signer) and bl.isSigner(signer):
|
|
||||||
validSig = True
|
|
||||||
bl.bheader['validSig'] = validSig
|
|
||||||
bl.bheader['meta'] = ''
|
|
||||||
retData = {'meta': bl.bheader, 'metadata': bl.bmetadata}
|
|
||||||
return json.dumps(retData)
|
|
||||||
else:
|
|
||||||
return bl.raw
|
|
@ -1 +1 @@
|
|||||||
from . import staticfiles, endpoints, motd
|
from . import staticfiles, endpoints
|
@ -78,10 +78,6 @@ class PrivateEndpoints:
|
|||||||
subprocess.Popen([SCRIPT_NAME, 'restart'])
|
subprocess.Popen([SCRIPT_NAME, 'restart'])
|
||||||
return Response("bye")
|
return Response("bye")
|
||||||
|
|
||||||
@private_endpoints_bp.route('/gethidden')
|
|
||||||
def get_hidden_blocks():
|
|
||||||
return Response('\n'.join(client_api.publicAPI.hideBlocks))
|
|
||||||
|
|
||||||
|
|
||||||
@private_endpoints_bp.route('/getuptime')
|
@private_endpoints_bp.route('/getuptime')
|
||||||
def show_uptime():
|
def show_uptime():
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
from flask import Blueprint
|
|
||||||
from flask import Response
|
|
||||||
import unpaddedbase32
|
|
||||||
|
|
||||||
from coredb import blockmetadb
|
|
||||||
import onionrblocks
|
|
||||||
from etc import onionrvalues
|
|
||||||
import config
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
|
|
||||||
bp = Blueprint('motd', __name__)
|
|
||||||
|
|
||||||
signer = config.get("motd.motd_key", onionrvalues.MOTD_SIGN_KEY)
|
|
||||||
|
|
||||||
@bp.route('/getmotd')
|
|
||||||
def get_motd()->Response:
|
|
||||||
motds = blockmetadb.get_blocks_by_type("motd")
|
|
||||||
newest_time = 0
|
|
||||||
message = "No MOTD currently present."
|
|
||||||
for x in motds:
|
|
||||||
bl = onionrblocks.onionrblockapi.Block(x)
|
|
||||||
if not bl.verifySig() or bl.signer != bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(signer))): continue
|
|
||||||
if not bl.isSigner(signer): continue
|
|
||||||
if bl.claimedTime > newest_time:
|
|
||||||
newest_time = bl.claimedTime
|
|
||||||
message = bl.bcontent
|
|
||||||
return Response(message, headers={"Content-Type": "text/plain"})
|
|
@ -1,94 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
view and interact with onionr sites
|
|
||||||
"""
|
|
||||||
import base64
|
|
||||||
import binascii
|
|
||||||
import mimetypes
|
|
||||||
|
|
||||||
import unpaddedbase32
|
|
||||||
|
|
||||||
from flask import Blueprint, Response, request, abort
|
|
||||||
|
|
||||||
from onionrblocks import onionrblockapi
|
|
||||||
import onionrexceptions
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from onionrutils import mnemonickeys
|
|
||||||
from . import sitefiles
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
site_api = Blueprint('siteapi', __name__)
|
|
||||||
|
|
||||||
@site_api.route('/site/<name>/', endpoint='site')
|
|
||||||
def site(name: str)->Response:
|
|
||||||
"""Accept a site 'name', if pubkey then show multi-page site, if hash show single page site"""
|
|
||||||
resp: str = 'Not Found'
|
|
||||||
mime_type = 'text/html'
|
|
||||||
|
|
||||||
# If necessary convert the name to base32 from mnemonic
|
|
||||||
if mnemonickeys.DELIMITER in name:
|
|
||||||
name = mnemonickeys.get_base32(name)
|
|
||||||
|
|
||||||
# Now make sure the key is regardless a valid base32 format ed25519 key (readding padding if necessary)
|
|
||||||
if stringvalidators.validate_pub_key(name):
|
|
||||||
name = unpaddedbase32.repad(name)
|
|
||||||
resp = sitefiles.get_file(name, 'index.html')
|
|
||||||
|
|
||||||
elif stringvalidators.validate_hash(name):
|
|
||||||
try:
|
|
||||||
resp = onionrblockapi.Block(name).bcontent
|
|
||||||
except onionrexceptions.NoDataAvailable:
|
|
||||||
abort(404)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
resp = base64.b64decode(resp)
|
|
||||||
except binascii.Error:
|
|
||||||
pass
|
|
||||||
if resp == 'Not Found' or not resp:
|
|
||||||
abort(404)
|
|
||||||
return Response(resp)
|
|
||||||
|
|
||||||
@site_api.route('/site/<name>/<path:file>', endpoint='siteFile')
|
|
||||||
def site_file(name: str, file: str)->Response:
|
|
||||||
"""Accept a site 'name', if pubkey then show multi-page site, if hash show single page site"""
|
|
||||||
resp: str = 'Not Found'
|
|
||||||
mime_type = mimetypes.MimeTypes().guess_type(file)[0]
|
|
||||||
|
|
||||||
# If necessary convert the name to base32 from mnemonic
|
|
||||||
if mnemonickeys.DELIMITER in name:
|
|
||||||
name = mnemonickeys.get_base32(name)
|
|
||||||
|
|
||||||
# Now make sure the key is regardless a valid base32 format ed25519 key (readding padding if necessary)
|
|
||||||
if stringvalidators.validate_pub_key(name):
|
|
||||||
name = unpaddedbase32.repad(name)
|
|
||||||
resp = sitefiles.get_file(name, file)
|
|
||||||
|
|
||||||
elif stringvalidators.validate_hash(name):
|
|
||||||
try:
|
|
||||||
resp = onionrblockapi.Block(name).bcontent
|
|
||||||
except onionrexceptions.NoDataAvailable:
|
|
||||||
abort(404)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
resp = base64.b64decode(resp)
|
|
||||||
except binascii.Error:
|
|
||||||
pass
|
|
||||||
if resp == 'Not Found' or not resp:
|
|
||||||
abort(404)
|
|
||||||
return Response(resp, mimetype=mime_type)
|
|
@ -1,49 +0,0 @@
|
|||||||
"""
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
view and interact with onionr sites
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
import onionrexceptions
|
|
||||||
from onionrutils import mnemonickeys
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from coredb import blockmetadb
|
|
||||||
from onionrblocks.onionrblockapi import Block
|
|
||||||
from onionrtypes import BlockHash
|
|
||||||
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def find_site(user_id: str) -> Union[BlockHash, None]:
|
|
||||||
"""Returns block hash str for latest block for a site by a given user id"""
|
|
||||||
# If mnemonic delim in key, convert to base32 version
|
|
||||||
if mnemonickeys.DELIMITER in user_id:
|
|
||||||
user_id = mnemonickeys.get_base32(user_id)
|
|
||||||
|
|
||||||
if not stringvalidators.validate_pub_key(user_id):
|
|
||||||
raise onionrexceptions.InvalidPubkey
|
|
||||||
|
|
||||||
found_site = None
|
|
||||||
sites = blockmetadb.get_blocks_by_type('osite')
|
|
||||||
|
|
||||||
# Find site by searching all site blocks. eww O(N) ☹️, TODO: event based
|
|
||||||
for site in sites:
|
|
||||||
site = Block(site)
|
|
||||||
if site.isSigner(user_id) and site.verifySig():
|
|
||||||
found_site = site.hash
|
|
||||||
return found_site
|
|
@ -1,79 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Read onionr site files
|
|
||||||
"""
|
|
||||||
from typing import Union, Tuple
|
|
||||||
import tarfile
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
|
|
||||||
import unpaddedbase32
|
|
||||||
|
|
||||||
from coredb import blockmetadb
|
|
||||||
from onionrblocks import onionrblockapi
|
|
||||||
from onionrblocks import insert
|
|
||||||
|
|
||||||
# Import types. Just for type hiting
|
|
||||||
from onionrtypes import UserID, DeterministicKeyPassphrase, BlockHash
|
|
||||||
|
|
||||||
from onionrcrypto import generate_deterministic
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def find_site_gzip(user_id: str)->tarfile.TarFile:
|
|
||||||
"""Return verified site tar object"""
|
|
||||||
sites = blockmetadb.get_blocks_by_type('osite')
|
|
||||||
user_site = None
|
|
||||||
unpadded_user = user_id
|
|
||||||
user_id = unpaddedbase32.repad(user_id)
|
|
||||||
for site in sites:
|
|
||||||
block = onionrblockapi.Block(site)
|
|
||||||
if block.isSigner(user_id) or block.isSigner(unpadded_user):
|
|
||||||
user_site = block
|
|
||||||
if not user_site is None:
|
|
||||||
return tarfile.open(fileobj=io.BytesIO(user_site.bcontent), mode='r')
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_file(user_id, file)->Union[bytes, None]:
|
|
||||||
"""Get a site file content"""
|
|
||||||
ret_data = ""
|
|
||||||
site = find_site_gzip(user_id)
|
|
||||||
|
|
||||||
if file.endswith('/'):
|
|
||||||
file += 'index.html'
|
|
||||||
if site is None: return None
|
|
||||||
for t_file in site.getmembers():
|
|
||||||
|
|
||||||
if t_file.name.replace('./', '') == file:
|
|
||||||
return site.extractfile(t_file)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def create_site(admin_pass: DeterministicKeyPassphrase, directory:str='.')->Tuple[UserID, BlockHash]:
|
|
||||||
public_key, private_key = generate_deterministic(admin_pass)
|
|
||||||
|
|
||||||
raw_tar = io.BytesIO()
|
|
||||||
|
|
||||||
tar = tarfile.open(mode='x:gz', fileobj=raw_tar)
|
|
||||||
tar.add(directory)
|
|
||||||
tar.close()
|
|
||||||
|
|
||||||
raw_tar.seek(0)
|
|
||||||
|
|
||||||
block_hash = insert(raw_tar.read(), header='osite', signing_key=private_key, sign=True)
|
|
||||||
|
|
||||||
return (public_key, block_hash)
|
|
@ -9,9 +9,6 @@ from gevent import sleep
|
|||||||
import gevent
|
import gevent
|
||||||
import ujson
|
import ujson
|
||||||
|
|
||||||
from onionrblocks.onionrblockapi import Block
|
|
||||||
from coredb.dbfiles import block_meta_db
|
|
||||||
from coredb.blockmetadb import get_block_list
|
|
||||||
from onionrutils.epoch import get_epoch
|
from onionrutils.epoch import get_epoch
|
||||||
from .. import wrapper
|
from .. import wrapper
|
||||||
"""
|
"""
|
||||||
@ -42,27 +39,3 @@ def stream_hello():
|
|||||||
sleep(1)
|
sleep(1)
|
||||||
return SSEWrapper.handle_sse_request(print_hello)
|
return SSEWrapper.handle_sse_request(print_hello)
|
||||||
|
|
||||||
|
|
||||||
@private_sse_blueprint.route('/recentblocks')
|
|
||||||
def stream_recent_blocks():
|
|
||||||
def _compile_json(b_list):
|
|
||||||
js = {}
|
|
||||||
block_obj = None
|
|
||||||
for block in b_list:
|
|
||||||
block_obj = Block(block)
|
|
||||||
if block_obj.isEncrypted:
|
|
||||||
js[block] = 'encrypted'
|
|
||||||
else:
|
|
||||||
js[block] = Block(block).btype
|
|
||||||
return ujson.dumps({"blocks": js}, reject_bytes=True)
|
|
||||||
|
|
||||||
def _stream_recent():
|
|
||||||
last_time = Path(block_meta_db).stat().st_ctime
|
|
||||||
while True:
|
|
||||||
if Path(block_meta_db).stat().st_ctime != last_time:
|
|
||||||
last_time = Path(block_meta_db).stat().st_ctime
|
|
||||||
yield "data: " + _compile_json(get_block_list(get_epoch() - 5)) + "\n\n"
|
|
||||||
else:
|
|
||||||
yield "data: none" + "\n\n"
|
|
||||||
sleep(5)
|
|
||||||
return SSEWrapper.handle_sse_request(_stream_recent)
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
from . import insert
|
|
||||||
from .insert import time_insert
|
|
||||||
from .blocklist import BlockList
|
|
||||||
insert = insert.insert_block
|
|
||||||
time_insert = time_insert
|
|
@ -1,11 +0,0 @@
|
|||||||
import ujson
|
|
||||||
import nacl.utils
|
|
||||||
from nacl.public import PrivateKey, SealedBox
|
|
||||||
|
|
||||||
from .blockmetadata import get_block_metadata_from_data
|
|
||||||
|
|
||||||
def block_decrypt(raw_block) -> DecryptedBlock:
|
|
||||||
block_header, user_meta, block_data = get_block_metadata_from_data(
|
|
||||||
raw_block)
|
|
||||||
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Import block data and save it
|
|
||||||
"""
|
|
||||||
from onionrexceptions import BlacklistedBlock
|
|
||||||
from onionrexceptions import DiskAllocationReached
|
|
||||||
from onionrexceptions import InvalidProof
|
|
||||||
from onionrexceptions import InvalidMetadata
|
|
||||||
import logger
|
|
||||||
from onionrutils import validatemetadata
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
from coredb import blockmetadb
|
|
||||||
from onionrblocks import blockmetadata
|
|
||||||
import onionrstorage
|
|
||||||
import onionrcrypto as crypto
|
|
||||||
from . import onionrblacklist
|
|
||||||
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def import_block_from_data(content):
|
|
||||||
blacklist = onionrblacklist.OnionrBlackList()
|
|
||||||
ret_data = False
|
|
||||||
|
|
||||||
content = bytesconverter.str_to_bytes(content)
|
|
||||||
|
|
||||||
data_hash = crypto.hashers.sha3_hash(content)
|
|
||||||
|
|
||||||
if blacklist.inBlacklist(data_hash):
|
|
||||||
raise BlacklistedBlock(f'%s is a blacklisted block {data_hash}')
|
|
||||||
|
|
||||||
# returns tuple(metadata, meta), meta is also in metadata
|
|
||||||
metas = blockmetadata.get_block_metadata_from_data(content)
|
|
||||||
metadata = metas[0]
|
|
||||||
|
|
||||||
# check if metadata is valid
|
|
||||||
if validatemetadata.validate_metadata(metadata, metas[2]):
|
|
||||||
# check if POW is enough/correct
|
|
||||||
if crypto.cryptoutils.verify_POW(content):
|
|
||||||
logger.info(f'Imported block passed proof, saving: {data_hash}.',
|
|
||||||
terminal=True)
|
|
||||||
try:
|
|
||||||
block_hash = onionrstorage.set_data(content)
|
|
||||||
except DiskAllocationReached:
|
|
||||||
logger.warn('Failed to save block due to full disk allocation')
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
blockmetadb.add_to_block_DB(block_hash, dataSaved=True)
|
|
||||||
# caches block metadata values to block database
|
|
||||||
blockmetadata.process_block_metadata(block_hash)
|
|
||||||
ret_data = block_hash
|
|
||||||
else:
|
|
||||||
raise InvalidProof
|
|
||||||
else:
|
|
||||||
raise InvalidMetadata
|
|
||||||
return ret_data
|
|
@ -1,60 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Get an auto updating list of blocks
|
|
||||||
"""
|
|
||||||
from threading import Thread
|
|
||||||
|
|
||||||
from watchdog.observers import Observer
|
|
||||||
from watchdog.events import FileSystemEventHandler
|
|
||||||
|
|
||||||
from utils.identifyhome import identify_home
|
|
||||||
from coredb.dbfiles import block_meta_db
|
|
||||||
from coredb.blockmetadb import get_block_list, get_blocks_by_type
|
|
||||||
from onionrutils.epoch import get_epoch
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class BlockList:
|
|
||||||
def __init__(self, auto_refresh=True, block_type=''):
|
|
||||||
self.block_type = block_type
|
|
||||||
self.refresh_db()
|
|
||||||
self.check_time = get_epoch()
|
|
||||||
|
|
||||||
class Refresher(FileSystemEventHandler):
|
|
||||||
@staticmethod
|
|
||||||
def on_modified(event):
|
|
||||||
if event.src_path != block_meta_db:
|
|
||||||
return
|
|
||||||
self.refresh_db()
|
|
||||||
if auto_refresh:
|
|
||||||
def auto_refresher():
|
|
||||||
observer = Observer()
|
|
||||||
observer.schedule(
|
|
||||||
Refresher(), identify_home(), recursive=False)
|
|
||||||
observer.start()
|
|
||||||
while observer.is_alive():
|
|
||||||
# call import func with timeout
|
|
||||||
observer.join(120)
|
|
||||||
Thread(target=auto_refresher, daemon=True).start()
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
return self.block_list
|
|
||||||
|
|
||||||
def refresh_db(self):
|
|
||||||
self.check_time = get_epoch()
|
|
||||||
if not self.block_type:
|
|
||||||
self.block_list = get_block_list()
|
|
||||||
else:
|
|
||||||
self.block_list = get_blocks_by_type(self.block_type)
|
|
@ -1,24 +0,0 @@
|
|||||||
'''
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Module to work with block metadata
|
|
||||||
'''
|
|
||||||
'''
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
'''
|
|
||||||
|
|
||||||
from . import hasblock, fromdata, process
|
|
||||||
has_block = hasblock.has_block
|
|
||||||
process_block_metadata = process.process_block_metadata
|
|
||||||
get_block_metadata_from_data = fromdata.get_block_metadata_from_data
|
|
@ -1,51 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Return a useful tuple of (metadata (header), meta, and data) by accepting raw block data
|
|
||||||
"""
|
|
||||||
from json import JSONDecodeError
|
|
||||||
import ujson as json
|
|
||||||
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
import logger
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def get_block_metadata_from_data(block_data):
|
|
||||||
"""
|
|
||||||
accepts block contents as string, returns a tuple of
|
|
||||||
metadata, meta (meta being internal metadata, which will be
|
|
||||||
returned as an encrypted base64 string if it is encrypted, dict if not).
|
|
||||||
"""
|
|
||||||
meta = {}
|
|
||||||
metadata = {}
|
|
||||||
data = block_data
|
|
||||||
try:
|
|
||||||
block_data = block_data.encode()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
metadata = json.loads(bytesconverter.bytes_to_str(block_data[:block_data.find(b'\n')]))
|
|
||||||
except JSONDecodeError:
|
|
||||||
pass
|
|
||||||
except ValueError:
|
|
||||||
logger.warn("Could not get metadata from:", terminal=True)
|
|
||||||
logger.warn(block_data, terminal=True)
|
|
||||||
else:
|
|
||||||
data = block_data[block_data.find(b'\n'):]
|
|
||||||
|
|
||||||
meta = metadata['meta']
|
|
||||||
return (metadata, meta, data)
|
|
@ -1,43 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Return a bool if a block is in the block metadata db or not
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
from coredb import dbfiles
|
|
||||||
import onionrexceptions
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from etc import onionrvalues
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def has_block(hash: str) -> bool:
|
|
||||||
"""Check for new block in the block meta db."""
|
|
||||||
conn = sqlite3.connect(
|
|
||||||
dbfiles.block_meta_db,
|
|
||||||
timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
if not stringvalidators.validate_hash(hash):
|
|
||||||
raise onionrexceptions.InvalidHexHash("Invalid hash")
|
|
||||||
for result in c.execute("SELECT COUNT() FROM hashes WHERE hash = ?", (hash,)):
|
|
||||||
if result[0] >= 1:
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return False
|
|
||||||
return False
|
|
@ -1,71 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Process block metadata with relevant actions
|
|
||||||
"""
|
|
||||||
from etc import onionrvalues
|
|
||||||
from onionrblocks import onionrblockapi
|
|
||||||
from onionrutils import epoch, bytesconverter
|
|
||||||
from coredb import blockmetadb
|
|
||||||
import logger
|
|
||||||
from onionrplugins import onionrevents
|
|
||||||
import onionrexceptions
|
|
||||||
from onionrusers import onionrusers
|
|
||||||
from onionrutils import updater
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def process_block_metadata(blockHash: str):
|
|
||||||
"""
|
|
||||||
Read metadata from a block and cache it to the block database.
|
|
||||||
|
|
||||||
blockHash -> sha3_256 hex formatted hash of Onionr block
|
|
||||||
"""
|
|
||||||
curTime = epoch.get_rounded_epoch(roundS=60)
|
|
||||||
myBlock = onionrblockapi.Block(blockHash)
|
|
||||||
if myBlock.isEncrypted:
|
|
||||||
myBlock.decrypt()
|
|
||||||
if (myBlock.isEncrypted and myBlock.decrypted) or (not myBlock.isEncrypted):
|
|
||||||
blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks
|
|
||||||
|
|
||||||
signer = bytesconverter.bytes_to_str(myBlock.signer)
|
|
||||||
valid = myBlock.verifySig()
|
|
||||||
if valid:
|
|
||||||
if myBlock.getMetadata('newFSKey') is not None:
|
|
||||||
try:
|
|
||||||
onionrusers.OnionrUser(signer).addForwardKey(myBlock.getMetadata('newFSKey'))
|
|
||||||
except onionrexceptions.InvalidPubkey:
|
|
||||||
logger.warn('%s has invalid forward secrecy key to add: %s' % (signer, myBlock.getMetadata('newFSKey')))
|
|
||||||
|
|
||||||
try:
|
|
||||||
if len(blockType) <= onionrvalues.MAX_BLOCK_TYPE_LENGTH:
|
|
||||||
blockmetadb.update_block_info(blockHash, 'dataType', blockType)
|
|
||||||
except TypeError:
|
|
||||||
logger.warn("Missing block information")
|
|
||||||
pass
|
|
||||||
# Set block expire time if specified
|
|
||||||
try:
|
|
||||||
expireTime = int(myBlock.getHeader('expire'))
|
|
||||||
# test that expire time is an integer of sane length (for epoch)
|
|
||||||
# doesn't matter if its too large because of the min() func below
|
|
||||||
if not len(str(expireTime)) < 20: raise ValueError('timestamp invalid')
|
|
||||||
except (ValueError, TypeError) as e:
|
|
||||||
expireTime = onionrvalues.DEFAULT_EXPIRE + curTime
|
|
||||||
finally:
|
|
||||||
expireTime = min(expireTime, curTime + onionrvalues.DEFAULT_EXPIRE)
|
|
||||||
blockmetadb.update_block_info(blockHash, 'expire', expireTime)
|
|
||||||
|
|
||||||
if blockType == 'update': updater.update_event(myBlock)
|
|
||||||
onionrevents.event('processblocks', data = {'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid})
|
|
@ -1,35 +0,0 @@
|
|||||||
"""Onionr - P2P Anonymous Storage Network.
|
|
||||||
|
|
||||||
Delete but do not blacklist plaintext blocks
|
|
||||||
"""
|
|
||||||
from coredb import blockmetadb
|
|
||||||
from onionrstorage.removeblock import remove_block
|
|
||||||
import onionrstorage
|
|
||||||
from .onionrblockapi import Block
|
|
||||||
import onionrexceptions
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def delete_plaintext_no_blacklist():
|
|
||||||
"""Delete, but do not blacklist, plaintext blocks."""
|
|
||||||
|
|
||||||
block_list = blockmetadb.get_block_list()
|
|
||||||
|
|
||||||
for block in block_list:
|
|
||||||
block = Block(hash=block, decrypt=False)
|
|
||||||
if not block.isEncrypted:
|
|
||||||
remove_block(block.hash) # delete metadata entry
|
|
||||||
onionrstorage.deleteBlock(block.hash) # delete block data
|
|
@ -1,4 +0,0 @@
|
|||||||
from . import main, timeinsert
|
|
||||||
|
|
||||||
insert_block = main.insert_block
|
|
||||||
time_insert = timeinsert.time_insert
|
|
@ -1,267 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Create and insert Onionr blocks
|
|
||||||
"""
|
|
||||||
from typing import Union
|
|
||||||
import ujson as json
|
|
||||||
|
|
||||||
from gevent import spawn
|
|
||||||
|
|
||||||
from onionrutils import bytesconverter, epoch
|
|
||||||
import filepaths
|
|
||||||
import onionrstorage
|
|
||||||
from .. import storagecounter
|
|
||||||
from onionrplugins import onionrevents as events
|
|
||||||
from etc import onionrvalues
|
|
||||||
import config
|
|
||||||
import onionrcrypto as crypto
|
|
||||||
import onionrexceptions
|
|
||||||
from onionrusers import onionrusers
|
|
||||||
from onionrutils import localcommand, stringvalidators
|
|
||||||
from .. import blockmetadata
|
|
||||||
import coredb
|
|
||||||
from onionrproofs import subprocesspow
|
|
||||||
import logger
|
|
||||||
from onionrtypes import UserIDSecretKey
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
storage_counter = storagecounter.StorageCounter()
|
|
||||||
|
|
||||||
|
|
||||||
def _check_upload_queue():
|
|
||||||
"""
|
|
||||||
Return the current upload queue len.
|
|
||||||
|
|
||||||
raises OverflowError if max, false if api not running
|
|
||||||
"""
|
|
||||||
max_upload_queue: int = 5000
|
|
||||||
queue = localcommand.local_command('/gethidden', max_wait=10)
|
|
||||||
up_queue = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
up_queue = len(queue.splitlines())
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if up_queue >= max_upload_queue:
|
|
||||||
raise OverflowError
|
|
||||||
return up_queue
|
|
||||||
|
|
||||||
|
|
||||||
def insert_block(data: Union[str, bytes], header: str = 'txt',
|
|
||||||
sign: bool = False, encryptType: str = '', symKey: str = '',
|
|
||||||
asymPeer: str = '', meta: dict = {},
|
|
||||||
expire: Union[int, None] = None, disableForward: bool = False,
|
|
||||||
signing_key: UserIDSecretKey = '') -> Union[str, bool]:
|
|
||||||
"""
|
|
||||||
Create and insert a block into the network.
|
|
||||||
|
|
||||||
encryptType must be specified to encrypt a block
|
|
||||||
if expire is less than date, assumes seconds into future.
|
|
||||||
if not assume exact epoch
|
|
||||||
"""
|
|
||||||
our_private_key = crypto.priv_key
|
|
||||||
our_pub_key = crypto.pub_key
|
|
||||||
|
|
||||||
allocationReachedMessage = 'Cannot insert block, disk allocation reached.'
|
|
||||||
if storage_counter.is_full():
|
|
||||||
logger.error(allocationReachedMessage)
|
|
||||||
raise onionrexceptions.DiskAllocationReached
|
|
||||||
|
|
||||||
if signing_key != '':
|
|
||||||
# if it was specified to use an alternative private key
|
|
||||||
our_private_key = signing_key
|
|
||||||
our_pub_key = bytesconverter.bytes_to_str(
|
|
||||||
crypto.cryptoutils.get_pub_key_from_priv(our_private_key))
|
|
||||||
|
|
||||||
retData = False
|
|
||||||
|
|
||||||
if type(data) is None:
|
|
||||||
raise ValueError('Data cannot be none')
|
|
||||||
|
|
||||||
createTime = epoch.get_epoch()
|
|
||||||
|
|
||||||
dataNonce = bytesconverter.bytes_to_str(crypto.hashers.sha3_hash(data))
|
|
||||||
try:
|
|
||||||
with open(filepaths.data_nonce_file, 'r') as nonces:
|
|
||||||
if dataNonce in nonces:
|
|
||||||
return retData
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
# record nonce
|
|
||||||
with open(filepaths.data_nonce_file, 'a') as nonce_file:
|
|
||||||
nonce_file.write(dataNonce + '\n')
|
|
||||||
|
|
||||||
plaintext = data
|
|
||||||
plaintextMeta = {}
|
|
||||||
plaintextPeer = asymPeer
|
|
||||||
|
|
||||||
retData = ''
|
|
||||||
signature = ''
|
|
||||||
signer = ''
|
|
||||||
metadata = {}
|
|
||||||
|
|
||||||
# metadata is full block metadata
|
|
||||||
# meta is internal, user specified metadata
|
|
||||||
|
|
||||||
# only use header if not set in provided meta
|
|
||||||
|
|
||||||
meta['type'] = str(header)
|
|
||||||
|
|
||||||
if encryptType in ('asym', 'sym'):
|
|
||||||
metadata['encryptType'] = encryptType
|
|
||||||
else:
|
|
||||||
if not config.get('general.store_plaintext_blocks', True):
|
|
||||||
raise onionrexceptions.InvalidMetadata(
|
|
||||||
"Plaintext blocks are disabled, " +
|
|
||||||
"yet a plaintext block was being inserted")
|
|
||||||
if encryptType not in ('', None):
|
|
||||||
raise onionrexceptions.InvalidMetadata(
|
|
||||||
'encryptType must be asym or sym, or blank')
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = data.encode()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if encryptType == 'asym':
|
|
||||||
# Duplicate the time in encrypted messages to help prevent replays
|
|
||||||
meta['rply'] = createTime
|
|
||||||
if sign and asymPeer != our_pub_key:
|
|
||||||
try:
|
|
||||||
forwardEncrypted = onionrusers.OnionrUser(
|
|
||||||
asymPeer).forwardEncrypt(data)
|
|
||||||
data = forwardEncrypted[0]
|
|
||||||
meta['forwardEnc'] = True
|
|
||||||
# Expire time of key. no sense keeping block after that
|
|
||||||
expire = forwardEncrypted[2]
|
|
||||||
except onionrexceptions.InvalidPubkey:
|
|
||||||
pass
|
|
||||||
if not disableForward:
|
|
||||||
fsKey = onionrusers.OnionrUser(asymPeer).generateForwardKey()
|
|
||||||
meta['newFSKey'] = fsKey
|
|
||||||
jsonMeta = json.dumps(meta)
|
|
||||||
plaintextMeta = jsonMeta
|
|
||||||
if sign:
|
|
||||||
signature = crypto.signing.ed_sign(
|
|
||||||
jsonMeta.encode() + data, key=our_private_key, encodeResult=True)
|
|
||||||
signer = our_pub_key
|
|
||||||
|
|
||||||
if len(jsonMeta) > 1000:
|
|
||||||
raise onionrexceptions.InvalidMetadata(
|
|
||||||
'meta in json encoded form must not exceed 1000 bytes')
|
|
||||||
|
|
||||||
# encrypt block metadata/sig/content
|
|
||||||
if encryptType == 'sym':
|
|
||||||
raise NotImplementedError("not yet implemented")
|
|
||||||
elif encryptType == 'asym':
|
|
||||||
if stringvalidators.validate_pub_key(asymPeer):
|
|
||||||
# Encrypt block data with forward secrecy key first, but not meta
|
|
||||||
jsonMeta = json.dumps(meta)
|
|
||||||
jsonMeta = crypto.encryption.pub_key_encrypt(
|
|
||||||
jsonMeta, asymPeer, encodedData=True).decode()
|
|
||||||
data = crypto.encryption.pub_key_encrypt(
|
|
||||||
data, asymPeer, encodedData=False)
|
|
||||||
signature = crypto.encryption.pub_key_encrypt(
|
|
||||||
signature, asymPeer, encodedData=True).decode()
|
|
||||||
signer = crypto.encryption.pub_key_encrypt(
|
|
||||||
signer, asymPeer, encodedData=True).decode()
|
|
||||||
try:
|
|
||||||
onionrusers.OnionrUser(asymPeer, saveUser=True)
|
|
||||||
except ValueError:
|
|
||||||
# if peer is already known
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
logger.warn(f"{asymPeer} is not a valid key to make a block to")
|
|
||||||
raise onionrexceptions.InvalidPubkey(
|
|
||||||
'tried to make block to invalid key is not a valid base32 encoded ed25519 key')
|
|
||||||
|
|
||||||
# compile metadata
|
|
||||||
metadata['meta'] = jsonMeta
|
|
||||||
if len(signature) > 0: # I don't like not pattern
|
|
||||||
metadata['sig'] = signature
|
|
||||||
metadata['signer'] = signer
|
|
||||||
metadata['time'] = createTime
|
|
||||||
|
|
||||||
# ensure expire is integer and of sane length
|
|
||||||
if type(expire) is not type(None): # noqa
|
|
||||||
if not len(str(int(expire))) < 20:
|
|
||||||
raise ValueError(
|
|
||||||
'expire must be valid int less than 20 digits in length')
|
|
||||||
# if expire is less than date, assume seconds into future
|
|
||||||
if expire < epoch.get_epoch():
|
|
||||||
expire = epoch.get_epoch() + expire
|
|
||||||
metadata['expire'] = expire
|
|
||||||
|
|
||||||
# send block data (and metadata) to POW module to get tokenized block data
|
|
||||||
payload = subprocesspow.SubprocessPOW(data, metadata).start()
|
|
||||||
|
|
||||||
if payload != False: # noqa
|
|
||||||
try:
|
|
||||||
retData = onionrstorage.set_data(payload)
|
|
||||||
except onionrexceptions.DiskAllocationReached:
|
|
||||||
logger.error(allocationReachedMessage)
|
|
||||||
retData = False
|
|
||||||
else:
|
|
||||||
if disableForward:
|
|
||||||
logger.warn(
|
|
||||||
f'{retData} asym encrypted block created w/o ephemerality')
|
|
||||||
"""
|
|
||||||
Tell the api server through localCommand to wait for the daemon to
|
|
||||||
upload this block to make statistical analysis more difficult
|
|
||||||
"""
|
|
||||||
spawn(
|
|
||||||
localcommand.local_command,
|
|
||||||
'/daemon-event/upload_event',
|
|
||||||
post=True,
|
|
||||||
is_json=True,
|
|
||||||
post_data={'block': retData}
|
|
||||||
).get(timeout=5)
|
|
||||||
coredb.blockmetadb.add.add_to_block_DB(
|
|
||||||
retData, selfInsert=True, dataSaved=True)
|
|
||||||
|
|
||||||
if expire is None:
|
|
||||||
coredb.blockmetadb.update_block_info(
|
|
||||||
retData, 'expire',
|
|
||||||
createTime + onionrvalues.DEFAULT_EXPIRE)
|
|
||||||
else:
|
|
||||||
coredb.blockmetadb.update_block_info(retData, 'expire', expire)
|
|
||||||
|
|
||||||
blockmetadata.process_block_metadata(retData)
|
|
||||||
|
|
||||||
if retData != False: # noqa
|
|
||||||
if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS:
|
|
||||||
events.event('insertdeniable',
|
|
||||||
{'content': plaintext, 'meta': plaintextMeta,
|
|
||||||
'hash': retData,
|
|
||||||
'peer': bytesconverter.bytes_to_str(asymPeer)},
|
|
||||||
threaded=True)
|
|
||||||
else:
|
|
||||||
events.event('insertblock',
|
|
||||||
{'content': plaintext, 'meta': plaintextMeta,
|
|
||||||
'hash': retData,
|
|
||||||
'peer': bytesconverter.bytes_to_str(asymPeer)},
|
|
||||||
threaded=True)
|
|
||||||
|
|
||||||
spawn(
|
|
||||||
localcommand.local_command,
|
|
||||||
'/daemon-event/remove_from_insert_queue_wrapper',
|
|
||||||
post=True,
|
|
||||||
post_data={'block_hash':
|
|
||||||
bytesconverter.bytes_to_str(
|
|
||||||
crypto.hashers.sha3_hash(data))},
|
|
||||||
is_json=True
|
|
||||||
).get(timeout=5)
|
|
||||||
return retData
|
|
@ -1,51 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Wrapper to insert blocks with variable delay
|
|
||||||
"""
|
|
||||||
from . import main
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def time_insert(*args, **kwargs):
|
|
||||||
"""Block insert wrapper to allow for insertions independent of mixmate.
|
|
||||||
|
|
||||||
Takes exact args as insert_block, with additional keyword:
|
|
||||||
delay=n; where n=seconds to tell initial nodes to delay share for.
|
|
||||||
|
|
||||||
defaults to 0 or previously set value in current block meta
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
kwargs['meta']
|
|
||||||
except KeyError:
|
|
||||||
kwargs['meta'] = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
delay = int(kwargs['meta']['dly'])
|
|
||||||
except KeyError:
|
|
||||||
delay = 0
|
|
||||||
try:
|
|
||||||
delay = kwargs['delay']
|
|
||||||
del kwargs['delay']
|
|
||||||
except KeyError:
|
|
||||||
delay = 0
|
|
||||||
|
|
||||||
# Ensure delay >=0
|
|
||||||
if delay < 0:
|
|
||||||
raise ValueError('delay cannot be less than 0')
|
|
||||||
|
|
||||||
kwargs['meta']['dly'] = delay
|
|
||||||
|
|
||||||
return main.insert_block(*args, **kwargs)
|
|
@ -1,130 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Handle maintenance of a blacklist database, for blocks and peers
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
from onionrplugins.onionrevents import event
|
|
||||||
import onionrcrypto
|
|
||||||
from onionrutils import epoch, bytesconverter
|
|
||||||
from coredb import dbfiles
|
|
||||||
from etc.onionrvalues import DATABASE_LOCK_TIMEOUT
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class OnionrBlackList:
|
|
||||||
def __init__(self):
|
|
||||||
self.blacklistDB = dbfiles.blacklist_db
|
|
||||||
|
|
||||||
if not os.path.exists(dbfiles.blacklist_db):
|
|
||||||
self.generateDB()
|
|
||||||
return
|
|
||||||
|
|
||||||
def inBlacklist(self, data):
|
|
||||||
hashed = bytesconverter.bytes_to_str(
|
|
||||||
onionrcrypto.hashers.sha3_hash(data))
|
|
||||||
retData = False
|
|
||||||
|
|
||||||
if not hashed.isalnum():
|
|
||||||
raise Exception("Hashed data is not alpha numeric")
|
|
||||||
if len(hashed) > 64:
|
|
||||||
raise Exception("Hashed data is too large")
|
|
||||||
|
|
||||||
for i in self._dbExecute(
|
|
||||||
"SELECT * FROM blacklist WHERE hash = ?", (hashed,)):
|
|
||||||
# this only executes if an entry is present by that hash
|
|
||||||
retData = True
|
|
||||||
break
|
|
||||||
|
|
||||||
return retData
|
|
||||||
|
|
||||||
def _dbExecute(self, toExec, params=()):
|
|
||||||
conn = sqlite3.connect(self.blacklistDB, timeout=DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
retData = c.execute(toExec, params)
|
|
||||||
conn.commit()
|
|
||||||
return retData
|
|
||||||
|
|
||||||
def deleteBeforeDate(self, date):
|
|
||||||
# TODO, delete blacklist entries before date
|
|
||||||
return
|
|
||||||
|
|
||||||
def deleteExpired(self, dataType=0):
|
|
||||||
"""Delete expired entries"""
|
|
||||||
deleteList = []
|
|
||||||
curTime = epoch.get_epoch()
|
|
||||||
|
|
||||||
try:
|
|
||||||
int(dataType)
|
|
||||||
except AttributeError:
|
|
||||||
raise TypeError("dataType must be int")
|
|
||||||
|
|
||||||
for i in self._dbExecute(
|
|
||||||
'SELECT * FROM blacklist WHERE dataType = ?', (dataType,)):
|
|
||||||
if i[1] == dataType:
|
|
||||||
if (curTime - i[2]) >= i[3]:
|
|
||||||
deleteList.append(i[0])
|
|
||||||
|
|
||||||
for thing in deleteList:
|
|
||||||
self._dbExecute("DELETE FROM blacklist WHERE hash = ?", (thing,))
|
|
||||||
|
|
||||||
def generateDB(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
def clearDB(self):
|
|
||||||
self._dbExecute("""DELETE FROM blacklist;""")
|
|
||||||
|
|
||||||
def getList(self):
|
|
||||||
data = self._dbExecute('SELECT * FROM blacklist')
|
|
||||||
myList = []
|
|
||||||
for i in data:
|
|
||||||
myList.append(i[0])
|
|
||||||
return myList
|
|
||||||
|
|
||||||
def addToDB(self, data, dataType=0, expire=0):
|
|
||||||
"""Add to the blacklist. Intended to be block hash, block data, peers, or transport addresses
|
|
||||||
0=block
|
|
||||||
1=peer
|
|
||||||
2=pubkey
|
|
||||||
"""
|
|
||||||
|
|
||||||
# we hash the data so we can remove data entirely from our node's disk
|
|
||||||
hashed = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(data))
|
|
||||||
|
|
||||||
event('blacklist_add', data={'data': data, 'hash': hashed})
|
|
||||||
|
|
||||||
if len(hashed) > 64:
|
|
||||||
raise Exception("Hashed data is too large")
|
|
||||||
|
|
||||||
if not hashed.isalnum():
|
|
||||||
raise Exception("Hashed data is not alpha numeric")
|
|
||||||
try:
|
|
||||||
int(dataType)
|
|
||||||
except ValueError:
|
|
||||||
raise Exception("dataType is not int")
|
|
||||||
try:
|
|
||||||
int(expire)
|
|
||||||
except ValueError:
|
|
||||||
raise Exception("expire is not int")
|
|
||||||
if self.inBlacklist(hashed):
|
|
||||||
return
|
|
||||||
insert = (hashed,)
|
|
||||||
blacklistDate = epoch.get_epoch()
|
|
||||||
try:
|
|
||||||
self._dbExecute("INSERT INTO blacklist (hash, dataType, blacklistDate, expire) VALUES(?, ?, ?, ?);", (str(hashed), dataType, blacklistDate, expire))
|
|
||||||
except sqlite3.IntegrityError:
|
|
||||||
pass
|
|
@ -1,443 +0,0 @@
|
|||||||
"""Onionr - P2P Anonymous Storage Network.
|
|
||||||
|
|
||||||
OnionrBlocks class for abstraction of blocks
|
|
||||||
"""
|
|
||||||
import datetime
|
|
||||||
import onionrstorage
|
|
||||||
|
|
||||||
import unpaddedbase32
|
|
||||||
import ujson as json
|
|
||||||
import nacl.exceptions
|
|
||||||
|
|
||||||
import logger
|
|
||||||
import onionrexceptions
|
|
||||||
from onionrusers import onionrusers
|
|
||||||
from onionrutils import stringvalidators, epoch
|
|
||||||
from coredb import blockmetadb
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
import onionrblocks
|
|
||||||
from onionrcrypto import encryption, cryptoutils as cryptoutils, signing
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Block:
|
|
||||||
blockCacheOrder = list() # NEVER write your own code that writes to this!
|
|
||||||
blockCache = dict() # should never be accessed directly, look at Block.getCache()
|
|
||||||
|
|
||||||
def __init__(self, hash = None, type = None, content = None, expire=None, decrypt=False, bypassReplayCheck=False):
|
|
||||||
# take from arguments
|
|
||||||
# sometimes people input a bytes object instead of str in `hash`
|
|
||||||
if (not hash is None) and isinstance(hash, bytes):
|
|
||||||
hash = hash.decode()
|
|
||||||
|
|
||||||
self.hash = hash
|
|
||||||
self.btype = type
|
|
||||||
self.bcontent = content
|
|
||||||
self.expire = expire
|
|
||||||
self.bypassReplayCheck = bypassReplayCheck
|
|
||||||
|
|
||||||
# initialize variables
|
|
||||||
self.valid = True
|
|
||||||
self.raw = None
|
|
||||||
self.signed = False
|
|
||||||
self.signature = None
|
|
||||||
self.signedData = None
|
|
||||||
self.blockFile = None
|
|
||||||
self.bheader = {}
|
|
||||||
self.bmetadata = {}
|
|
||||||
self.isEncrypted = False
|
|
||||||
self.decrypted = False
|
|
||||||
self.signer = None
|
|
||||||
self.validSig = False
|
|
||||||
self.autoDecrypt = decrypt
|
|
||||||
self.claimedTime = None
|
|
||||||
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def decrypt(self, encodedData = True):
|
|
||||||
"""
|
|
||||||
Decrypt a block, loading decrypted data into their vars
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.decrypted:
|
|
||||||
return True
|
|
||||||
retData = False
|
|
||||||
# decrypt data
|
|
||||||
if self.getHeader('encryptType') == 'asym':
|
|
||||||
try:
|
|
||||||
self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=False)
|
|
||||||
|
|
||||||
bmeta = encryption.pub_key_decrypt(self.bmetadata, encodedData=encodedData)
|
|
||||||
|
|
||||||
try:
|
|
||||||
bmeta = bmeta.decode()
|
|
||||||
except AttributeError:
|
|
||||||
# yet another bytes fix
|
|
||||||
pass
|
|
||||||
self.bmetadata = json.loads(bmeta)
|
|
||||||
self.signature = encryption.pub_key_decrypt(self.signature, encodedData=encodedData)
|
|
||||||
|
|
||||||
self.signer = encryption.pub_key_decrypt(self.signer, encodedData=encodedData)
|
|
||||||
|
|
||||||
self.bheader['signer'] = self.signer.decode()
|
|
||||||
self.signedData = json.dumps(self.bmetadata).encode() + self.bcontent
|
|
||||||
|
|
||||||
if not self.signer is None:
|
|
||||||
if not self.verifySig():
|
|
||||||
raise onionrexceptions.SignatureError("Block has invalid signature")
|
|
||||||
|
|
||||||
# Check for replay attacks
|
|
||||||
try:
|
|
||||||
if epoch.get_epoch() - blockmetadb.get_block_date(self.hash) > 60:
|
|
||||||
if not cryptoutils.replay_validator(self.bmetadata['rply']): raise onionrexceptions.ReplayAttack
|
|
||||||
except (AssertionError, KeyError, TypeError, onionrexceptions.ReplayAttack) as e:
|
|
||||||
if not self.bypassReplayCheck:
|
|
||||||
# Zero out variables to prevent reading of replays
|
|
||||||
self.bmetadata = {}
|
|
||||||
self.signer = ''
|
|
||||||
self.bheader['signer'] = ''
|
|
||||||
self.signedData = ''
|
|
||||||
self.signature = ''
|
|
||||||
raise onionrexceptions.ReplayAttack('Signature is too old. possible replay attack')
|
|
||||||
try:
|
|
||||||
if not self.bmetadata['forwardEnc']: raise KeyError
|
|
||||||
except (AssertionError, KeyError) as e:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
self.bcontent = onionrusers.OnionrUser(self.signer).forwardDecrypt(self.bcontent)
|
|
||||||
except (onionrexceptions.DecryptionError, nacl.exceptions.CryptoError) as e:
|
|
||||||
logger.error(str(e))
|
|
||||||
pass
|
|
||||||
except (nacl.exceptions.CryptoError,) as e:
|
|
||||||
logger.debug(f'Could not decrypt block. encodedData: {encodedData}. Either invalid key or corrupted data ' + str(e))
|
|
||||||
except onionrexceptions.ReplayAttack:
|
|
||||||
logger.warn('%s is possibly a replay attack' % (self.hash,))
|
|
||||||
else:
|
|
||||||
retData = True
|
|
||||||
self.decrypted = True
|
|
||||||
return retData
|
|
||||||
|
|
||||||
def verifySig(self):
|
|
||||||
"""
|
|
||||||
Verify if a block's signature is signed by its claimed signer
|
|
||||||
"""
|
|
||||||
if self.signer is None:
|
|
||||||
return False
|
|
||||||
if signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
|
|
||||||
self.validSig = True
|
|
||||||
else:
|
|
||||||
self.validSig = False
|
|
||||||
return self.validSig
|
|
||||||
|
|
||||||
def update(self, data = None, file = None):
|
|
||||||
"""
|
|
||||||
Loads data from a block in to the current object.
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- data (str):
|
|
||||||
- if None: will load from file by hash
|
|
||||||
- else: will load from `data` string
|
|
||||||
- file (str):
|
|
||||||
- if None: will load from file specified in this parameter
|
|
||||||
- else: will load from wherever block is stored by hash
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (bool): indicates whether or not the operation was successful
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# import from string
|
|
||||||
blockdata = data
|
|
||||||
|
|
||||||
# import from file
|
|
||||||
if blockdata is None:
|
|
||||||
try:
|
|
||||||
blockdata = onionrstorage.getData(self.getHash())#.decode()
|
|
||||||
except AttributeError:
|
|
||||||
raise onionrexceptions.NoDataAvailable('Block does not exist')
|
|
||||||
else:
|
|
||||||
self.blockFile = None
|
|
||||||
# parse block
|
|
||||||
self.raw = blockdata
|
|
||||||
self.bheader = json.loads(self.getRaw()[:self.getRaw().index(b'\n')])
|
|
||||||
self.bcontent = self.getRaw()[self.getRaw().index(b'\n') + 1:]
|
|
||||||
if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
|
|
||||||
self.bmetadata = self.getHeader('meta', None)
|
|
||||||
self.isEncrypted = True
|
|
||||||
else:
|
|
||||||
self.bmetadata = json.loads(self.getHeader('meta', None))
|
|
||||||
self.btype = self.getMetadata('type', None)
|
|
||||||
self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
|
|
||||||
# TODO: detect if signer is hash of pubkey or not
|
|
||||||
self.signer = self.getHeader('signer', None)
|
|
||||||
self.signature = self.getHeader('sig', None)
|
|
||||||
# signed data is jsonMeta + block content (no linebreak)
|
|
||||||
self.signedData = (None if not self.isSigned() else self.getHeader('meta').encode() + self.getContent())
|
|
||||||
self.date = blockmetadb.get_block_date(self.getHash())
|
|
||||||
self.claimedTime = self.getHeader('time', None)
|
|
||||||
|
|
||||||
if not self.getDate() is None:
|
|
||||||
self.date = datetime.datetime.fromtimestamp(self.getDate())
|
|
||||||
|
|
||||||
self.valid = True
|
|
||||||
|
|
||||||
if self.autoDecrypt:
|
|
||||||
self.decrypt()
|
|
||||||
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.warn('Failed to parse block %s' % self.getHash(), error = e, timestamp = False)
|
|
||||||
|
|
||||||
|
|
||||||
self.valid = False
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# getters
|
|
||||||
|
|
||||||
def getExpire(self):
|
|
||||||
"""
|
|
||||||
Returns the expire time for a block
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (int): the expire time for a block, or None
|
|
||||||
"""
|
|
||||||
return self.expire
|
|
||||||
|
|
||||||
def getHash(self):
|
|
||||||
"""
|
|
||||||
Returns the hash of the block if saved to file
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (str): the hash of the block, or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.hash
|
|
||||||
|
|
||||||
def getType(self):
|
|
||||||
"""
|
|
||||||
Returns the type of the block
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (str): the type of the block
|
|
||||||
"""
|
|
||||||
return self.btype
|
|
||||||
|
|
||||||
def getRaw(self):
|
|
||||||
"""
|
|
||||||
Returns the raw contents of the block, if saved to file
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (bytes): the raw contents of the block, or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.raw
|
|
||||||
|
|
||||||
def getHeader(self, key = None, default = None):
|
|
||||||
"""
|
|
||||||
Returns the header information
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- key (str): only returns the value of the key in the header
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (dict/str): either the whole header as a dict, or one value
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not key is None:
|
|
||||||
if key in self.getHeader():
|
|
||||||
return self.getHeader()[key]
|
|
||||||
return default
|
|
||||||
return self.bheader
|
|
||||||
|
|
||||||
def getMetadata(self, key = None, default = None):
|
|
||||||
"""
|
|
||||||
Returns the metadata information
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- key (str): only returns the value of the key in the metadata
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (dict/str): either the whole metadata as a dict, or one value
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not key is None:
|
|
||||||
if key in self.getMetadata():
|
|
||||||
return self.getMetadata()[key]
|
|
||||||
return default
|
|
||||||
return self.bmetadata
|
|
||||||
|
|
||||||
def getContent(self):
|
|
||||||
"""
|
|
||||||
Returns the contents of the block
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (str): the contents of the block
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.bcontent
|
|
||||||
|
|
||||||
def getDate(self):
|
|
||||||
"""
|
|
||||||
Returns the date that the block was received, if loaded from file
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (datetime): the date that the block was received
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.date
|
|
||||||
|
|
||||||
def getBlockFile(self):
|
|
||||||
"""
|
|
||||||
Returns the location of the block file if it is saved
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (str): the location of the block file, or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.blockFile
|
|
||||||
|
|
||||||
def isValid(self):
|
|
||||||
"""
|
|
||||||
Checks if the block is valid
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (bool): whether or not the block is valid
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.valid
|
|
||||||
|
|
||||||
def isSigned(self):
|
|
||||||
"""
|
|
||||||
Checks if the block was signed
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (bool): whether or not the block is signed
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.signed
|
|
||||||
|
|
||||||
def getSignature(self):
|
|
||||||
"""
|
|
||||||
Returns the base64-encoded signature
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (str): the signature, or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.signature
|
|
||||||
|
|
||||||
def getSignedData(self):
|
|
||||||
"""
|
|
||||||
Returns the data that was signed
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (str): the data that was signed, or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.signedData
|
|
||||||
|
|
||||||
def isSigner(self, signer, encodedData = True):
|
|
||||||
"""
|
|
||||||
Checks if the block was signed by the signer inputted
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- signer (str): the public key of the signer to check against
|
|
||||||
- encodedData (bool): whether or not the `signer` argument is base64 encoded
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (bool): whether or not the signer of the block is the signer inputted
|
|
||||||
"""
|
|
||||||
signer = unpaddedbase32.repad(bytesconverter.str_to_bytes(signer))
|
|
||||||
try:
|
|
||||||
if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# setters
|
|
||||||
|
|
||||||
def setType(self, btype):
|
|
||||||
"""
|
|
||||||
Sets the type of the block
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- btype (str): the type of block to be set to
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (Block): the Block instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.btype = btype
|
|
||||||
return self
|
|
||||||
|
|
||||||
def setMetadata(self, key, val):
|
|
||||||
"""
|
|
||||||
Sets a custom metadata value
|
|
||||||
|
|
||||||
Metadata should not store block-specific data structures.
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- key (str): the key
|
|
||||||
- val: the value (type is irrelevant)
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (Block): the Block instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.bmetadata[key] = val
|
|
||||||
return self
|
|
||||||
|
|
||||||
def setContent(self, bcontent):
|
|
||||||
"""
|
|
||||||
Sets the contents of the block
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- bcontent (str): the contents to be set to
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (Block): the Block instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.bcontent = str(bcontent)
|
|
||||||
return self
|
|
||||||
|
|
||||||
# static functions
|
|
||||||
def exists(bHash):
|
|
||||||
"""
|
|
||||||
Checks if a block is saved to file or not
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- hash (str/Block):
|
|
||||||
- if (Block): check if this block is saved to file
|
|
||||||
- if (str): check if a block by this hash is in file
|
|
||||||
|
|
||||||
Outputs:
|
|
||||||
- (bool): whether or not the block file exists
|
|
||||||
"""
|
|
||||||
|
|
||||||
# no input data? scrap it.
|
|
||||||
if bHash is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if isinstance(bHash, Block):
|
|
||||||
bHash = bHash.getHash()
|
|
||||||
|
|
||||||
ret = isinstance(onionrstorage.getData(bHash), type(None))
|
|
||||||
|
|
||||||
return not ret
|
|
@ -1,96 +0,0 @@
|
|||||||
"""
|
|
||||||
Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Keep track of how much disk space we're using
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from threading import Thread
|
|
||||||
|
|
||||||
from watchdog.observers import Observer
|
|
||||||
from watchdog.events import FileSystemEventHandler
|
|
||||||
|
|
||||||
import config
|
|
||||||
from filepaths import usage_file
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
config.reload()
|
|
||||||
|
|
||||||
|
|
||||||
def _read_data_file(f) -> int:
|
|
||||||
amount = 0
|
|
||||||
try:
|
|
||||||
with open(f, 'r') as f:
|
|
||||||
amount = int(f.read())
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
except ValueError:
|
|
||||||
pass # Possibly happens when the file is empty
|
|
||||||
return amount
|
|
||||||
|
|
||||||
|
|
||||||
class StorageCounter:
|
|
||||||
def __init__(self):
|
|
||||||
self.data_file = usage_file
|
|
||||||
self.amount: int = None
|
|
||||||
Path(self.data_file).touch()
|
|
||||||
|
|
||||||
def auto_refresher():
|
|
||||||
class Refresher(FileSystemEventHandler):
|
|
||||||
@staticmethod
|
|
||||||
def on_modified(event):
|
|
||||||
self.amount = _read_data_file(self.data_file)
|
|
||||||
observer = Observer()
|
|
||||||
observer.schedule(Refresher(), usage_file)
|
|
||||||
observer.start()
|
|
||||||
while observer.is_alive():
|
|
||||||
# call import func with timeout
|
|
||||||
observer.join(120)
|
|
||||||
Thread(target=auto_refresher, daemon=True).start()
|
|
||||||
self.amount = _read_data_file(self.data_file)
|
|
||||||
|
|
||||||
def is_full(self) -> bool:
|
|
||||||
"""Returns if the allocated disk space is full (this is Onionr config,
|
|
||||||
not true FS capacity)"""
|
|
||||||
ret_data = False
|
|
||||||
if config.get('allocations.disk', 1073741824) <= (self.amount + 1000):
|
|
||||||
ret_data = True
|
|
||||||
return ret_data
|
|
||||||
|
|
||||||
def _update(self, data):
|
|
||||||
with open(self.data_file, 'w') as data_file:
|
|
||||||
data_file.write(str(data))
|
|
||||||
|
|
||||||
def get_percent(self) -> int:
|
|
||||||
"""Return percent (decimal/float) of disk space we're using"""
|
|
||||||
amount = self.amount
|
|
||||||
return round(amount / config.get('allocations.disk', 2000000000), 2)
|
|
||||||
|
|
||||||
def add_bytes(self, amount) -> int:
|
|
||||||
"""Record that we are now using more disk space,
|
|
||||||
unless doing so would exceed configured max"""
|
|
||||||
new_amount = amount + self.amount
|
|
||||||
ret_data = new_amount
|
|
||||||
if new_amount > config.get('allocations.disk', 2000000000):
|
|
||||||
ret_data = 0
|
|
||||||
else:
|
|
||||||
self._update(new_amount)
|
|
||||||
return ret_data
|
|
||||||
|
|
||||||
def remove_bytes(self, amount) -> int:
|
|
||||||
"""Record that we are now using less disk space"""
|
|
||||||
new_amount = self.amount - amount
|
|
||||||
self._update(new_amount)
|
|
||||||
return new_amount
|
|
@ -1,56 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
This file contains the command for banning blocks from the node
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import logger
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from onionrstorage import removeblock
|
|
||||||
from onionrstorage import deleteBlock
|
|
||||||
from onionrblocks import onionrblacklist
|
|
||||||
from utils import reconstructhash
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def ban_block():
|
|
||||||
"""Delete a block, permanently blacklisting it."""
|
|
||||||
blacklist = onionrblacklist.OnionrBlackList()
|
|
||||||
try:
|
|
||||||
ban = sys.argv[2]
|
|
||||||
except IndexError:
|
|
||||||
# Get the hash if its not provided as a CLI argument
|
|
||||||
ban = logger.readline('Enter a block hash:').strip()
|
|
||||||
# Make sure the hash has no truncated zeroes
|
|
||||||
ban = reconstructhash.reconstruct_hash(ban)
|
|
||||||
if stringvalidators.validate_hash(ban):
|
|
||||||
if not blacklist.inBlacklist(ban):
|
|
||||||
try:
|
|
||||||
blacklist.addToDB(ban)
|
|
||||||
removeblock.remove_block(ban)
|
|
||||||
deleteBlock(ban)
|
|
||||||
except Exception as error: # pylint: disable=W0703
|
|
||||||
logger.error('Could not blacklist block',
|
|
||||||
error=error, terminal=True)
|
|
||||||
else:
|
|
||||||
logger.info('Block blacklisted', terminal=True)
|
|
||||||
else:
|
|
||||||
logger.warn('That block is already blacklisted', terminal=True)
|
|
||||||
else:
|
|
||||||
logger.error('Invalid block hash', terminal=True)
|
|
||||||
|
|
||||||
|
|
||||||
ban_block.onionr_help = "<block hash>: " # type: ignore
|
|
||||||
ban_block.onionr_help += "deletes and blacklists a block" # type: ignore
|
|
@ -35,7 +35,6 @@ from utils.bettersleep import better_sleep
|
|||||||
from .killdaemon import kill_daemon # noqa
|
from .killdaemon import kill_daemon # noqa
|
||||||
from .showlogo import show_logo
|
from .showlogo import show_logo
|
||||||
|
|
||||||
from sneakernet import sneakernet_import_thread
|
|
||||||
from setupkvvars import setup_kv
|
from setupkvvars import setup_kv
|
||||||
"""
|
"""
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
@ -142,9 +141,6 @@ def daemon():
|
|||||||
events.event('init', threaded=False)
|
events.event('init', threaded=False)
|
||||||
events.event('daemon_start')
|
events.event('daemon_start')
|
||||||
|
|
||||||
if config.get('transports.sneakernet', True):
|
|
||||||
Thread(target=sneakernet_import_thread, daemon=True).start()
|
|
||||||
|
|
||||||
|
|
||||||
better_sleep(5)
|
better_sleep(5)
|
||||||
|
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
This file handles the command for exporting blocks to disk
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import logger
|
|
||||||
import onionrstorage
|
|
||||||
from utils import createdirs
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT
|
|
||||||
import filepaths
|
|
||||||
|
|
||||||
import os
|
|
||||||
from coredb import blockmetadb
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _do_export(b_hash):
|
|
||||||
createdirs.create_dirs()
|
|
||||||
data = onionrstorage.getData(b_hash)
|
|
||||||
with open('%s/%s%s' % (filepaths.export_location,
|
|
||||||
b_hash, BLOCK_EXPORT_FILE_EXT), 'wb') as export:
|
|
||||||
export.write(data)
|
|
||||||
logger.info('Block exported as file', terminal=True)
|
|
||||||
|
|
||||||
|
|
||||||
def export_block(*args):
|
|
||||||
"""Export block based on hash from stdin or argv."""
|
|
||||||
if args:
|
|
||||||
b_hash = args[0]
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
if not stringvalidators.validate_hash(sys.argv[2]):
|
|
||||||
raise ValueError
|
|
||||||
except (IndexError, ValueError):
|
|
||||||
logger.error('No valid block hash specified.', terminal=True)
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
b_hash = sys.argv[2]
|
|
||||||
_do_export(b_hash)
|
|
||||||
|
|
||||||
|
|
||||||
export_block.onionr_help = "<block hash>: Export block to " # type: ignore
|
|
||||||
export_block.onionr_help += "a file. Export directory is in " # type: ignore
|
|
||||||
export_block.onionr_help += "Onionr home under block-export" # type: ignore
|
|
@ -1,106 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
This file handles the commands for adding
|
|
||||||
and getting files from the Onionr network
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import logger
|
|
||||||
from onionrblocks.onionrblockapi import Block
|
|
||||||
import onionrexceptions
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from etc import onionrvalues
|
|
||||||
from onionrblocks import insert
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_ORIG_DIR = onionrvalues.ORIG_RUN_DIR_ENV_VAR
|
|
||||||
|
|
||||||
|
|
||||||
def _get_dir(path: str) -> str:
|
|
||||||
if not os.getenv(_ORIG_DIR) is None:
|
|
||||||
return os.getenv(_ORIG_DIR) + '/' + path # type: ignore
|
|
||||||
else:
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def add_html(singleBlock=True, blockType='html'):
|
|
||||||
"""Create one-off web page from HTML file, no ext resources."""
|
|
||||||
add_file(blockType=blockType)
|
|
||||||
|
|
||||||
|
|
||||||
add_html.onionr_help = "Adds an HTML file into Onionr. Does " # type: ignore
|
|
||||||
add_html.onionr_help += "not include dependant resources" # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def add_file(blockType='bin'):
|
|
||||||
"""Add a file to the onionr network."""
|
|
||||||
if len(sys.argv) >= 3:
|
|
||||||
filename = sys.argv[2]
|
|
||||||
|
|
||||||
if not os.path.exists(_get_dir(filename)):
|
|
||||||
logger.error(
|
|
||||||
'That file does not exist. Improper path (specify full path)?',
|
|
||||||
terminal=True)
|
|
||||||
return
|
|
||||||
logger.info('Adding file, this might take a long time.',
|
|
||||||
terminal=True)
|
|
||||||
try:
|
|
||||||
with open(_get_dir(filename), 'rb') as single_file:
|
|
||||||
blockhash = insert(single_file.read(), header=blockType)
|
|
||||||
if len(blockhash) > 0:
|
|
||||||
logger.info('File %s saved in block %s' %
|
|
||||||
(filename, blockhash), terminal=True)
|
|
||||||
except Exception as err: # pylint: disable=W0703
|
|
||||||
logger.error('Failed to save file in block ' +
|
|
||||||
str(err), timestamp=False, terminal=True)
|
|
||||||
else:
|
|
||||||
logger.error('%s add-file <filename>' %
|
|
||||||
sys.argv[0], timestamp=False, terminal=True)
|
|
||||||
|
|
||||||
|
|
||||||
add_file.onionr_help = "<file path> Add a file into " # type: ignore
|
|
||||||
add_file.onionr_help += "the Onionr network" # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def get_file():
|
|
||||||
"""Get a file from onionr blocks."""
|
|
||||||
try:
|
|
||||||
file_name = _get_dir(sys.argv[2])
|
|
||||||
bHash = sys.argv[3]
|
|
||||||
except IndexError:
|
|
||||||
logger.error("Syntax %s %s" % (
|
|
||||||
sys.argv[0], '/path/to/filename <blockhash>'), terminal=True)
|
|
||||||
else:
|
|
||||||
logger.info(file_name, terminal=True)
|
|
||||||
|
|
||||||
if os.path.exists(file_name):
|
|
||||||
logger.error("File already exists", terminal=True)
|
|
||||||
return
|
|
||||||
if not stringvalidators.validate_hash(bHash):
|
|
||||||
logger.error('Block hash is invalid', terminal=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file_name, 'wb') as my_file:
|
|
||||||
my_file.write(Block(bHash).bcontent)
|
|
||||||
except onionrexceptions.NoDataAvailable:
|
|
||||||
logger.error(
|
|
||||||
'That block is not available. Trying again later may work.',
|
|
||||||
terminal=True)
|
|
||||||
|
|
||||||
|
|
||||||
get_file.onionr_help = "<file path> <block hash>: Download " # type: ignore
|
|
||||||
get_file.onionr_help += "a file from the onionr network." # type: ignore
|
|
@ -1,37 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Dumb listing of Onionr sites
|
|
||||||
"""
|
|
||||||
from coredb.blockmetadb import get_blocks_by_type
|
|
||||||
from onionrblocks.onionrblockapi import Block
|
|
||||||
import logger
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def print_site_list():
|
|
||||||
"""Create a new MOTD message for the Onionr network."""
|
|
||||||
block_list = get_blocks_by_type('osite')
|
|
||||||
if not block_list:
|
|
||||||
logger.info('No sites saved right now', terminal=True)
|
|
||||||
for block in block_list:
|
|
||||||
block = Block(block)
|
|
||||||
if block.isSigned():
|
|
||||||
logger.info(block.signer.replace('=', ''), terminal=True)
|
|
||||||
else:
|
|
||||||
logger.info(block.hash, terminal=True)
|
|
||||||
|
|
||||||
|
|
||||||
print_site_list.onionr_help = "Dumbly list all Onionr sites currently saved" # type: ignore
|
|
@ -4,10 +4,9 @@ This module defines commands to show stats/details about the local node
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import logger
|
import logger
|
||||||
from onionrblocks import onionrblacklist
|
|
||||||
from onionrutils import mnemonickeys
|
from onionrutils import mnemonickeys
|
||||||
from utils import sizeutils, getconsolewidth, identifyhome
|
from utils import sizeutils, getconsolewidth, identifyhome
|
||||||
from coredb import blockmetadb, keydb
|
from coredb import keydb
|
||||||
import onionrcrypto
|
import onionrcrypto
|
||||||
import config
|
import config
|
||||||
from etc import onionrvalues
|
from etc import onionrvalues
|
||||||
@ -43,9 +42,8 @@ def show_stats():
|
|||||||
"""Print/log statistic info about our Onionr install."""
|
"""Print/log statistic info about our Onionr install."""
|
||||||
try:
|
try:
|
||||||
# define stats messages here
|
# define stats messages here
|
||||||
totalBlocks = len(blockmetadb.get_block_list())
|
|
||||||
home = identifyhome.identify_home()
|
home = identifyhome.identify_home()
|
||||||
totalBanned = len(onionrblacklist.OnionrBlackList().getList())
|
|
||||||
|
|
||||||
messages = {
|
messages = {
|
||||||
# info about local client
|
# info about local client
|
||||||
@ -69,9 +67,7 @@ def show_stats():
|
|||||||
'div2': True,
|
'div2': True,
|
||||||
'Enabled Plugins':
|
'Enabled Plugins':
|
||||||
str(len(config.get('plugins.enabled', list()))) + ' / ' +
|
str(len(config.get('plugins.enabled', list()))) + ' / ' +
|
||||||
str(len(os.listdir(home + 'plugins/'))),
|
str(len(os.listdir(home + 'plugins/')))
|
||||||
'Stored Blocks': str(totalBlocks),
|
|
||||||
'Deleted Blocks': str(totalBanned)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# color configuration
|
# color configuration
|
||||||
|
@ -6,19 +6,14 @@ from typing import Callable
|
|||||||
|
|
||||||
from .. import onionrstatistics, version, daemonlaunch
|
from .. import onionrstatistics, version, daemonlaunch
|
||||||
from .. import openwebinterface
|
from .. import openwebinterface
|
||||||
from .. import banblocks # Command to blacklist a block by its hash
|
|
||||||
from .. import filecommands # commands to share files with onionr
|
|
||||||
from .. import exportblocks # commands to export blocks
|
|
||||||
from .. import pubkeymanager # commands to add or change id
|
from .. import pubkeymanager # commands to add or change id
|
||||||
from .. import resetplugins # command to reinstall default plugins
|
from .. import resetplugins # command to reinstall default plugins
|
||||||
from .. import softreset # command to delete onionr blocks
|
from .. import softreset # command to delete onionr blocks
|
||||||
from .. import restartonionr # command to restart Onionr
|
from .. import restartonionr # command to restart Onionr
|
||||||
from .. import runtimetestcmd # cmd to execute the runtime integration tests
|
from .. import runtimetestcmd # cmd to execute the runtime integration tests
|
||||||
from .. import sitecreator # cmd to create multi-page sites
|
|
||||||
from ..listsites import print_site_list # cmd to list list ids
|
|
||||||
|
|
||||||
import onionrexceptions
|
import onionrexceptions
|
||||||
from onionrutils import importnewblocks # func to import new blocks
|
|
||||||
"""
|
"""
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -42,8 +37,6 @@ def get_arguments() -> dict:
|
|||||||
dynamically modify them with plugins
|
dynamically modify them with plugins
|
||||||
"""
|
"""
|
||||||
args = {
|
args = {
|
||||||
('blacklist', 'blacklist-block', 'remove-block',
|
|
||||||
'removeblock', 'banblock', 'ban-block'): banblocks.ban_block,
|
|
||||||
('details', 'info'): onionrstatistics.show_details,
|
('details', 'info'): onionrstatistics.show_details,
|
||||||
('stats', 'statistics'): onionrstatistics.show_stats,
|
('stats', 'statistics'): onionrstatistics.show_stats,
|
||||||
('version',): version.version,
|
('version',): version.version,
|
||||||
@ -53,15 +46,6 @@ def get_arguments() -> dict:
|
|||||||
('openhome', 'gui', 'openweb',
|
('openhome', 'gui', 'openweb',
|
||||||
'open-home', 'open-web'): openwebinterface.open_home,
|
'open-home', 'open-web'): openwebinterface.open_home,
|
||||||
('get-url', 'url', 'get-web'): openwebinterface.get_url,
|
('get-url', 'url', 'get-web'): openwebinterface.get_url,
|
||||||
('addhtml', 'add-html'): filecommands.add_html,
|
|
||||||
('addsite', 'add-site',
|
|
||||||
'update-site', 'updatesite'): sitecreator.create_multipage_site,
|
|
||||||
('listsites', 'list-sites'): print_site_list,
|
|
||||||
('addfile', 'add-file'): filecommands.add_file,
|
|
||||||
('get-file', 'getfile'): filecommands.get_file,
|
|
||||||
('export-block', 'exportblock'): exportblocks.export_block,
|
|
||||||
('importblocks',
|
|
||||||
'import-blocks', 'import-block'): importnewblocks.import_new_blocks,
|
|
||||||
('addid', 'add-id'): pubkeymanager.add_ID,
|
('addid', 'add-id'): pubkeymanager.add_ID,
|
||||||
('changeid', 'change-id'): pubkeymanager.change_ID,
|
('changeid', 'change-id'): pubkeymanager.change_ID,
|
||||||
('add-vanity', 'addvanity'): pubkeymanager.add_vanity,
|
('add-vanity', 'addvanity'): pubkeymanager.add_vanity,
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Command to create Onionr mutli-page sites
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import getpass
|
|
||||||
|
|
||||||
from niceware import generate_passphrase
|
|
||||||
|
|
||||||
from httpapi import onionrsitesapi
|
|
||||||
import logger
|
|
||||||
from etc import onionrvalues
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def create_multipage_site():
|
|
||||||
"""Command to create mutlipage sites with specified dir and password."""
|
|
||||||
error_encountered = False
|
|
||||||
orig_dir = os.getcwd()
|
|
||||||
try:
|
|
||||||
directory = sys.argv[2]
|
|
||||||
os.chdir(directory)
|
|
||||||
directory = '.'
|
|
||||||
except IndexError:
|
|
||||||
directory = '.'
|
|
||||||
try:
|
|
||||||
passphrase = sys.argv[3]
|
|
||||||
except IndexError:
|
|
||||||
logger.warn('''It is critical that this passphrase is long.
|
|
||||||
If you want to update your site later you must remember the passphrase.''',
|
|
||||||
terminal=True)
|
|
||||||
|
|
||||||
passphrase = "-".join(generate_passphrase(32))
|
|
||||||
print("Site restore phrase:", passphrase)
|
|
||||||
|
|
||||||
if len(passphrase) < onionrvalues.PASSWORD_LENGTH:
|
|
||||||
error_encountered = True
|
|
||||||
logger.error(
|
|
||||||
f'Passphrase must be at least {onionrvalues.PASSWORD_LENGTH}' +
|
|
||||||
' characters.', terminal=True)
|
|
||||||
|
|
||||||
if error_encountered:
|
|
||||||
sys.exit(1)
|
|
||||||
logger.info('Generating site...', terminal=True)
|
|
||||||
results = onionrsitesapi.sitefiles.create_site(
|
|
||||||
passphrase, directory=directory)
|
|
||||||
results = (results[0].replace('=', ''), results[1])
|
|
||||||
logger.info(f'Site address {results[0]}', terminal=True)
|
|
||||||
logger.info(f'Block for this version {results[1]}', terminal=True)
|
|
||||||
os.chdir(orig_dir)
|
|
||||||
|
|
||||||
|
|
||||||
create_multipage_site.onionr_help = "[directory path " # type: ignore
|
|
||||||
create_multipage_site.onionr_help += "(default relative)] " # type: ignore
|
|
||||||
create_multipage_site.onionr_help += "- packages a whole " # type: ignore
|
|
||||||
create_multipage_site.onionr_help += "directory and makes " # type: ignore
|
|
||||||
create_multipage_site.onionr_help += "it available as " # type: ignore
|
|
||||||
create_multipage_site.onionr_help += "an Onionr site." # type: ignore
|
|
@ -1,7 +1,6 @@
|
|||||||
from . import safecompare, replayvalidation, verifypow
|
from . import safecompare, replayvalidation
|
||||||
from . import getpubfrompriv
|
from . import getpubfrompriv
|
||||||
|
|
||||||
replay_validator = replayvalidation.replay_timestamp_validation
|
replay_validator = replayvalidation.replay_timestamp_validation
|
||||||
safe_compare = safecompare.safe_compare
|
safe_compare = safecompare.safe_compare
|
||||||
verify_POW = verifypow.verify_POW
|
|
||||||
get_pub_key_from_priv = getpubfrompriv.get_pub_key_from_priv
|
get_pub_key_from_priv = getpubfrompriv.get_pub_key_from_priv
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Proof of work module
|
|
||||||
"""
|
|
||||||
import multiprocessing, time, math, threading, binascii, sys, json
|
|
||||||
import nacl.encoding, nacl.hash, nacl.utils
|
|
||||||
|
|
||||||
import config
|
|
||||||
import logger
|
|
||||||
from onionrblocks import onionrblockapi
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
from onionrcrypto import hashers
|
|
||||||
|
|
||||||
from .blocknoncestart import BLOCK_NONCE_START_INT
|
|
||||||
from .vdf import create_vdf
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
config.reload()
|
|
||||||
|
|
||||||
|
|
||||||
def getDifficultyForNewBlock(data):
|
|
||||||
"""
|
|
||||||
Get difficulty for block. Accepts size in integer, Block instance, or str/bytes full block contents
|
|
||||||
"""
|
|
||||||
if isinstance(data, onionrblockapi.Block):
|
|
||||||
dataSizeInBytes = len(bytesconverter.str_to_bytes(data.getRaw()))
|
|
||||||
else:
|
|
||||||
dataSizeInBytes = len(bytesconverter.str_to_bytes(data))
|
|
||||||
|
|
||||||
minDifficulty = config.get('general.minimum_send_pow', 4)
|
|
||||||
totalDifficulty = max(minDifficulty, math.floor(dataSizeInBytes / 1000000.0))
|
|
||||||
|
|
||||||
return totalDifficulty
|
|
||||||
|
|
||||||
|
|
||||||
def getHashDifficulty(h: str):
|
|
||||||
"""
|
|
||||||
Return the amount of leading zeroes in a hex hash string (hexHash)
|
|
||||||
"""
|
|
||||||
return len(h) - len(h.lstrip('0'))
|
|
||||||
|
|
||||||
|
|
||||||
def hashMeetsDifficulty(hexHash):
|
|
||||||
"""
|
|
||||||
Return bool for a hash string to see if it meets pow difficulty defined in config
|
|
||||||
"""
|
|
||||||
hashDifficulty = getHashDifficulty(hexHash)
|
|
||||||
|
|
||||||
try:
|
|
||||||
expected = int(config.get('general.minimum_block_pow'))
|
|
||||||
except TypeError:
|
|
||||||
raise ValueError('Missing general.minimum_block_pow config')
|
|
||||||
|
|
||||||
return hashDifficulty >= expected
|
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
BLOCK_NONCE_START_INT = -10000000
|
|
@ -1,149 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Multiprocess proof of work
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from multiprocessing import Pipe, Process
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import secrets
|
|
||||||
|
|
||||||
import onionrproofs
|
|
||||||
|
|
||||||
import ujson as json
|
|
||||||
|
|
||||||
import logger
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
from onionrcrypto.hashers import sha3_hash
|
|
||||||
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SubprocessPOW:
|
|
||||||
def __init__(self, data, metadata, subproc_count=None):
|
|
||||||
"""
|
|
||||||
Onionr proof of work using multiple processes
|
|
||||||
Accepts block data, block metadata
|
|
||||||
if subproc_count is not set,
|
|
||||||
os.cpu_count() is used to determine the number of processes
|
|
||||||
|
|
||||||
Due to Python GIL multiprocessing/use of external libraries
|
|
||||||
is necessary to accelerate CPU bound tasks
|
|
||||||
"""
|
|
||||||
# No known benefit to using more processes than there are cores.
|
|
||||||
# Note: os.cpu_count perhaps not always accurate
|
|
||||||
if subproc_count is None:
|
|
||||||
subproc_count = os.cpu_count()
|
|
||||||
self.subproc_count = subproc_count
|
|
||||||
self.result = ''
|
|
||||||
self.shutdown = False
|
|
||||||
self.data = data
|
|
||||||
self.metadata = metadata
|
|
||||||
|
|
||||||
"""dump dict to measure bytes of json metadata
|
|
||||||
Cannot reuse later bc the pow token must be added
|
|
||||||
"""
|
|
||||||
json_metadata = json.dumps(metadata).encode()
|
|
||||||
|
|
||||||
self.data = bytesconverter.str_to_bytes(data)
|
|
||||||
|
|
||||||
compiled_data = bytes(json_metadata + b'\n' + self.data)
|
|
||||||
|
|
||||||
# Calculate difficulty. May use better algorithm in the future.
|
|
||||||
self.difficulty = onionrproofs.getDifficultyForNewBlock(compiled_data)
|
|
||||||
|
|
||||||
logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))
|
|
||||||
|
|
||||||
self.main_hash = '0' * 64
|
|
||||||
self.puzzle = self.main_hash[0:min(self.difficulty,
|
|
||||||
len(self.main_hash))]
|
|
||||||
self.shutdown = False
|
|
||||||
self.payload = None
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""spawn the multiproc handler threads"""
|
|
||||||
# Create a new thread for each subprocess
|
|
||||||
for _ in range(self.subproc_count): # noqa
|
|
||||||
threading.Thread(target=self._spawn_proc, daemon=True).start()
|
|
||||||
# Monitor the processes for a payload, shut them down when its found
|
|
||||||
while True:
|
|
||||||
if self.payload is None:
|
|
||||||
time.sleep(0.1)
|
|
||||||
else:
|
|
||||||
self.shutdown = True
|
|
||||||
return self.payload
|
|
||||||
|
|
||||||
def _spawn_proc(self):
|
|
||||||
"""Create a child proof of work process
|
|
||||||
wait for data and send shutdown signal when its found"""
|
|
||||||
# The importerror started happening in 3.9.x
|
|
||||||
# not worth fixing because this POW will be replaced by VDF
|
|
||||||
try:
|
|
||||||
parent_conn, child_conn = Pipe()
|
|
||||||
p = Process(target=self.do_pow, args=(child_conn,), daemon=True)
|
|
||||||
p.start()
|
|
||||||
except ImportError:
|
|
||||||
logger.error(
|
|
||||||
"Error in subprocess module when getting new POW " +
|
|
||||||
"pipe.\nThis is related to a problem in 3.9.x", terminal=True)
|
|
||||||
return
|
|
||||||
payload = None
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
data = parent_conn.recv()
|
|
||||||
if len(data) >= 1:
|
|
||||||
payload = data
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
p.terminate()
|
|
||||||
self.payload = payload
|
|
||||||
|
|
||||||
def do_pow(self, pipe):
|
|
||||||
"""find partial hash colision generating nonce for a block"""
|
|
||||||
nonce = 0
|
|
||||||
data = self.data
|
|
||||||
metadata = self.metadata
|
|
||||||
metadata['n'] = secrets.randbits(16)
|
|
||||||
puzzle = self.puzzle
|
|
||||||
difficulty = self.difficulty
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
#logger.info('still running', terminal=True)
|
|
||||||
# Break if shutdown received
|
|
||||||
try:
|
|
||||||
if pipe.poll() and pipe.recv() == 'shutdown':
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
# Load nonce into block metadata
|
|
||||||
metadata['c'] = nonce
|
|
||||||
# Serialize metadata, combine with block data
|
|
||||||
payload = json.dumps(metadata).encode() + b'\n' + data
|
|
||||||
# Check sha3_256 hash of block, compare to puzzle
|
|
||||||
# Send payload if puzzle finished
|
|
||||||
token = sha3_hash(payload)
|
|
||||||
# ensure token is string
|
|
||||||
token = bytesconverter.bytes_to_str(token)
|
|
||||||
if puzzle == token[0:difficulty]:
|
|
||||||
pipe.send(payload)
|
|
||||||
break
|
|
||||||
nonce += 1
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
@ -1,43 +0,0 @@
|
|||||||
import multiprocessing
|
|
||||||
|
|
||||||
import mimcvdf
|
|
||||||
|
|
||||||
|
|
||||||
def _wrap_vdf_create(queue, block_data_bytes, rounds):
|
|
||||||
queue.put(mimcvdf.vdf_create(block_data_bytes, rounds))
|
|
||||||
|
|
||||||
|
|
||||||
def _wrap_vdf_verify(queue, block_data_bytes, block_hash_hex, rounds):
|
|
||||||
queue.put(mimcvdf.vdf_verify(block_data_bytes, block_hash_hex, rounds))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def rounds_for_bytes(byte_count: int):
|
|
||||||
return byte_count * 1000
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_vdf(block_data_bytes):
|
|
||||||
rounds = rounds_for_bytes(block_data_bytes)
|
|
||||||
queue = multiprocessing.Queue()
|
|
||||||
vdf_proc = multiprocessing.Process(
|
|
||||||
target=_wrap_vdf_create,
|
|
||||||
args=(queue, block_data_bytes, rounds))
|
|
||||||
vdf_proc.start()
|
|
||||||
vdf_proc.join()
|
|
||||||
return queue.get()
|
|
||||||
|
|
||||||
|
|
||||||
def verify_vdf(block_hash_hex, block_data_bytes):
|
|
||||||
rounds = rounds_for_bytes(block_data_bytes)
|
|
||||||
if rounds < 10 ** 6:
|
|
||||||
# >million rounds it starts to take long enough to warrant a subprocess
|
|
||||||
queue = multiprocessing.Queue()
|
|
||||||
vdf_proc = multiprocessing.Process(
|
|
||||||
target=_wrap_vdf_verify,
|
|
||||||
args=(queue, block_data_bytes, block_hash_hex, rounds))
|
|
||||||
vdf_proc.start()
|
|
||||||
vdf_proc.join()
|
|
||||||
return queue.get()
|
|
||||||
return mimcvdf.vdf_verify(block_data_bytes, block_hash_hex, rounds)
|
|
||||||
|
|
@ -77,57 +77,6 @@ def createPeerDB():
|
|||||||
conn.close()
|
conn.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
def createBlockDB():
|
|
||||||
'''
|
|
||||||
Create a database for blocks
|
|
||||||
|
|
||||||
hash - the hash of a block
|
|
||||||
dateReceived - the date the block was recieved, not necessarily when it was created
|
|
||||||
decrypted - if we can successfully decrypt the block (does not describe its current state)
|
|
||||||
dataType - data type of the block
|
|
||||||
dataFound - if the data has been found for the block
|
|
||||||
dataSaved - if the data has been saved for the block
|
|
||||||
sig - optional signature by the author (not optional if author is specified)
|
|
||||||
author - multi-round partial sha3-256 hash of authors public key
|
|
||||||
dateClaimed - timestamp claimed inside the block, only as trustworthy as the block author is
|
|
||||||
expire int - block expire date in epoch
|
|
||||||
'''
|
|
||||||
if os.path.exists(dbfiles.block_meta_db):
|
|
||||||
raise FileExistsError("Block database already exists")
|
|
||||||
conn = sqlite3.connect(dbfiles.block_meta_db)
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''CREATE TABLE hashes(
|
|
||||||
hash text not null,
|
|
||||||
dateReceived int,
|
|
||||||
decrypted int,
|
|
||||||
dataType text,
|
|
||||||
dataFound int,
|
|
||||||
dataSaved int,
|
|
||||||
sig text,
|
|
||||||
author text,
|
|
||||||
dateClaimed int,
|
|
||||||
expire int
|
|
||||||
);
|
|
||||||
''')
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
def createBlockDataDB():
|
|
||||||
if os.path.exists(dbfiles.block_data_db):
|
|
||||||
raise FileExistsError("Block data database already exists")
|
|
||||||
else:
|
|
||||||
if not os.path.exists(filepaths.block_data_location):
|
|
||||||
os.mkdir(filepaths.block_data_location)
|
|
||||||
conn = sqlite3.connect(dbfiles.block_data_db)
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('''CREATE TABLE blockData(
|
|
||||||
hash text not null,
|
|
||||||
data blob not null
|
|
||||||
);
|
|
||||||
''')
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
def createForwardKeyDB():
|
def createForwardKeyDB():
|
||||||
'''
|
'''
|
||||||
@ -168,5 +117,4 @@ def create_blacklist_db():
|
|||||||
|
|
||||||
|
|
||||||
create_funcs = [createAddressDB, createPeerDB,
|
create_funcs = [createAddressDB, createPeerDB,
|
||||||
createBlockDB, createBlockDataDB,
|
|
||||||
createForwardKeyDB, create_blacklist_db]
|
createForwardKeyDB, create_blacklist_db]
|
@ -1,116 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Handle block storage, providing an abstraction for
|
|
||||||
storing blocks between file system and database
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from coredb import dbfiles
|
|
||||||
from filepaths import block_data_location
|
|
||||||
import onionrexceptions
|
|
||||||
from onionrcrypto import hashers
|
|
||||||
from . import setdata, removeblock
|
|
||||||
from etc.onionrvalues import DATABASE_LOCK_TIMEOUT, BLOCK_EXPORT_FILE_EXT
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
DB_ENTRY_SIZE_LIMIT = 10000 # Will be a config option
|
|
||||||
|
|
||||||
set_data = setdata.set_data
|
|
||||||
|
|
||||||
|
|
||||||
def _dbInsert(block_hash, data):
|
|
||||||
conn = sqlite3.connect(dbfiles.block_data_db,
|
|
||||||
timeout=DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
data = (block_hash, data)
|
|
||||||
c.execute('INSERT INTO blockData (hash, data) VALUES(?, ?);', data)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def _dbFetch(block_hash):
|
|
||||||
conn = sqlite3.connect(dbfiles.block_data_db,
|
|
||||||
timeout=DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
for i in c.execute(
|
|
||||||
'SELECT data from blockData where hash = ?', (block_hash,)):
|
|
||||||
return i[0]
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def deleteBlock(block_hash):
|
|
||||||
# Call removeblock.remove_block to automatically want to remove storage byte count
|
|
||||||
if os.path.exists(f'{block_data_location}/{block_hash}{BLOCK_EXPORT_FILE_EXT}'):
|
|
||||||
os.remove(f'{block_data_location}/{block_hash}{BLOCK_EXPORT_FILE_EXT}')
|
|
||||||
return True
|
|
||||||
conn = sqlite3.connect(dbfiles.block_data_db,
|
|
||||||
timeout=DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
data = (block_hash,)
|
|
||||||
c.execute('DELETE FROM blockData where hash = ?', data)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def store(data, block_hash=''):
|
|
||||||
if not stringvalidators.validate_hash(block_hash):
|
|
||||||
raise ValueError
|
|
||||||
ourHash = hashers.sha3_hash(data)
|
|
||||||
if block_hash != '':
|
|
||||||
if not ourHash == block_hash:
|
|
||||||
raise ValueError('Hash specified does not meet internal hash check')
|
|
||||||
else:
|
|
||||||
block_hash = ourHash
|
|
||||||
|
|
||||||
if DB_ENTRY_SIZE_LIMIT >= sys.getsizeof(data):
|
|
||||||
_dbInsert(block_hash, data)
|
|
||||||
else:
|
|
||||||
with open(
|
|
||||||
f'{block_data_location}/{block_hash}{BLOCK_EXPORT_FILE_EXT}', 'wb') as blck_file:
|
|
||||||
blck_file.write(data)
|
|
||||||
|
|
||||||
|
|
||||||
def getData(bHash):
|
|
||||||
|
|
||||||
if not stringvalidators.validate_hash(bHash):
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
bHash = bytesconverter.bytes_to_str(bHash)
|
|
||||||
bHash = bHash.strip()
|
|
||||||
# First check DB for data entry by hash
|
|
||||||
# if no entry, check disk
|
|
||||||
# If no entry in either, raise an exception
|
|
||||||
ret_data = None
|
|
||||||
fileLocation = '%s/%s%s' % (
|
|
||||||
block_data_location,
|
|
||||||
bHash, BLOCK_EXPORT_FILE_EXT)
|
|
||||||
not_found_msg = "Block data not found for: " + str(bHash)
|
|
||||||
if os.path.exists(fileLocation):
|
|
||||||
with open(fileLocation, 'rb') as block:
|
|
||||||
ret_data = block.read()
|
|
||||||
else:
|
|
||||||
ret_data = _dbFetch(bHash)
|
|
||||||
|
|
||||||
if ret_data is None:
|
|
||||||
raise onionrexceptions.NoDataAvailable(not_found_msg)
|
|
||||||
return ret_data
|
|
@ -1,52 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
remove onionr block from meta db
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
import onionrexceptions
|
|
||||||
import onionrstorage
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from coredb import dbfiles
|
|
||||||
from onionrblocks import storagecounter
|
|
||||||
from etc.onionrvalues import DATABASE_LOCK_TIMEOUT
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
storage_counter = storagecounter.StorageCounter()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_block(block):
|
|
||||||
"""Remove a block from this node.
|
|
||||||
|
|
||||||
(does not automatically blacklist).
|
|
||||||
**You may want blacklist.addToDB(blockHash)
|
|
||||||
"""
|
|
||||||
if stringvalidators.validate_hash(block):
|
|
||||||
try:
|
|
||||||
data_size = sys.getsizeof(onionrstorage.getData(block))
|
|
||||||
except onionrexceptions.NoDataAvailable:
|
|
||||||
data_size = 0
|
|
||||||
conn = sqlite3.connect(
|
|
||||||
dbfiles.block_meta_db, timeout=DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
t = (block,)
|
|
||||||
c.execute('Delete from hashes where hash=?;', t)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
if data_size:
|
|
||||||
storage_counter.remove_bytes(data_size)
|
|
||||||
else:
|
|
||||||
raise onionrexceptions.InvalidHexHash
|
|
@ -1,70 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Test Onionr as it is running
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
import onionrstorage
|
|
||||||
import onionrexceptions
|
|
||||||
import onionrcrypto as crypto
|
|
||||||
import filepaths
|
|
||||||
from onionrblocks import storagecounter, blockmetadata
|
|
||||||
from coredb import dbfiles
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
from etc.onionrvalues import DATABASE_LOCK_TIMEOUT
|
|
||||||
from onionrtypes import BlockHash
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
storage_counter = storagecounter.StorageCounter()
|
|
||||||
|
|
||||||
|
|
||||||
def set_data(data):
|
|
||||||
"""Set the data assciated with a hash."""
|
|
||||||
dataSize = sys.getsizeof(data)
|
|
||||||
nonce_hash = crypto.hashers.sha3_hash(
|
|
||||||
bytesconverter.str_to_bytes(
|
|
||||||
blockmetadata.fromdata.get_block_metadata_from_data(data)[2]))
|
|
||||||
nonce_hash = bytesconverter.bytes_to_str(nonce_hash)
|
|
||||||
|
|
||||||
if not type(data) is bytes:
|
|
||||||
data = data.encode()
|
|
||||||
|
|
||||||
dataHash = crypto.hashers.sha3_hash(data)
|
|
||||||
|
|
||||||
if type(dataHash) is bytes:
|
|
||||||
dataHash = dataHash.decode()
|
|
||||||
try:
|
|
||||||
onionrstorage.getData(dataHash)
|
|
||||||
except onionrexceptions.NoDataAvailable:
|
|
||||||
if storage_counter.add_bytes(dataSize):
|
|
||||||
onionrstorage.store(data, block_hash=dataHash)
|
|
||||||
conn = sqlite3.connect(
|
|
||||||
dbfiles.block_meta_db, timeout=DATABASE_LOCK_TIMEOUT)
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute(
|
|
||||||
"UPDATE hashes SET dataSaved=1 WHERE hash = ?;",
|
|
||||||
(dataHash,))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
with open(filepaths.data_nonce_file, 'a') as nonceFile:
|
|
||||||
nonceFile.write(nonce_hash + '\n')
|
|
||||||
else:
|
|
||||||
raise onionrexceptions.DiskAllocationReached
|
|
||||||
else:
|
|
||||||
raise onionrexceptions.DataExists(
|
|
||||||
"Data is already set for " + dataHash)
|
|
||||||
|
|
||||||
return dataHash
|
|
@ -1,60 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
import new blocks from disk, providing transport agnosticism
|
|
||||||
"""
|
|
||||||
import glob
|
|
||||||
|
|
||||||
import logger
|
|
||||||
from onionrblocks import blockmetadata
|
|
||||||
from coredb import blockmetadb
|
|
||||||
import filepaths
|
|
||||||
import onionrcrypto as crypto
|
|
||||||
from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def import_new_blocks(scanDir=''):
|
|
||||||
"""Scan for new blocks ON THE DISK and import them"""
|
|
||||||
blockList = blockmetadb.get_block_list()
|
|
||||||
exist = False
|
|
||||||
if scanDir == '':
|
|
||||||
scanDir = filepaths.block_data_location
|
|
||||||
if not scanDir.endswith('/'):
|
|
||||||
scanDir += '/'
|
|
||||||
for block in glob.glob(scanDir + "*%s" % (BLOCK_EXPORT_FILE_EXT,)):
|
|
||||||
if block.replace(scanDir, '').replace(BLOCK_EXPORT_FILE_EXT, '') \
|
|
||||||
not in blockList:
|
|
||||||
exist = True
|
|
||||||
logger.info('Found new block on dist %s' % block, terminal=True)
|
|
||||||
with open(block, 'rb') as newBlock:
|
|
||||||
block = block.replace(scanDir, '').replace(
|
|
||||||
BLOCK_EXPORT_FILE_EXT, '')
|
|
||||||
if crypto.hashers.sha3_hash(newBlock.read()) == block.replace(
|
|
||||||
BLOCK_EXPORT_FILE_EXT, ''):
|
|
||||||
blockmetadb.add_to_block_DB(block.replace(
|
|
||||||
BLOCK_EXPORT_FILE_EXT, ''), dataSaved=True)
|
|
||||||
logger.info('Imported block %s' % block, terminal=True)
|
|
||||||
blockmetadata.process_block_metadata(block)
|
|
||||||
else:
|
|
||||||
logger.warn('Failed to verify hash for %s' % block,
|
|
||||||
terminal=True)
|
|
||||||
if not exist:
|
|
||||||
logger.info('No blocks found to import', terminal=True)
|
|
||||||
|
|
||||||
|
|
||||||
import_new_blocks.onionr_help = \
|
|
||||||
f"Scan the Onionr data directory under {filepaths.block_data_location}" + \
|
|
||||||
"for new block files (.db not supported) to import"
|
|
@ -8,12 +8,9 @@ from secrets import SystemRandom
|
|||||||
import logger
|
import logger
|
||||||
from onionrutils import epoch
|
from onionrutils import epoch
|
||||||
|
|
||||||
from . import uicheck, inserttest, stresstest
|
from . import uicheck
|
||||||
from .webpasstest import webpass_test
|
from .webpasstest import webpass_test
|
||||||
from .osver import test_os_ver_endpoint
|
from .osver import test_os_ver_endpoint
|
||||||
from .clearnettor import test_clearnet_tor_request
|
|
||||||
from .housekeeping import test_inserted_housekeeping
|
|
||||||
from .sneakernettest import test_sneakernet_import
|
|
||||||
from .dnsrebindingtest import test_dns_rebinding
|
from .dnsrebindingtest import test_dns_rebinding
|
||||||
"""
|
"""
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
@ -31,14 +28,8 @@ from .dnsrebindingtest import test_dns_rebinding
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
RUN_TESTS = [uicheck.check_ui,
|
RUN_TESTS = [uicheck.check_ui,
|
||||||
inserttest.insert_bin_test,
|
|
||||||
stresstest.stress_test_block_insert,
|
|
||||||
webpass_test,
|
webpass_test,
|
||||||
test_os_ver_endpoint,
|
test_os_ver_endpoint,
|
||||||
test_clearnet_tor_request,
|
|
||||||
test_inserted_housekeeping,
|
|
||||||
sneakernettest.test_sneakernet_import,
|
|
||||||
test_dns_rebinding
|
|
||||||
]
|
]
|
||||||
|
|
||||||
SUCCESS_FILE = os.path.dirname(os.path.realpath(__file__)) + '/../../tests/runtime-result.txt'
|
SUCCESS_FILE = os.path.dirname(os.path.realpath(__file__)) + '/../../tests/runtime-result.txt'
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Ensure that clearnet cannot be reached
|
|
||||||
"""
|
|
||||||
from onionrutils.basicrequests import do_get_request
|
|
||||||
from onionrutils import localcommand
|
|
||||||
import logger
|
|
||||||
import config
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def test_clearnet_tor_request(testmanager):
|
|
||||||
"""Ensure that Tor cannot request clearnet address.
|
|
||||||
|
|
||||||
Does not run if Tor is being reused
|
|
||||||
"""
|
|
||||||
|
|
||||||
config.reload()
|
|
||||||
leak_result = ""
|
|
||||||
|
|
||||||
if config.get('tor.use_existing_tor', False):
|
|
||||||
logger.warn(
|
|
||||||
"Can't ensure Tor reqs to clearnet won't happen when reusing Tor")
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
socks_port = localcommand.local_command('/gettorsocks')
|
|
||||||
|
|
||||||
# Don't worry, this request isn't meant to go through,
|
|
||||||
# but if it did it would be through Tor
|
|
||||||
|
|
||||||
try:
|
|
||||||
leak_result: str = do_get_request(
|
|
||||||
'https://example.com/notvalidpage',
|
|
||||||
port=socks_port, ignoreAPI=True).lower()
|
|
||||||
except AttributeError:
|
|
||||||
leak_result = ""
|
|
||||||
except Exception as e:
|
|
||||||
logger.warn(str(e))
|
|
||||||
try:
|
|
||||||
if 'example' in leak_result:
|
|
||||||
logger.error('Tor was able to request a clearnet site')
|
|
||||||
raise ValueError('Tor was able to request a clearnet site')
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
@ -1,25 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from gevent import sleep
|
|
||||||
|
|
||||||
from onionrblocks import insert
|
|
||||||
import logger
|
|
||||||
from coredb.blockmetadb import get_block_list
|
|
||||||
from onionrutils import epoch
|
|
||||||
|
|
||||||
|
|
||||||
def test_inserted_housekeeping(testmanager):
|
|
||||||
"""Tests that inserted blocks are proprely deleted"""
|
|
||||||
bl = insert('testdata', expire=12)
|
|
||||||
wait_seconds = 132 # Wait two minutes plus expire time
|
|
||||||
count = 0
|
|
||||||
if bl in get_block_list():
|
|
||||||
while count < wait_seconds:
|
|
||||||
if bl in get_block_list():
|
|
||||||
sleep(0.8)
|
|
||||||
count += 1
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
raise ValueError('Inserted block with expiry not erased')
|
|
||||||
else:
|
|
||||||
raise ValueError('Inserted block in expiry test not present in list')
|
|
@ -1,20 +0,0 @@
|
|||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
import onionrblocks
|
|
||||||
import logger
|
|
||||||
import coredb
|
|
||||||
|
|
||||||
def _check_remote_node(testmanager):
|
|
||||||
return
|
|
||||||
|
|
||||||
def insert_bin_test(testmanager):
|
|
||||||
data = os.urandom(32)
|
|
||||||
b_hash = onionrblocks.insert(data)
|
|
||||||
time.sleep(0.3)
|
|
||||||
if b_hash not in testmanager._too_many.get_by_string("PublicAPI").hideBlocks:
|
|
||||||
raise ValueError("Block not hidden")
|
|
||||||
|
|
||||||
if b_hash not in coredb.blockmetadb.get_block_list():
|
|
||||||
logger.error(str(b_hash) + 'is not in bl')
|
|
||||||
raise ValueError
|
|
@ -1,27 +0,0 @@
|
|||||||
import os
|
|
||||||
from shutil import move
|
|
||||||
|
|
||||||
from onionrblocks import insert
|
|
||||||
from onionrstorage import deleteBlock
|
|
||||||
from onionrcommands.exportblocks import export_block
|
|
||||||
from filepaths import export_location, block_data_location, data_nonce_file
|
|
||||||
from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT
|
|
||||||
from onionrstorage.removeblock import remove_block
|
|
||||||
from onionrstorage import deleteBlock
|
|
||||||
from coredb.blockmetadb import get_block_list
|
|
||||||
from utils import bettersleep
|
|
||||||
from gevent import sleep
|
|
||||||
|
|
||||||
def test_sneakernet_import(test_manager):
|
|
||||||
in_db = lambda b: b in get_block_list()
|
|
||||||
bl = insert(os.urandom(10))
|
|
||||||
assert in_db(bl)
|
|
||||||
export_block(bl)
|
|
||||||
assert os.path.exists(export_location + bl + BLOCK_EXPORT_FILE_EXT)
|
|
||||||
remove_block(bl)
|
|
||||||
deleteBlock(bl)
|
|
||||||
assert not in_db(bl)
|
|
||||||
os.remove(data_nonce_file)
|
|
||||||
move(export_location + bl + BLOCK_EXPORT_FILE_EXT, block_data_location)
|
|
||||||
sleep(1)
|
|
||||||
assert in_db(bl)
|
|
@ -1,17 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
import onionrblocks
|
|
||||||
import logger
|
|
||||||
import coredb
|
|
||||||
from onionrutils import epoch
|
|
||||||
|
|
||||||
def stress_test_block_insert(testmanager):
|
|
||||||
return
|
|
||||||
start = epoch.get_epoch()
|
|
||||||
count = 100
|
|
||||||
max_insert_speed = 120
|
|
||||||
for x in range(count): onionrblocks.insert(os.urandom(32))
|
|
||||||
speed = epoch.get_epoch() - start
|
|
||||||
if speed < max_insert_speed:
|
|
||||||
raise ValueError(f'{count} blocks inserted too fast, {max_insert_speed}, got {speed}')
|
|
||||||
logger.info(f'runtest stress block insertion: {count} blocks inserted in {speed}s')
|
|
@ -1,68 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Detect new block files in a given directory
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
from watchdog.observers import Observer
|
|
||||||
from watchdog.events import FileSystemEventHandler
|
|
||||||
|
|
||||||
import config
|
|
||||||
from filepaths import block_data_location
|
|
||||||
from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT
|
|
||||||
from onionrblocks.blockimporter import import_block_from_data
|
|
||||||
import onionrexceptions
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
watch_paths = config.get('transports.sneakernet.paths', list([]))
|
|
||||||
if block_data_location not in watch_paths:
|
|
||||||
watch_paths.append(block_data_location)
|
|
||||||
|
|
||||||
|
|
||||||
class _Importer(FileSystemEventHandler):
|
|
||||||
@staticmethod
|
|
||||||
def on_created(event):
|
|
||||||
if not event.src_path.endswith(BLOCK_EXPORT_FILE_EXT):
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
with open(event.src_path, 'rb') as block_file:
|
|
||||||
block_data = block_file.read()
|
|
||||||
except FileNotFoundError:
|
|
||||||
return
|
|
||||||
os.remove(event.src_path)
|
|
||||||
try:
|
|
||||||
import_block_from_data(block_data)
|
|
||||||
except( # noqa
|
|
||||||
onionrexceptions.DataExists,
|
|
||||||
onionrexceptions.BlockMetaEntryExists,
|
|
||||||
onionrexceptions.InvalidMetadata) as _:
|
|
||||||
return
|
|
||||||
if block_data_location in event.src_path:
|
|
||||||
try:
|
|
||||||
os.remove(event.src_path)
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def sneakernet_import_thread():
|
|
||||||
"""Add block data dir & confed paths to fs observer to watch for new bls"""
|
|
||||||
observer = Observer()
|
|
||||||
for path in watch_paths:
|
|
||||||
observer.schedule(_Importer(), path, recursive=True)
|
|
||||||
observer.start()
|
|
||||||
while observer.is_alive():
|
|
||||||
# call import func with timeout
|
|
||||||
observer.join(60)
|
|
@ -1,123 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
This file primarily serves to allow specific fetching of circles board messages
|
|
||||||
"""
|
|
||||||
import operator
|
|
||||||
import os
|
|
||||||
|
|
||||||
import ujson as json
|
|
||||||
|
|
||||||
from flask import Response, Blueprint
|
|
||||||
from flask import send_from_directory
|
|
||||||
from deadsimplekv import DeadSimpleKV
|
|
||||||
|
|
||||||
from utils import identifyhome
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
flask_blueprint = Blueprint('circles', __name__)
|
|
||||||
|
|
||||||
root = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
|
|
||||||
|
|
||||||
with open(
|
|
||||||
os.path.dirname(
|
|
||||||
os.path.realpath(__file__)) + '/info.json', 'r') as info_file:
|
|
||||||
data = info_file.read().strip()
|
|
||||||
version = json.loads(data)['version']
|
|
||||||
|
|
||||||
BOARD_CACHE_FILE = identifyhome.identify_home() + '/board-index.cache.json'
|
|
||||||
|
|
||||||
read_only_cache = DeadSimpleKV(
|
|
||||||
BOARD_CACHE_FILE,
|
|
||||||
flush_on_exit=False,
|
|
||||||
refresh_seconds=30)
|
|
||||||
|
|
||||||
@flask_blueprint.route('/board/<path:path>', endpoint='circlesstatic')
|
|
||||||
def load_mail(path):
|
|
||||||
return send_from_directory(root + '/web/', path)
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/board/', endpoint='circlesindex')
|
|
||||||
def load_mail_index():
|
|
||||||
return send_from_directory(root + '/web/', 'index.html')
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/circles/getpostsbyboard/<board>')
|
|
||||||
def get_post_by_board(board):
|
|
||||||
board_cache = DeadSimpleKV(
|
|
||||||
BOARD_CACHE_FILE,
|
|
||||||
flush_on_exit=False)
|
|
||||||
board_cache.refresh()
|
|
||||||
posts = board_cache.get(board)
|
|
||||||
if posts is None:
|
|
||||||
posts = ''
|
|
||||||
else:
|
|
||||||
posts = ','.join(posts)
|
|
||||||
return Response(posts)
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/circles/getpostsbyboard/<board>/<offset>')
|
|
||||||
def get_post_by_board_with_offset(board, offset):
|
|
||||||
offset = int(offset)
|
|
||||||
OFFSET_COUNT = 10
|
|
||||||
board_cache = DeadSimpleKV(
|
|
||||||
BOARD_CACHE_FILE,
|
|
||||||
flush_on_exit=False)
|
|
||||||
board_cache.refresh()
|
|
||||||
posts = board_cache.get(board)
|
|
||||||
if posts is None:
|
|
||||||
posts = ''
|
|
||||||
else:
|
|
||||||
posts.reverse()
|
|
||||||
posts = ','.join(posts[offset:offset + OFFSET_COUNT])
|
|
||||||
return Response(posts)
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/circles/version')
|
|
||||||
def get_version():
|
|
||||||
return Response(version)
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/circles/removefromcache/<board>/<name>',
|
|
||||||
methods=['POST'])
|
|
||||||
def remove_from_cache(board, name):
|
|
||||||
board_cache = DeadSimpleKV(BOARD_CACHE_FILE,
|
|
||||||
flush_on_exit=False)
|
|
||||||
board_cache.refresh()
|
|
||||||
posts = board_cache.get(board)
|
|
||||||
try:
|
|
||||||
posts.remove(name)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
board_cache.put(board, posts)
|
|
||||||
return Response('success')
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/circles/getpopular/<count>')
|
|
||||||
def get_popular(count):
|
|
||||||
read_only_cache.refresh()
|
|
||||||
boards = json.loads(read_only_cache.get_raw_json())
|
|
||||||
for board in boards:
|
|
||||||
boards[board] = len(boards[board])
|
|
||||||
|
|
||||||
|
|
||||||
top_boards = sorted(boards.items(), key=operator.itemgetter(1), reverse=True)[:int(count)]
|
|
||||||
|
|
||||||
only_board_names = []
|
|
||||||
for b in top_boards:
|
|
||||||
only_board_names.append(b[0])
|
|
||||||
|
|
||||||
return Response(','.join(only_board_names), content_type='text/csv')
|
|
@ -1,4 +0,0 @@
|
|||||||
{ "name": "circles",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "onionr"
|
|
||||||
}
|
|
@ -1,176 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
This default plugin handles "flow" messages
|
|
||||||
(global chatroom style communication)
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import deadsimplekv as simplekv
|
|
||||||
from utils import identifyhome, reconstructhash
|
|
||||||
from coredb import blockmetadb
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import locale
|
|
||||||
from onionrblocks.onionrblockapi import Block
|
|
||||||
import logger
|
|
||||||
import onionrblocks
|
|
||||||
from onionrutils import escapeansi, epoch, bytesconverter
|
|
||||||
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
# import after path insert
|
|
||||||
import flowapi # noqa
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
flask_blueprint = flowapi.flask_blueprint
|
|
||||||
security_whitelist = ['circles.circlesstatic', 'circles.circlesindex']
|
|
||||||
|
|
||||||
plugin_name = 'circles'
|
|
||||||
PLUGIN_VERSION = '0.1.0'
|
|
||||||
|
|
||||||
EXPIRE_TIME = 43200
|
|
||||||
|
|
||||||
class OnionrFlow:
|
|
||||||
def __init__(self):
|
|
||||||
self.alreadyOutputed = []
|
|
||||||
self.flowRunning = False
|
|
||||||
self.channel = ""
|
|
||||||
return
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
logger.warn(
|
|
||||||
"Please note: everything said here is public, " +
|
|
||||||
"even if a random channel name is used.", terminal=True)
|
|
||||||
message = ""
|
|
||||||
self.flowRunning = True
|
|
||||||
try:
|
|
||||||
self.channel = logger.readline(
|
|
||||||
"Enter a channel name or none for default:").strip()
|
|
||||||
except (KeyboardInterrupt, EOFError):
|
|
||||||
self.flowRunning = False
|
|
||||||
newThread = threading.Thread(target=self.showOutput, daemon=True)
|
|
||||||
newThread.start()
|
|
||||||
while self.flowRunning:
|
|
||||||
if self.channel == "":
|
|
||||||
self.channel = "global"
|
|
||||||
try:
|
|
||||||
message = logger.readline(f'\nInsert message into {plugin_name}:').strip().replace(
|
|
||||||
'\n', '\\n').replace('\r', '\\r')
|
|
||||||
except EOFError:
|
|
||||||
pass
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self.flowRunning = False
|
|
||||||
else:
|
|
||||||
if message == "q":
|
|
||||||
self.flowRunning = False
|
|
||||||
expireTime = epoch.get_epoch() + EXPIRE_TIME
|
|
||||||
if len(message) > 0:
|
|
||||||
logger.info('Inserting message as block...', terminal=True)
|
|
||||||
onionrblocks.insert(message, header='brd',
|
|
||||||
expire=expireTime,
|
|
||||||
meta = {
|
|
||||||
'ch': self.channel})
|
|
||||||
|
|
||||||
logger.info(f"{plugin_name} is exiting, goodbye", terminal=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
def showOutput(self):
|
|
||||||
while isinstance(self.channel, type(None)) and self.flowRunning:
|
|
||||||
time.sleep(1)
|
|
||||||
try:
|
|
||||||
while self.flowRunning:
|
|
||||||
for block in blockmetadb.get_blocks_by_type('brd'):
|
|
||||||
if block in self.alreadyOutputed:
|
|
||||||
continue
|
|
||||||
block = Block(block)
|
|
||||||
b_hash = bytesconverter.bytes_to_str(block.getHash())
|
|
||||||
if block.getMetadata('ch') != self.channel:
|
|
||||||
continue
|
|
||||||
if not self.flowRunning:
|
|
||||||
break
|
|
||||||
logger.info('\n------------------------',
|
|
||||||
prompt=False, terminal=True)
|
|
||||||
content = block.getContent()
|
|
||||||
# Escape new lines, remove trailing whitespace, and escape ansi sequences
|
|
||||||
content = escapeansi.escape_ANSI(content.replace(
|
|
||||||
b'\n', b'\\n').replace(b'\r', b'\\r').strip().decode('utf-8'))
|
|
||||||
logger.info(block.getDate().strftime(
|
|
||||||
"%m/%d %H:%M") + ' - ' +
|
|
||||||
logger.colors.reset + content,
|
|
||||||
prompt=False, terminal=True)
|
|
||||||
self.alreadyOutputed.append(b_hash)
|
|
||||||
time.sleep(5)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self.flowRunning = False
|
|
||||||
|
|
||||||
|
|
||||||
def on_circles_cmd(api, data=None):
|
|
||||||
OnionrFlow().start()
|
|
||||||
|
|
||||||
|
|
||||||
def on_circlesend_cmd(api, data=None):
|
|
||||||
err_msg = "Second arg is board name, third is quoted message"
|
|
||||||
try:
|
|
||||||
sys.argv[2]
|
|
||||||
except IndexError:
|
|
||||||
logger.error(err_msg, terminal=True)
|
|
||||||
try:
|
|
||||||
sys.argv[3]
|
|
||||||
except IndexError:
|
|
||||||
logger.error(err_msg, terminal=True)
|
|
||||||
|
|
||||||
bl = onionrblocks.insert(sys.argv[3], header='brd',
|
|
||||||
expire=(EXPIRE_TIME + epoch.get_epoch()),
|
|
||||||
meta={'ch': sys.argv[2]})
|
|
||||||
print(bl)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_softreset(api, data=None):
|
|
||||||
try:
|
|
||||||
os.remove(identifyhome.identify_home() + '/board-index.cache.json')
|
|
||||||
logger.info('Cleared Circles board cache')
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def on_processblocks(api, data=None):
|
|
||||||
metadata = data['block'].bmetadata # Get the block metadata
|
|
||||||
if data['type'] != 'brd':
|
|
||||||
return
|
|
||||||
|
|
||||||
b_hash = reconstructhash.deconstruct_hash(
|
|
||||||
data['block'].hash) # Get the 0-truncated block hash
|
|
||||||
board_cache = simplekv.DeadSimpleKV(identifyhome.identify_home(
|
|
||||||
) + '/board-index.cache.json', flush_on_exit=False) # get the board index cache
|
|
||||||
board_cache.refresh()
|
|
||||||
# Validate the channel name is sane for caching
|
|
||||||
try:
|
|
||||||
ch = metadata['ch']
|
|
||||||
except KeyError:
|
|
||||||
ch = 'global'
|
|
||||||
ch_len = len(ch)
|
|
||||||
if ch_len == 0:
|
|
||||||
ch = 'global'
|
|
||||||
elif ch_len > 12:
|
|
||||||
return
|
|
||||||
|
|
||||||
existing_posts = board_cache.get(ch)
|
|
||||||
if existing_posts is None:
|
|
||||||
existing_posts = []
|
|
||||||
existing_posts.append(data['block'].hash)
|
|
||||||
board_cache.put(ch, existing_posts)
|
|
||||||
board_cache.flush()
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Auto refresh board posts
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
var checkbox = document.getElementById('refreshCheckbox')
|
|
||||||
function autoRefresh(){
|
|
||||||
if (! checkbox.checked || document.hidden){return}
|
|
||||||
getBlocks()
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupInterval(){
|
|
||||||
if (checkbox.checked){
|
|
||||||
refreshInterval = setInterval(autoRefresh, 3000)
|
|
||||||
autoRefresh()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
clearInterval(refreshInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
var refreshInterval = setInterval(autoRefresh, 3000)
|
|
||||||
setupInterval()
|
|
||||||
|
|
||||||
checkbox.onchange = function(){setupInterval}
|
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", function() {
|
|
||||||
if (document.visibilityState === 'visible') {
|
|
||||||
autoRefresh()
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,265 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
This file handles the boards/circles interface
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
requested = []
|
|
||||||
newPostForm = document.getElementById('addMsg')
|
|
||||||
firstLoad = true
|
|
||||||
lastLoadedBoard = 'global'
|
|
||||||
loadingMessage = document.getElementById('loadingBoard')
|
|
||||||
loadedAny = false
|
|
||||||
loadingTimeout = 8000
|
|
||||||
|
|
||||||
let toggleLoadingMessage = function(){
|
|
||||||
switch (loadingMessage.style.display){
|
|
||||||
case "inline-block":
|
|
||||||
loadingMessage.style.display = "none"
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
loadingMessage.style.display = "initial"
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/circles/version', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((ver) => ver.text())
|
|
||||||
.then(function(ver) {
|
|
||||||
document.getElementById('circlesVersion').innerText = ver
|
|
||||||
})
|
|
||||||
|
|
||||||
function appendMessages(msg, blockHash, beforeHash, channel) {
|
|
||||||
if (channel !== document.getElementById('feedIDInput').value) return // ignore if channel name isn't matching
|
|
||||||
if (msg.length == 0) return // ignore empty messages
|
|
||||||
|
|
||||||
var humanDate = new Date(0)
|
|
||||||
var msgDate = msg['meta']['time']
|
|
||||||
var feed = document.getElementById("feed")
|
|
||||||
var beforeEl = null
|
|
||||||
|
|
||||||
if (msgDate === undefined){
|
|
||||||
msgDate = 'unknown'
|
|
||||||
} else {
|
|
||||||
humanDate.setUTCSeconds(msgDate)
|
|
||||||
msgDate = humanDate.toLocaleString("en-US", {timeZone: "Etc/GMT"})
|
|
||||||
}
|
|
||||||
|
|
||||||
var el = document.createElement('div')
|
|
||||||
el.innerText = msg['content']
|
|
||||||
|
|
||||||
if (beforeHash !== null) {
|
|
||||||
for (i = 0; i < feed.children.length; i++) {
|
|
||||||
if (feed.children[i].getAttribute('data-bl') === beforeHash) {
|
|
||||||
beforeEl = feed.children[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Template Test */
|
|
||||||
// Test to see if the browser supports the HTML template element by checking
|
|
||||||
// for the presence of the template element's content attribute.
|
|
||||||
if ('content' in document.createElement('template')) {
|
|
||||||
|
|
||||||
// Instantiate the table with the existing HTML tbody
|
|
||||||
// and the row with the template
|
|
||||||
var template = document.getElementById('cMsgTemplate')
|
|
||||||
|
|
||||||
// Clone the new row and insert it into the table
|
|
||||||
var clone = document.importNode(template.content, true)
|
|
||||||
var div = clone.querySelectorAll("div")
|
|
||||||
var identicon = clone.querySelectorAll("img")
|
|
||||||
|
|
||||||
div[0].classList.add('entry')
|
|
||||||
div[0].setAttribute('timestamp', msg['meta']['time'])
|
|
||||||
|
|
||||||
div[0].setAttribute('data-bl', blockHash)
|
|
||||||
div[2].textContent = msg['content']
|
|
||||||
if (typeof msg['meta']['signer'] != 'undefined' && msg['meta']['signer'].length > 0){
|
|
||||||
div[3].textContent = msg['meta']['signer'].substr(0, 5)
|
|
||||||
setHumanReadableIDOnPost(div[3], msg['meta']['signer'])
|
|
||||||
div[3].onclick = function(){
|
|
||||||
navigator.clipboard.writeText(div[3].title).then(function() {
|
|
||||||
PNotify.notice("Copied poster identity to clipboard")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
div[3].title = msg['meta']['signer']
|
|
||||||
userIcon(msg['meta']['signer']).then(function(data){
|
|
||||||
identicon[0].src = "data:image/svg+xml;base64," + data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
identicon[0].remove()
|
|
||||||
}
|
|
||||||
div[4].textContent = msgDate
|
|
||||||
|
|
||||||
loadingMessage.style.display = "none"
|
|
||||||
loadedAny = true
|
|
||||||
if (firstLoad){
|
|
||||||
//feed.appendChild(clone)
|
|
||||||
feed.prepend(clone)
|
|
||||||
firstLoad = false
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if (beforeEl === null){
|
|
||||||
feed.prepend(clone)
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
beforeEl.insertAdjacentElement("beforebegin", clone.children[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBlocks(){
|
|
||||||
var feed = document.getElementById("feed")
|
|
||||||
var ch = document.getElementById('feedIDInput').value
|
|
||||||
if (lastLoadedBoard !== ch){
|
|
||||||
requested = []
|
|
||||||
|
|
||||||
toggleLoadingMessage()
|
|
||||||
loadedAny = false
|
|
||||||
|
|
||||||
while (feed.firstChild) feed.removeChild(feed.firstChild); // remove all messages from feed
|
|
||||||
|
|
||||||
setTimeout(function(){
|
|
||||||
if (! loadedAny && ch == document.getElementById('feedIDInput').value){
|
|
||||||
PNotify.notice("There are no posts for " + ch + ". You can be the first!")
|
|
||||||
}
|
|
||||||
}, loadingTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
lastLoadedBoard = ch
|
|
||||||
if (document.getElementById('none') !== null){
|
|
||||||
document.getElementById('none').remove();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/circles/getpostsbyboard/' + ch, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.text())
|
|
||||||
.then(function(feedText) {
|
|
||||||
var blockList = feedText.split(',')
|
|
||||||
|
|
||||||
for (i = 0; i < blockList.length; i++){
|
|
||||||
blockList[i] = "0".repeat(64 - blockList[i].length) + blockList[i] // pad hash with zeroes
|
|
||||||
|
|
||||||
if (! requested.includes(blockList[i])){
|
|
||||||
if (blockList[i].length == 0) continue
|
|
||||||
else requested.push(blockList[i])
|
|
||||||
loadMessage(blockList[i], blockList, i, ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortEntries()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMessage(blockHash, blockList, count, channel){
|
|
||||||
if (blockHash == '0000000000000000000000000000000000000000000000000000000000000000'){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fetch('/getblockdata/' + blockHash, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}}).then(function(response) {
|
|
||||||
if (!response.ok) {
|
|
||||||
let on404 = function() {
|
|
||||||
if (response.status == 404){
|
|
||||||
fetch('/circles/removefromcache/' + channel + '/' + blockHash, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
response.json().then(function(data){
|
|
||||||
let before = blockList[count - 1]
|
|
||||||
let delay = 2000
|
|
||||||
if (typeof before == "undefined"){
|
|
||||||
before = null
|
|
||||||
} else {
|
|
||||||
let existing = document.getElementsByClassName('cMsgBox')
|
|
||||||
for (x = 0; x < existing.length; x++){
|
|
||||||
if (existing[x].getAttribute('data-bl') === before){
|
|
||||||
delay = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTimeout(function(){appendMessages(data, blockHash, before, channel)}, delay)
|
|
||||||
})
|
|
||||||
return response;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('refreshFeed').onclick = function() {
|
|
||||||
getBlocks()
|
|
||||||
}
|
|
||||||
|
|
||||||
newPostForm.onsubmit = function(){
|
|
||||||
var message = document.getElementById('newMsgText').value
|
|
||||||
var channel = document.getElementById('feedIDInput').value
|
|
||||||
var meta = {'ch': channel}
|
|
||||||
let doSign = document.getElementById('postAnon').checked
|
|
||||||
var postData = {'message': message, 'sign': doSign, 'type': 'brd', 'encrypt': false, 'meta': JSON.stringify(meta)}
|
|
||||||
postData = JSON.stringify(postData)
|
|
||||||
newPostForm.style.display = 'none'
|
|
||||||
fetch('/insertblock', {
|
|
||||||
method: 'POST',
|
|
||||||
body: postData,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((resp) => resp.text())
|
|
||||||
.then(function(data) {
|
|
||||||
newPostForm.style.display = 'block'
|
|
||||||
if (data == 'failure due to duplicate insert'){
|
|
||||||
PNotify.error({
|
|
||||||
text: "This message is already queued"
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
PNotify.success({
|
|
||||||
text: "Message queued for posting"
|
|
||||||
})
|
|
||||||
setTimeout(function(){getBlocks()}, 500)
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
resetCirclePickers = function(){
|
|
||||||
document.getElementById('recommendedBoards').value = ""
|
|
||||||
document.getElementById('popularBoards').value = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('feedIDInput').onchange = resetCirclePickers
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Handle default board picker
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
recommendedIDs = document.getElementById('recommendedBoards')
|
|
||||||
|
|
||||||
recommendedIDs.onchange = function(){
|
|
||||||
document.getElementById('feedIDInput').value = recommendedIDs.value
|
|
||||||
getBlocks()
|
|
||||||
resetCirclePickers()
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
detect for Circles if plaintext insert/storage is enabled
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
plaintext_enabled = null
|
|
||||||
|
|
||||||
fetch('/config/get/general.store_plaintext_blocks', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.text())
|
|
||||||
.then(function(data) {
|
|
||||||
plaintext_enabled = true
|
|
||||||
if (data == "false"){
|
|
||||||
plaintext_enabled = false
|
|
||||||
PNotify.error({
|
|
||||||
text: "Plaintext storage is disabled. You will not be able to see new posts or make posts yourself"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,196 +0,0 @@
|
|||||||
<!DOCTYPE HTML>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset='utf-8'>
|
|
||||||
<!--Mobile responsive-->
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>
|
|
||||||
Circles
|
|
||||||
</title>
|
|
||||||
<link rel="shortcut icon" type="image/ico" href="/shared/images/favicon.ico">
|
|
||||||
<link rel="stylesheet" href="/shared/main/PNotifyBrightTheme.css">
|
|
||||||
<link rel="stylesheet" href="/shared/fontawesome-free-5.10.2/css/all.min.css">
|
|
||||||
<link rel="stylesheet" href="/gettheme">
|
|
||||||
<link rel="stylesheet" href="theme.css">
|
|
||||||
<script defer src="/shared/base32.js"></script>
|
|
||||||
<script defer src="/shared/identicon.js"></script>
|
|
||||||
<script defer src="/shared/node_modules/pnotify/dist/iife/PNotify.js"></script>
|
|
||||||
<script defer src="/shared/node_modules/pnotify/dist/iife/PNotifyButtons.js"></script>
|
|
||||||
<script defer src="/shared/useridenticons.js"></script>
|
|
||||||
<script defer src="/shared/misc.js"></script>
|
|
||||||
<script defer src="/shared/navbar.js"></script>
|
|
||||||
<script defer src="/shared/main/apicheck.js"></script>
|
|
||||||
<script defer src="detect-plaintext-storage.js"></script>
|
|
||||||
<script defer src="sethumanreadable.js"></script>
|
|
||||||
<script defer src="default-circle-picker.js"></script>
|
|
||||||
<script defer src="sort-posts.js"></script>
|
|
||||||
<script defer src="board.js"></script>
|
|
||||||
<script defer src="autorefresh.js"></script>
|
|
||||||
<script defer src="popular.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
<a class="navbar-item idLink" href="/">
|
|
||||||
<img src="/shared/images/favicon.ico" class="navbarLogo">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
|
||||||
data-target="navbarBasic">
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="navbarBasic" class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item idLink" href="/mail/">Mail</a>
|
|
||||||
<a class="navbar-item idLink" href="/friends/">Friends</a>
|
|
||||||
<a class="navbar-item idLink" href="/board/">Circles</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!--Hero (Dark Section)-->
|
|
||||||
<section class="hero is-small is-dark">
|
|
||||||
<div class="hero-body">
|
|
||||||
<div class="container">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">
|
|
||||||
<h1 class="title">
|
|
||||||
Circles <span class="is-pulled-right">v<span id='circlesVersion'></span></span>
|
|
||||||
</h1>
|
|
||||||
<h2 class="subtitle">
|
|
||||||
Anonymous message boards
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<!--Start Content-->
|
|
||||||
<div class="container">
|
|
||||||
<div class="columns">
|
|
||||||
<!--Add Friend-->
|
|
||||||
<div class="column is-one-third">
|
|
||||||
<div class="card">
|
|
||||||
<form method='POST' action='/' id='addMsg'>
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">
|
|
||||||
Post message
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
<textarea id='newMsgText' class="textarea" name='newMsgText' rows=10 cols=50 required
|
|
||||||
minlength="2"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer class="card-footer">
|
|
||||||
<a class="card-footer-item">
|
|
||||||
<input class='button is-primary' type='submit' value='Post'>
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--Feed-->
|
|
||||||
<div class="column">
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">
|
|
||||||
Feed
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
<div class="field">
|
|
||||||
<div class="field has-addons">
|
|
||||||
<p class="control">
|
|
||||||
<a class="button is-static">Circle Name</a>
|
|
||||||
</p>
|
|
||||||
<p class="control is-expanded">
|
|
||||||
<input id="feedIDInput" class="input" placeholder="Board name" value="global">
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<a class="button is-success" id="refreshFeed">Refresh Feed</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-2">
|
|
||||||
<div class="control">
|
|
||||||
<label for="recommendedBoards" class="label">Default Circles:</label>
|
|
||||||
<div class="select">
|
|
||||||
<select id="recommendedBoards">
|
|
||||||
<option value=""></option>
|
|
||||||
<option value="global">Global</option>
|
|
||||||
<option value="onionr">Onionr</option>
|
|
||||||
<option value="games">Games</option>
|
|
||||||
<option value="politics">Politics</option>
|
|
||||||
<option value="tech">Tech</option>
|
|
||||||
<option value="random">Random</option>
|
|
||||||
<option value="privacy">Privacy</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="column is-2">
|
|
||||||
<div class="control">
|
|
||||||
<label for="popularBoards" class="label">Popular Circles:</label>
|
|
||||||
<div class="select">
|
|
||||||
<select id="popularBoards">
|
|
||||||
<option value=""> </option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="control">
|
|
||||||
<br>
|
|
||||||
Note: All posts in Circles are publicly accessible.
|
|
||||||
</p>
|
|
||||||
<input type="checkbox" class="checkbox" id="refreshCheckbox" checked>
|
|
||||||
<label for="refreshCheckbox">Auto refresh feed</label>
|
|
||||||
<br>
|
|
||||||
<input type="checkbox" class="checkbox" id="postAnon" checked>
|
|
||||||
<label for="postAnon">Sign posts</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<span id='loadingBoard'><i class="fas fa-yin-yang fa-spin"></i></span>
|
|
||||||
<div id='feed'>
|
|
||||||
<span id='none'>None yet, try refreshing 😃</span>
|
|
||||||
<!--Message Items are appended here based on template-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--Template markup for Circle message-->
|
|
||||||
<template id="cMsgTemplate">
|
|
||||||
<div class="box cMsgBox">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column cMsg">
|
|
||||||
Message
|
|
||||||
</div>
|
|
||||||
<div class="column cAuthor is-narrow"></div>
|
|
||||||
<img class="identicon image is-48x48" alt="user icon" src="/shared/images/anon.svg">
|
|
||||||
<div class="column is-narrow cMsgDate">
|
|
||||||
Date
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Load popular boards and show them in the UI. Handle selections of popular boards.
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
fetch('/circles/getpopular/8', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((popular) => popular.text())
|
|
||||||
.then(function(popular) {
|
|
||||||
var popularSelect = document.getElementById('popularBoards')
|
|
||||||
let boards = popular.split(',')
|
|
||||||
for (board of boards){
|
|
||||||
let newOption = document.createElement('option')
|
|
||||||
if (board == ""){continue}
|
|
||||||
newOption.value = board
|
|
||||||
newOption.innerText = board.charAt(0).toUpperCase() + board.slice(1)
|
|
||||||
console.debug(board)
|
|
||||||
popularSelect.appendChild(newOption)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
document.getElementById('popularBoards').onchange = function(){
|
|
||||||
document.getElementById('feedIDInput').value = document.getElementById('popularBoards').value
|
|
||||||
getBlocks()
|
|
||||||
resetCirclePickers()
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Set human readable public keys onto post author elements
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
humanReadableKeys = {}
|
|
||||||
|
|
||||||
function setHumanReadableIDOnPost(el, key){
|
|
||||||
if (typeof humanReadableKeys[key] == "undefined"){
|
|
||||||
fetch('/getHumanReadable/' + key, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.text()) // Transform the data into json
|
|
||||||
.then(function(data) {
|
|
||||||
if (data.includes('HTML')){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
humanReadableKeys[key] = data
|
|
||||||
setHumanReadableIDOnPost(el, key)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
el.innerText = humanReadableKeys[key].split('-').slice(0, 3).join(' ')
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Sort post entries
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function sortEntries() {
|
|
||||||
var entries = document.getElementsByClassName('entry')
|
|
||||||
|
|
||||||
if (entries.length > 1) {
|
|
||||||
const sortBy = 'timestamp'
|
|
||||||
const parent = entries[0].parentNode
|
|
||||||
|
|
||||||
const sorted = Array.from(entries).sort((a, b) => b.getAttribute(sortBy) - a.getAttribute(sortBy))
|
|
||||||
sorted.forEach(element => parent.appendChild(element))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
.cMsg{
|
|
||||||
word-wrap:break-word;
|
|
||||||
word-break:break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body{
|
|
||||||
background-color: #212224;
|
|
||||||
color: white;
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
This file primarily serves to allow specific fetching of circles board messages
|
|
||||||
"""
|
|
||||||
from flask import Response, Blueprint, g
|
|
||||||
|
|
||||||
from deadsimplekv import DeadSimpleKV
|
|
||||||
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
flask_blueprint = Blueprint('debugAPI', __name__)
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/debug/dump_shared_state')
|
|
||||||
def get_shared_state() -> Response:
|
|
||||||
"""Return somewhat human-readable dump of shared toomanyobjects."""
|
|
||||||
resp = ""
|
|
||||||
for i in g.too_many.objects.keys:
|
|
||||||
resp += i + dir(g.too_many.objects.keys[i]) + "\n"
|
|
||||||
return Response(resp)
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/debug/dump_shared_vars')
|
|
||||||
def get_shared_vars() -> Response:
|
|
||||||
"""Return somewhat human-readable dump of pseudo globals (DeadSimpleKV)."""
|
|
||||||
kv: DeadSimpleKV = g.too_many.get(DeadSimpleKV)
|
|
||||||
resp = ""
|
|
||||||
for i in kv.keys:
|
|
||||||
resp += i + dir(g.too_many.objects.keys[i]) + "\n"
|
|
||||||
return Response(resp)
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
{ "name": "debug",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"author": "onionr"
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Client HTTP API debug endpoints
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import locale
|
|
||||||
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
# import after path insert
|
|
||||||
import debugapi # noqa
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
flask_blueprint = debugapi.flask_blueprint
|
|
||||||
|
|
||||||
plugin_name = 'debuginfo'
|
|
||||||
PLUGIN_VERSION = '0.0.0'
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name" : "encrypt",
|
|
||||||
"version" : "1.0",
|
|
||||||
"author" : "onionr"
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
'''
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
This default plugin allows users to encrypt/decrypt messages without using blocks
|
|
||||||
'''
|
|
||||||
'''
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Imports some useful libraries
|
|
||||||
import logger, config, threading, time, datetime, sys
|
|
||||||
|
|
||||||
import ujson as json
|
|
||||||
from nacl.exceptions import TypeError as NaclTypeError
|
|
||||||
|
|
||||||
from onionrutils import stringvalidators, bytesconverter
|
|
||||||
from onionrcrypto import encryption, keypair, signing, getourkeypair
|
|
||||||
import onionrexceptions, onionrusers
|
|
||||||
|
|
||||||
import locale
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
plugin_name = 'encrypt'
|
|
||||||
|
|
||||||
class PlainEncryption:
|
|
||||||
def __init__(self, api):
|
|
||||||
self.api = api
|
|
||||||
return
|
|
||||||
def encrypt(self):
|
|
||||||
# peer, data
|
|
||||||
plaintext = ""
|
|
||||||
encrypted = ""
|
|
||||||
# detect if signing is enabled
|
|
||||||
sign = True
|
|
||||||
try:
|
|
||||||
if sys.argv[3].lower() == 'false':
|
|
||||||
sign = False
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not stringvalidators.validate_pub_key(sys.argv[2]):
|
|
||||||
raise onionrexceptions.InvalidPubkey
|
|
||||||
except (ValueError, IndexError) as e:
|
|
||||||
logger.error("Peer public key not specified", terminal=True)
|
|
||||||
except onionrexceptions.InvalidPubkey:
|
|
||||||
logger.error("Invalid public key", terminal=True)
|
|
||||||
else:
|
|
||||||
pubkey = sys.argv[2]
|
|
||||||
# Encrypt if public key is valid
|
|
||||||
logger.info("Please enter your message (ctrl-d or -q to stop):", terminal=True)
|
|
||||||
try:
|
|
||||||
for line in sys.stdin:
|
|
||||||
if line == '-q\n':
|
|
||||||
break
|
|
||||||
plaintext += line
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(1)
|
|
||||||
# Build Message to encrypt
|
|
||||||
data = {}
|
|
||||||
myPub = keypair[0]
|
|
||||||
if sign:
|
|
||||||
data['sig'] = signing.ed_sign(plaintext, key=keypair[1], encodeResult=True)
|
|
||||||
data['sig'] = bytesconverter.bytes_to_str(data['sig'])
|
|
||||||
data['signer'] = myPub
|
|
||||||
data['data'] = plaintext
|
|
||||||
data = json.dumps(data)
|
|
||||||
plaintext = data
|
|
||||||
encrypted = encryption.pub_key_encrypt(plaintext, pubkey, encodedData=True)
|
|
||||||
encrypted = bytesconverter.bytes_to_str(encrypted)
|
|
||||||
logger.info('Encrypted Message: \n\nONIONR ENCRYPTED DATA %s END ENCRYPTED DATA' % (encrypted,), terminal=True)
|
|
||||||
|
|
||||||
def decrypt(self):
|
|
||||||
plaintext = ""
|
|
||||||
data = ""
|
|
||||||
logger.info("Please enter your message (ctrl-d or -q to stop):", terminal=True)
|
|
||||||
keypair = getourkeypair.get_keypair()
|
|
||||||
try:
|
|
||||||
for line in sys.stdin:
|
|
||||||
if line == '-q\n':
|
|
||||||
break
|
|
||||||
data += line
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(1)
|
|
||||||
if len(data) <= 1:
|
|
||||||
return
|
|
||||||
encrypted = data.replace('ONIONR ENCRYPTED DATA ', '').replace('END ENCRYPTED DATA', '')
|
|
||||||
myPub = keypair[0]
|
|
||||||
decrypted = encryption.pub_key_decrypt(encrypted, privkey=keypair[1], encodedData=True)
|
|
||||||
if decrypted == False:
|
|
||||||
logger.error("Decryption failed", terminal=True)
|
|
||||||
else:
|
|
||||||
data = json.loads(decrypted)
|
|
||||||
logger.info('Decrypted Message: \n\n%s' % data['data'], terminal=True)
|
|
||||||
try:
|
|
||||||
logger.info("Signing public key: %s" % (data['signer'],), terminal=True)
|
|
||||||
if not signing.ed_verify(data['data'], data['signer'], data['sig']): raise ValueError
|
|
||||||
except (ValueError, KeyError) as e:
|
|
||||||
logger.warn("WARNING: THIS MESSAGE HAS A MISSING OR INVALID SIGNATURE", terminal=True)
|
|
||||||
else:
|
|
||||||
logger.info("Message has good signature.", terminal=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
def on_decrypt_cmd(api, data=None):
|
|
||||||
try:
|
|
||||||
PlainEncryption(api).decrypt()
|
|
||||||
except binascii.Error:
|
|
||||||
logger.error("Invalid ciphertext padding", terminal=True)
|
|
||||||
except NaclTypeError:
|
|
||||||
logger.error("Ciphertext too short.", terminal=True)
|
|
||||||
|
|
||||||
def on_encrypt_cmd(api, data=None):
|
|
||||||
PlainEncryption(api).encrypt()
|
|
||||||
|
|
||||||
on_encrypt_cmd.onionr_help = """encrypt <user_key>\nEncrypt text data to an Onionr user key. Similar to PGP"""
|
|
||||||
on_decrypt_cmd.onionr_help = """decrypt\nDecrypt text data with your Onionr key. Similar to PGP"""
|
|
||||||
ONIONR_COMMANDS = ['encrypt', 'decrypt']
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name" : "pms",
|
|
||||||
"version" : "0.1.2",
|
|
||||||
"author" : "onionr"
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Load the user's inbox and return it as a list
|
|
||||||
"""
|
|
||||||
from onionrblocks import onionrblockapi
|
|
||||||
from coredb import blockmetadb
|
|
||||||
from utils import reconstructhash, identifyhome
|
|
||||||
import deadsimplekv as simplekv
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
inbox_cache = {}
|
|
||||||
def load_inbox():
|
|
||||||
inbox_list = []
|
|
||||||
deleted = simplekv.DeadSimpleKV(identifyhome.identify_home() + '/mailcache.dat').get('deleted_mail')
|
|
||||||
if deleted is None:
|
|
||||||
deleted = []
|
|
||||||
|
|
||||||
for blockHash in blockmetadb.get_blocks_by_type('pm'):
|
|
||||||
if blockHash in deleted:
|
|
||||||
continue
|
|
||||||
if blockHash in inbox_cache:
|
|
||||||
inbox_list.append(blockHash)
|
|
||||||
continue
|
|
||||||
block = onionrblockapi.Block(blockHash)
|
|
||||||
block.decrypt()
|
|
||||||
if block.decrypted and reconstructhash.deconstruct_hash(blockHash):
|
|
||||||
inbox_cache[blockHash] = True
|
|
||||||
inbox_list.append(blockHash)
|
|
||||||
return inbox_list
|
|
@ -1,97 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
HTTP endpoints for mail plugin
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
import ujson as json
|
|
||||||
from flask import Response, request, redirect, Blueprint, abort
|
|
||||||
from flask import send_from_directory
|
|
||||||
import deadsimplekv as simplekv
|
|
||||||
|
|
||||||
from httpapi.sse.wrapper import SSEWrapper
|
|
||||||
from onionrusers import contactmanager
|
|
||||||
from onionrutils import stringvalidators
|
|
||||||
from utils import reconstructhash, identifyhome
|
|
||||||
from utils.bettersleep import better_sleep as sleep
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
import loadinbox
|
|
||||||
import sentboxdb
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
flask_blueprint = Blueprint('mail', __name__)
|
|
||||||
kv = simplekv.DeadSimpleKV(identifyhome.identify_home() + '/mailcache.dat')
|
|
||||||
root = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
|
|
||||||
sse_wrapper = SSEWrapper()
|
|
||||||
|
|
||||||
@flask_blueprint.route('/mail/<path:path>', endpoint='mailstatic')
|
|
||||||
def load_mail(path):
|
|
||||||
return send_from_directory(root + '/web/', path)
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/mail/', endpoint='mailindex')
|
|
||||||
def load_mail_index():
|
|
||||||
return send_from_directory(root + '/web/', 'index.html')
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/mail/ping')
|
|
||||||
def mail_ping():
|
|
||||||
return 'pong!'
|
|
||||||
|
|
||||||
@flask_blueprint.route('/mail/deletemsg/<block>', methods=['POST'])
|
|
||||||
def mail_delete(block):
|
|
||||||
if not stringvalidators.validate_hash(block):
|
|
||||||
abort(504)
|
|
||||||
block = reconstructhash.deconstruct_hash(block)
|
|
||||||
existing = kv.get('deleted_mail')
|
|
||||||
if existing is None:
|
|
||||||
existing = []
|
|
||||||
if block not in existing:
|
|
||||||
existing.append(block)
|
|
||||||
kv.put('deleted_mail', existing)
|
|
||||||
kv.flush()
|
|
||||||
return 'success'
|
|
||||||
|
|
||||||
@flask_blueprint.route('/mail/getinbox')
|
|
||||||
def list_inbox():
|
|
||||||
return ','.join(loadinbox.load_inbox())
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/mail/streaminbox')
|
|
||||||
def stream_inbox():
|
|
||||||
def _stream():
|
|
||||||
while True:
|
|
||||||
yield "data: " + ','.join(loadinbox.load_inbox()) + "\n\n"
|
|
||||||
sleep(1)
|
|
||||||
return sse_wrapper.handle_sse_request(_stream)
|
|
||||||
|
|
||||||
|
|
||||||
@flask_blueprint.route('/mail/getsentbox')
|
|
||||||
def list_sentbox():
|
|
||||||
kv.refresh()
|
|
||||||
sentbox_list = sentboxdb.SentBox().listSent()
|
|
||||||
list_copy = list(sentbox_list)
|
|
||||||
deleted = kv.get('deleted_mail')
|
|
||||||
if deleted is None:
|
|
||||||
deleted = []
|
|
||||||
for sent in list_copy:
|
|
||||||
if sent['hash'] in deleted:
|
|
||||||
sentbox_list.remove(sent)
|
|
||||||
continue
|
|
||||||
sent['name'] = contactmanager.ContactManager(sent['peer'], saveUser=False).get_info('name')
|
|
||||||
return json.dumps(sentbox_list)
|
|
@ -1,87 +0,0 @@
|
|||||||
"""Onionr - Private P2P Communication.
|
|
||||||
|
|
||||||
Private messages in an email like fashion
|
|
||||||
"""
|
|
||||||
import locale
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
import ujson as json
|
|
||||||
|
|
||||||
from onionrusers import contactmanager
|
|
||||||
from utils import reconstructhash
|
|
||||||
from onionrutils import bytesconverter
|
|
||||||
import config
|
|
||||||
import notifier
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
|
|
||||||
plugin_name = 'pms'
|
|
||||||
PLUGIN_VERSION = '0.1.2'
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
import sentboxdb, mailapi, loadinbox # import after path insert
|
|
||||||
from onblacklist import on_blacklist_add
|
|
||||||
|
|
||||||
flask_blueprint = mailapi.flask_blueprint
|
|
||||||
security_whitelist = ['mail.mailstatic', 'mail.mailindex']
|
|
||||||
|
|
||||||
|
|
||||||
def add_deleted(keyStore, b_hash):
|
|
||||||
existing = keyStore.get('deleted_mail')
|
|
||||||
bHash = reconstructhash.reconstruct_hash(b_hash)
|
|
||||||
if existing is None:
|
|
||||||
existing = []
|
|
||||||
else:
|
|
||||||
if bHash in existing:
|
|
||||||
return
|
|
||||||
keyStore.put('deleted_mail', existing.append(b_hash))
|
|
||||||
|
|
||||||
|
|
||||||
def on_insertblock(api, data={}):
|
|
||||||
meta = json.loads(data['meta'])
|
|
||||||
if meta['type'] == 'pm':
|
|
||||||
sentboxTools = sentboxdb.SentBox()
|
|
||||||
sentboxTools.addToSent(data['hash'], data['peer'], data['content'], meta['subject'])
|
|
||||||
|
|
||||||
|
|
||||||
def on_processblocks(api, data=None):
|
|
||||||
if data['type'] != 'pm':
|
|
||||||
return
|
|
||||||
notification_func = notifier.notify
|
|
||||||
data['block'].decrypt()
|
|
||||||
metadata = data['block'].bmetadata
|
|
||||||
|
|
||||||
signer = bytesconverter.bytes_to_str(data['block'].signer)
|
|
||||||
user = contactmanager.ContactManager(signer, saveUser=False)
|
|
||||||
name = user.get_info("name")
|
|
||||||
if name != 'anonymous' and name != None:
|
|
||||||
signer = name.title()
|
|
||||||
else:
|
|
||||||
signer = signer[:5]
|
|
||||||
|
|
||||||
|
|
||||||
if data['block'].decrypted:
|
|
||||||
config.reload()
|
|
||||||
|
|
||||||
if config.get('mail.notificationSound', True):
|
|
||||||
notification_func = notifier.notification_with_sound
|
|
||||||
if config.get('mail.notificationSetting', True):
|
|
||||||
if not config.get('mail.strangersNotification', True):
|
|
||||||
if not user.isFriend():
|
|
||||||
return
|
|
||||||
notification_func(title="Onionr Mail - New Message", message="From: %s\n\nSubject: %s" % (signer, metadata['subject']))
|
|
@ -1,12 +0,0 @@
|
|||||||
from threading import Thread
|
|
||||||
|
|
||||||
from onionrutils import localcommand
|
|
||||||
|
|
||||||
|
|
||||||
def on_blacklist_add(api, data=None):
|
|
||||||
blacklisted_data = data['data']
|
|
||||||
|
|
||||||
def remove():
|
|
||||||
localcommand.local_command(f'/mail/deletemsg/{blacklisted_data}', post=True)
|
|
||||||
|
|
||||||
Thread(target=remove).start()
|
|
@ -1,79 +0,0 @@
|
|||||||
"""
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
This file handles the sentbox for the mail plugin
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
from onionrutils import epoch
|
|
||||||
from utils import identifyhome, reconstructhash
|
|
||||||
"""
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SentBox:
|
|
||||||
def __init__(self):
|
|
||||||
self.dbLocation = identifyhome.identify_home() + '/sentbox.db'
|
|
||||||
if not os.path.exists(self.dbLocation):
|
|
||||||
self.createDB()
|
|
||||||
return
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
self.conn = sqlite3.connect(self.dbLocation, timeout=30)
|
|
||||||
self.cursor = self.conn.cursor()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.conn.close()
|
|
||||||
|
|
||||||
def createDB(self):
|
|
||||||
conn = sqlite3.connect(self.dbLocation)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("""CREATE TABLE sent(
|
|
||||||
hash id not null,
|
|
||||||
peer text not null,
|
|
||||||
message text not null,
|
|
||||||
subject text not null,
|
|
||||||
date int not null
|
|
||||||
);
|
|
||||||
""")
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
def listSent(self):
|
|
||||||
self.connect()
|
|
||||||
retData = []
|
|
||||||
for entry in self.cursor.execute('SELECT * FROM sent;'):
|
|
||||||
retData.append({'hash': entry[0], 'peer': entry[1], 'message': entry[2], 'subject': entry[3], 'date': entry[4]})
|
|
||||||
self.close()
|
|
||||||
return retData
|
|
||||||
|
|
||||||
def addToSent(self, blockID, peer, message, subject=''):
|
|
||||||
blockID = reconstructhash.deconstruct_hash(blockID)
|
|
||||||
self.connect()
|
|
||||||
args = (blockID, peer, message, subject, epoch.get_epoch())
|
|
||||||
self.cursor.execute('INSERT INTO sent VALUES(?, ?, ?, ?, ?)', args)
|
|
||||||
self.conn.commit()
|
|
||||||
self.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
def removeSent(self, blockID):
|
|
||||||
blockID = reconstructhash.deconstruct_hash(blockID)
|
|
||||||
self.connect()
|
|
||||||
args = (blockID,)
|
|
||||||
self.cursor.execute('DELETE FROM sent where hash=?', args)
|
|
||||||
self.conn.commit()
|
|
||||||
self.close()
|
|
||||||
return
|
|
@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Settings modal closing
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
document.getElementById('closeSettingsModalButton').onclick = function(){
|
|
||||||
document.getElementById('settingsModal').classList.remove('is-active')
|
|
||||||
setActiveTab('inbox')
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector("#settingsModal .modal-background").onclick = function(){
|
|
||||||
document.getElementById('settingsModal').classList.remove('is-active')
|
|
||||||
setActiveTab('inbox')
|
|
||||||
}
|
|
@ -1,262 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>
|
|
||||||
Onionr Mail
|
|
||||||
</title>
|
|
||||||
<link rel="shortcut icon" type="image/ico" href="/shared/images/favicon.ico">
|
|
||||||
<link rel="stylesheet" href="/shared/fontawesome-free-5.10.2/css/all.min.css">
|
|
||||||
<link rel="stylesheet" href="/shared/main/PNotifyBrightTheme.css">
|
|
||||||
<link rel="stylesheet" href="/gettheme">
|
|
||||||
<link rel="stylesheet" href="/shared/node_modules/bulma-switch/dist/css/bulma-switch.min.css">
|
|
||||||
<link rel="stylesheet" href="/mail/mail.css">
|
|
||||||
<script defer src="/shared/node_modules/pnotify/dist/iife/PNotify.js"></script>
|
|
||||||
<script defer src="/shared/node_modules/pnotify/dist/iife/PNotifyButtons.js"></script>
|
|
||||||
<script defer src="/shared/eventsource.js"></script>
|
|
||||||
<script defer src="/shared/main/apicheck.js"></script>
|
|
||||||
<script defer src="/shared/misc.js"></script>
|
|
||||||
<script defer src="/mail/sethumanreadable.js"></script>
|
|
||||||
<script defer src="/mail/loadsettings.js"></script>
|
|
||||||
<script defer src="/mail/mail.js"></script>
|
|
||||||
<script defer src="/mail/sendmail.js"></script>
|
|
||||||
<script defer src="/mail/closesettings.js"></script>
|
|
||||||
<script defer src="/mail/settings.js"></script>
|
|
||||||
<script defer src="/shared/navbar.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
<a class="navbar-item idLink" href="/">
|
|
||||||
<img src="/shared/images/favicon.ico" class="navbarLogo">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
|
||||||
data-target="navbarBasic">
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="navbarBasic" class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item idLink" href="/mail/">Mail</a>
|
|
||||||
<a class="navbar-item idLink" href="/friends/">Friends</a>
|
|
||||||
<a class="navbar-item idLink" href="/board/">Circles</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!--Hero (Dark Section)-->
|
|
||||||
<section class="hero is-small is-dark">
|
|
||||||
<div class="hero-body">
|
|
||||||
<div class="container">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">
|
|
||||||
<h1 class="title">
|
|
||||||
Mail
|
|
||||||
</h1>
|
|
||||||
<h2 class="subtitle">
|
|
||||||
Private and safe messages
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="column is-7">
|
|
||||||
<div class="field">
|
|
||||||
<div class="field has-addons">
|
|
||||||
<p class="control">
|
|
||||||
<a class="button is-static">
|
|
||||||
<i class="fas fa-fingerprint"></i>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p class="control is-expanded">
|
|
||||||
<input id="myPub" class="input myPub" type="text" readonly>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<a id="myPubCopy" class="button is-primary"><i class="fas fa-copy"></i></a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<div id="messageDisplay" class="overlay">
|
|
||||||
<div class="overlayContent">
|
|
||||||
<span class="closeOverlay" overlay="messageDisplay"></span>
|
|
||||||
<div>
|
|
||||||
From: <input type="text" id="fromUser" readonly> Signature: <span id="sigValid"></span> <span
|
|
||||||
id="addUnknownContact"><button class="button is-primary">Add to Contacts</button></span>
|
|
||||||
</div>
|
|
||||||
<div class="break-up">
|
|
||||||
Subject: <span id="subjectView"></span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="replyBtn" class="button is-primary break-up">Reply</button>
|
|
||||||
</div>
|
|
||||||
<div id="signatureValidity"></div>
|
|
||||||
<div id="threadDisplay" class="pre messageContent">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="sentboxDisplay" class="overlay">
|
|
||||||
<div class="overlayContent">
|
|
||||||
<span class="closeOverlay" overlay="sentboxDisplay"></span>
|
|
||||||
To: <input id="toID" readonly type="text">
|
|
||||||
<div id="sentboxDisplayText" class="pre messageContent">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="settingsModal" class="modal">
|
|
||||||
<div class="modal-background"></div>
|
|
||||||
<div class="modal-card">
|
|
||||||
<header class="modal-card-head">
|
|
||||||
<button id="closeSettingsModalButton" class="closeSettingsModal delete" aria-label="close"></button>
|
|
||||||
</header>
|
|
||||||
<section class="modal-card-body">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">
|
|
||||||
<div>Ask senders to use forward-secrecy</div>
|
|
||||||
<small>Turn off if using 1 ID on more than 1 device, or have Onionr data erasure on exit enabled.</small>
|
|
||||||
</div>
|
|
||||||
<div class="column is-2">
|
|
||||||
<div class="field">
|
|
||||||
<input id="forwardSecrecySetting" type="checkbox"
|
|
||||||
class="switch is-rounded is-danger">
|
|
||||||
<label for="forwardSecrecySetting"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">
|
|
||||||
<div>Message padding</div>
|
|
||||||
<small>Improves privacy slightly for a small performance hit.</small>
|
|
||||||
</div>
|
|
||||||
<div class="column is-2">
|
|
||||||
<div class="field">
|
|
||||||
<input id="messagePaddingSetting" type="checkbox"
|
|
||||||
class="switch is-rounded is-warning">
|
|
||||||
<label for="messagePaddingSetting"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">
|
|
||||||
Inbox notifications
|
|
||||||
</div>
|
|
||||||
<div class="column is-2">
|
|
||||||
<div class="field">
|
|
||||||
<input id="notificationSetting" type="checkbox"
|
|
||||||
class="switch is-rounded" checked>
|
|
||||||
<label for="notificationSetting"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="columns notificationSetting">
|
|
||||||
<div class="column">
|
|
||||||
Notifications for stranger's messages
|
|
||||||
</div>
|
|
||||||
<div class="column is-2">
|
|
||||||
<div class="field">
|
|
||||||
<input id="strangersNotification" type="checkbox"
|
|
||||||
class="switch is-rounded" checked>
|
|
||||||
<label for="strangersNotification"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="columns notificationSetting">
|
|
||||||
<div class="column">
|
|
||||||
Notification sound
|
|
||||||
</div>
|
|
||||||
<div class="column is-2">
|
|
||||||
<div class="field">
|
|
||||||
<input id="notificationSound" type="checkbox"
|
|
||||||
class="switch is-rounded" checked>
|
|
||||||
<label for="notificationSound"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
Signature
|
|
||||||
<textarea id="mailSignatureSetting" class="textarea" placeholder="Signature to add to every message" rows="5"></textarea>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--Start of content-->
|
|
||||||
<div class="container">
|
|
||||||
<div class="tabs" id="tabBtns">
|
|
||||||
<ul>
|
|
||||||
<li class="is-active">
|
|
||||||
<a>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa fa-envelope"></i>
|
|
||||||
</span>
|
|
||||||
<span id="inboxTab">Inbox</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa fa-paper-plane"></i>
|
|
||||||
</span>
|
|
||||||
<span>Sent</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa fa-pen"></i>
|
|
||||||
</span>
|
|
||||||
<span>Compose</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa fa-cog"></i>
|
|
||||||
</span>
|
|
||||||
<span>Settings</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="content" id="noInbox">No messages to show ¯\_(ツ)_/¯</div>
|
|
||||||
<div class="content">
|
|
||||||
<div class="mailPing">
|
|
||||||
API server either shutdown, has disabled mail, or has experienced a bug.
|
|
||||||
</div>
|
|
||||||
<div id="threads" class="threads">
|
|
||||||
<div id="threadPlaceholder">Nothing here yet 😞</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="sendMessage">
|
|
||||||
<div class="container">
|
|
||||||
<div class="field">
|
|
||||||
<label><i class="fas fa-user"></i> Select friend: <select id="friendSelect"></select></label>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="" id="sendForm" enctype="application/x-www-form-urlencoded">
|
|
||||||
<div class="field">
|
|
||||||
To: <input id="draftID" type="text" name="to" placeholder="pubkey or select above" autocomplete="off" required>
|
|
||||||
</div>
|
|
||||||
Subject: <input name="subject" id="draftSubject" maxlength="25" type="text"
|
|
||||||
placeholder="message subject" autocomplete="off">
|
|
||||||
<div class="field">
|
|
||||||
<textarea name="message" class="textarea" placeholder="type your message..." id="draftText" required></textarea>
|
|
||||||
</div>
|
|
||||||
<input type="submit" value="Send" class="button is-primary successBtn">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="infoOverlay" class="overlay">
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,29 +0,0 @@
|
|||||||
mailSettings = {}
|
|
||||||
|
|
||||||
fetch('/config/get/mail', {
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.json())
|
|
||||||
.then(function(settings) {
|
|
||||||
mailSettings = settings || {}
|
|
||||||
if (mailSettings.default_forward_secrecy === false){
|
|
||||||
document.getElementById('forwardSecrecySetting').checked = false
|
|
||||||
}
|
|
||||||
if (mailSettings.use_padding === false){
|
|
||||||
document.getElementById('messagePaddingSetting').checked = false
|
|
||||||
}
|
|
||||||
if (mailSettings.notificationSetting === false){
|
|
||||||
document.getElementById('notificationSetting').checked = false
|
|
||||||
}
|
|
||||||
if (mailSettings.notificationSound === false){
|
|
||||||
document.getElementById('notificationSound').checked = false
|
|
||||||
}
|
|
||||||
if (typeof mailSettings.signature != undefined && mailSettings.signature != null && mailSettings.signature != ""){
|
|
||||||
document.getElementById('mailSignatureSetting').value = mailSettings.signature
|
|
||||||
}
|
|
||||||
if (mailSettings.strangersNotification == false){
|
|
||||||
document.getElementById('strangersNotification').checked = false
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,60 +0,0 @@
|
|||||||
.threadEntry button{
|
|
||||||
margin-right: 1%;
|
|
||||||
}
|
|
||||||
.threadEntry span, .sentboxList span{
|
|
||||||
padding-left: 1%;
|
|
||||||
}
|
|
||||||
.threadEntry, .sentboxList{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlayContent{
|
|
||||||
background-color: lightgray;
|
|
||||||
border: 3px solid black;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: black;
|
|
||||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
|
||||||
min-height: 100%;
|
|
||||||
padding: 1em;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#draftText{
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
display: block;
|
|
||||||
width: 50%;
|
|
||||||
height: 75%;
|
|
||||||
min-width: 2%;
|
|
||||||
min-height: 5%;
|
|
||||||
background: white;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sentboxList{
|
|
||||||
padding-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent{
|
|
||||||
padding-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#tabBtns ul .icon {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#settingsModal .modal-card-body{
|
|
||||||
font-size: 150%;
|
|
||||||
min-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settingsModal textarea{
|
|
||||||
resize: none;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#settingsModal small{
|
|
||||||
font-size: 0.5em;
|
|
||||||
}
|
|
@ -1,467 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
This file handles the mail interface
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
pms = ''
|
|
||||||
sentbox = ''
|
|
||||||
threadPart = document.getElementById('threads')
|
|
||||||
threadPlaceholder = document.getElementById('threadPlaceholder')
|
|
||||||
tabBtns = document.getElementById('tabBtns')
|
|
||||||
threadContent = {}
|
|
||||||
replyBtn = document.getElementById('replyBtn')
|
|
||||||
addUnknownContact = document.getElementById('addUnknownContact')
|
|
||||||
noInbox = document.getElementById('noInbox')
|
|
||||||
humanReadableCache = {}
|
|
||||||
|
|
||||||
function stripEndZeroes(str){
|
|
||||||
str = str.split("")
|
|
||||||
let zeroCount = 0
|
|
||||||
for (x = str.length - 1; x != 0; x--){
|
|
||||||
if (str[x] == "0"){
|
|
||||||
zeroCount += 1
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str.splice(str.length - zeroCount, zeroCount)
|
|
||||||
str = str.join("")
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addContact(pubkey, friendName){
|
|
||||||
fetch('/friends/add/' + pubkey, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}}).then(function(data) {
|
|
||||||
if (friendName.trim().length > 0){
|
|
||||||
post_to_url('/friends/setinfo/' + pubkey + '/name', {'data': friendName, 'token': webpass})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function openReply(bHash, quote, subject){
|
|
||||||
var inbox = document.getElementsByClassName('threadEntry')
|
|
||||||
var entry = ''
|
|
||||||
var friendName = ''
|
|
||||||
var key = ''
|
|
||||||
for(var i = 0; i < inbox.length; i++) {
|
|
||||||
if (inbox[i].getAttribute('data-hash') === bHash){
|
|
||||||
entry = inbox[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (entry.getAttribute('data-nameset') == 'true'){
|
|
||||||
document.getElementById('friendSelect').value = document.getElementById('fromUser').value
|
|
||||||
}
|
|
||||||
key = entry.getAttribute('data-pubkey')
|
|
||||||
document.getElementById('draftID').value = key
|
|
||||||
document.getElementById('draftSubject').value = 'RE: ' + subject
|
|
||||||
|
|
||||||
// Add quoted reply
|
|
||||||
var splitQuotes = quote.split('\n')
|
|
||||||
for (var x = 0; x < splitQuotes.length; x++){
|
|
||||||
splitQuotes[x] = '> ' + splitQuotes[x]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof humanReadableCache[key] != 'undefined'){
|
|
||||||
document.getElementById('draftID').value = humanReadableCache[key]
|
|
||||||
quote = '\n' + humanReadableCache[key].split('-').slice(0,3).join('-') + ' wrote:\n' + splitQuotes.join('\n')
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
quote = '\n' + key.substring(0, 12) + ' wrote:' + '\n' + splitQuotes.join('\n')
|
|
||||||
}
|
|
||||||
document.getElementById('draftText').value = quote
|
|
||||||
setActiveTab('compose')
|
|
||||||
}
|
|
||||||
|
|
||||||
function openThread(bHash, sender, date, sigBool, pubkey, subjectLine){
|
|
||||||
addUnknownContact.style.display = 'none'
|
|
||||||
var messageDisplay = document.getElementById('threadDisplay')
|
|
||||||
|
|
||||||
fetch('/getblockbody/' + bHash, {
|
|
||||||
"method": "get",
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.text())
|
|
||||||
.then(function(resp) {
|
|
||||||
document.getElementById('fromUser').value = sender || 'Anonymous'
|
|
||||||
document.getElementById('fromUser').value = pubkey || ''
|
|
||||||
document.getElementById('subjectView').innerText = subjectLine
|
|
||||||
|
|
||||||
resp = stripEndZeroes(resp)
|
|
||||||
|
|
||||||
messageDisplay.innerText = resp
|
|
||||||
var sigEl = document.getElementById('sigValid')
|
|
||||||
var sigMsg = 'signature'
|
|
||||||
|
|
||||||
// show add unknown contact button if peer is unknown
|
|
||||||
if (sender !== myPub && sigBool){
|
|
||||||
addUnknownContact.style.display = 'inline'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sigBool){
|
|
||||||
sigMsg = 'Good ' + sigMsg
|
|
||||||
sigEl.classList.remove('danger')
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
sigMsg = 'Bad/no ' + sigMsg + ' (message could be impersonating someone)'
|
|
||||||
sigEl.classList.add('danger')
|
|
||||||
replyBtn.style.display = 'none'
|
|
||||||
}
|
|
||||||
sigEl.innerText = sigMsg
|
|
||||||
overlay('messageDisplay')
|
|
||||||
replyBtn.onclick = function(){
|
|
||||||
document.getElementById('messageDisplay').style.visibility = 'hidden'
|
|
||||||
openReply(bHash, messageDisplay.innerText, subjectLine)
|
|
||||||
}
|
|
||||||
addUnknownContact.onclick = function(){
|
|
||||||
var friendName = prompt("Enter an alias for this contact:")
|
|
||||||
if (friendName === null || friendName.length == 0){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addContact(pubkey, friendName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveTab(tabName){
|
|
||||||
threadPart.innerHTML = ""
|
|
||||||
noInbox.style.display = 'none'
|
|
||||||
window.inboxActive = false
|
|
||||||
document.getElementById('sendMessage').classList.add('is-hidden')
|
|
||||||
switch(tabName){
|
|
||||||
case 'inbox':
|
|
||||||
window.inboxActive = true
|
|
||||||
refreshPms()
|
|
||||||
getInbox()
|
|
||||||
break
|
|
||||||
case 'sent':
|
|
||||||
getSentbox()
|
|
||||||
break
|
|
||||||
case 'compose':
|
|
||||||
document.getElementById('sendMessage').classList.remove('is-hidden')
|
|
||||||
break
|
|
||||||
case 'settings':
|
|
||||||
document.getElementById('settingsModal').classList.add('is-active')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteMessage(bHash){
|
|
||||||
fetch('/mail/deletemsg/' + bHash, {
|
|
||||||
"method": "post",
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
|
|
||||||
function mailPing(){
|
|
||||||
fetch('/mail/ping', {
|
|
||||||
"method": "get",
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then(function(resp) {
|
|
||||||
var pings = document.getElementsByClassName('mailPing')
|
|
||||||
if (resp.ok){
|
|
||||||
for (var i=0; i < pings.length; i++){
|
|
||||||
pings[i].style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
for (var i=0; i < pings.length; i++){
|
|
||||||
pings[i].style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadInboxEntries(bHash){
|
|
||||||
fetch('/getblockheader/' + bHash, {
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.json()) // Transform the data into json
|
|
||||||
.then(function(resp) {
|
|
||||||
//console.log(resp)
|
|
||||||
var entry = document.createElement('div')
|
|
||||||
var bHashDisplay = document.createElement('span')
|
|
||||||
//var senderInput = document.createElement('input')
|
|
||||||
var senderInput = document.createElement('span')
|
|
||||||
var subjectLine = document.createElement('span')
|
|
||||||
var dateStr = document.createElement('span')
|
|
||||||
var validSig = document.createElement('span')
|
|
||||||
var deleteBtn = document.createElement('button')
|
|
||||||
var humanDate = new Date(0)
|
|
||||||
var metadata = resp['metadata']
|
|
||||||
humanDate.setUTCSeconds(resp['meta']['time'])
|
|
||||||
humanDate = humanDate.toString()
|
|
||||||
validSig.style.display = 'none'
|
|
||||||
|
|
||||||
if (typeof resp['meta']['signer'] != 'undefined' && resp['meta']['signer'] != ''){
|
|
||||||
fetch('/friends/getinfo/' + resp['meta']['signer'] + '/name', {
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then(function(resp2){
|
|
||||||
if (!resp2.ok){
|
|
||||||
setHumanReadableValue(senderInput, resp['meta']['signer'])
|
|
||||||
entry.setAttribute('data-nameSet', false)
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
resp2.text().then(function(resp2){
|
|
||||||
loadHumanReadableToCache(resp['meta']['signer'])
|
|
||||||
senderInput.innerText = resp2
|
|
||||||
entry.setAttribute('data-nameSet', true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
senderInput.innerText = 'Anonymous'
|
|
||||||
entry.setAttribute('data-nameSet', false)
|
|
||||||
}
|
|
||||||
if (! resp['meta']['validSig']){
|
|
||||||
validSig.style.display = 'inline'
|
|
||||||
validSig.innerText = 'Signature Validity: Bad'
|
|
||||||
validSig.style.color = 'red'
|
|
||||||
}
|
|
||||||
//bHashDisplay.innerText = bHash.substring(0, 10)
|
|
||||||
entry.setAttribute('data-hash', bHash)
|
|
||||||
entry.setAttribute('data-pubkey', resp['meta']['signer'])
|
|
||||||
senderInput.readOnly = true
|
|
||||||
dateStr.innerText = humanDate.substring(0, humanDate.indexOf('('))
|
|
||||||
deleteBtn.classList.add('delete', 'deleteBtn')
|
|
||||||
if (metadata['subject'] === undefined || metadata['subject'] === null) {
|
|
||||||
subjectLine.innerText = '()'
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
subjectLine.innerText = '(' + metadata['subject'] + ')'
|
|
||||||
}
|
|
||||||
//entry.innerHTML = 'sender ' + resp['meta']['signer'] + ' - ' + resp['meta']['time']
|
|
||||||
threadPart.appendChild(entry)
|
|
||||||
entry.appendChild(deleteBtn)
|
|
||||||
entry.appendChild(bHashDisplay)
|
|
||||||
entry.appendChild(senderInput)
|
|
||||||
entry.appendChild(subjectLine)
|
|
||||||
entry.appendChild(dateStr)
|
|
||||||
entry.appendChild(validSig)
|
|
||||||
entry.classList.add('threadEntry')
|
|
||||||
|
|
||||||
entry.onclick = function(event){
|
|
||||||
if (event.target.classList.contains('deleteBtn')){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
openThread(entry.getAttribute('data-hash'), senderInput.innerText, dateStr.innerText, resp['meta']['validSig'], entry.getAttribute('data-pubkey'), subjectLine.innerText)
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteBtn.onclick = function(){
|
|
||||||
entry.parentNode.removeChild(entry);
|
|
||||||
deleteMessage(entry.getAttribute('data-hash'))
|
|
||||||
}
|
|
||||||
|
|
||||||
}.bind(bHash))
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInbox(){
|
|
||||||
if (! window.inboxActive){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var els = document.getElementsByClassName('threadEntry')
|
|
||||||
var showed = false
|
|
||||||
for(var i = 0; i < pms.length; i++) {
|
|
||||||
var add = true
|
|
||||||
if (pms[i].trim().length == 0){
|
|
||||||
noInbox.style.display = 'block'
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
threadPlaceholder.style.display = 'none'
|
|
||||||
showed = true
|
|
||||||
}
|
|
||||||
for (var x = 0; x < els.length; x++){
|
|
||||||
if (pms[i] === els[x].getAttribute('data-hash')){
|
|
||||||
add = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (add && window.inboxActive) {
|
|
||||||
loadInboxEntries(pms[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (! showed){
|
|
||||||
threadPlaceholder.style.display = 'block'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSentbox(){
|
|
||||||
fetch('/mail/getsentbox', {
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.json()) // Transform the data into json
|
|
||||||
.then(function(resp) {
|
|
||||||
var keys = [];
|
|
||||||
var entry = document.createElement('div')
|
|
||||||
for(var k in resp) keys.push(k);
|
|
||||||
if (keys.length == 0){
|
|
||||||
threadPart.innerHTML = "nothing to show here yet."
|
|
||||||
}
|
|
||||||
for (var i = keys.length - 1; i > -1; i--) (function(i, resp){
|
|
||||||
var entry = document.createElement('div')
|
|
||||||
var toLabel = document.createElement('span')
|
|
||||||
toLabel.innerText = 'To: '
|
|
||||||
var toEl = document.createElement('span')
|
|
||||||
toEl.classList.add('toElement')
|
|
||||||
var sentDate = document.createElement('span')
|
|
||||||
var humanDate = new Date(0)
|
|
||||||
humanDate.setUTCSeconds(resp[i]['date'])
|
|
||||||
humanDate = humanDate.toString()
|
|
||||||
var preview = document.createElement('span')
|
|
||||||
var deleteBtn = document.createElement('button')
|
|
||||||
var message = resp[i]['message']
|
|
||||||
deleteBtn.classList.add('deleteBtn', 'delete')
|
|
||||||
|
|
||||||
sentDate.innerText = humanDate.substring(0, humanDate.indexOf('('))
|
|
||||||
if (resp[i]['name'] == null || resp[i]['name'].toLowerCase() == 'anonymous'){
|
|
||||||
toEl.innerText = resp[i]['peer']
|
|
||||||
setHumanReadableValue(toEl, resp[i]['peer'])
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
toEl.innerText = resp[i]['name']
|
|
||||||
}
|
|
||||||
preview.innerText = '(' + resp[i]['subject'] + ')'
|
|
||||||
entry.classList.add('sentboxList')
|
|
||||||
entry.setAttribute('data-hash', resp[i]['hash'])
|
|
||||||
entry.appendChild(deleteBtn)
|
|
||||||
entry.appendChild(toLabel)
|
|
||||||
entry.appendChild(toEl)
|
|
||||||
entry.appendChild(preview)
|
|
||||||
entry.appendChild(sentDate)
|
|
||||||
|
|
||||||
threadPart.appendChild(entry)
|
|
||||||
|
|
||||||
entry.onclick = function(e){
|
|
||||||
if (e.target.classList.contains('deleteBtn')){
|
|
||||||
deleteMessage(e.target.parentNode.getAttribute('data-hash'))
|
|
||||||
e.target.parentNode.parentNode.removeChild(e.target.parentNode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
showSentboxWindow(toEl.innerText, message)
|
|
||||||
}
|
|
||||||
})(i, resp)
|
|
||||||
threadPart.appendChild(entry)
|
|
||||||
}.bind(threadPart))
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSentboxWindow(to, content){
|
|
||||||
content = stripEndZeroes(content)
|
|
||||||
document.getElementById('toID').value = to
|
|
||||||
document.getElementById('sentboxDisplayText').innerText = content
|
|
||||||
overlay('sentboxDisplay')
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshPms(callNext){
|
|
||||||
if (! window.inboxActive){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (document.hidden){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fetch('/mail/getinbox', {
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.text())
|
|
||||||
.then(function(data) {
|
|
||||||
pms = data.split(',')
|
|
||||||
if (pms.length > 0){
|
|
||||||
noInbox.style.display = 'none'
|
|
||||||
}
|
|
||||||
if (callNext){
|
|
||||||
getInbox()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
tabBtns.onclick = function(event){
|
|
||||||
var children = tabBtns.children[0].children
|
|
||||||
for (var i = 0; i < children.length; i++) {
|
|
||||||
var btn = children[i]
|
|
||||||
btn.classList.remove('is-active')
|
|
||||||
}
|
|
||||||
event.target.parentElement.parentElement.classList.add('is-active')
|
|
||||||
setActiveTab(event.target.innerText.toLowerCase())
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < document.getElementsByClassName('refresh').length; i++){
|
|
||||||
document.getElementsByClassName('refresh')[i].style.float = 'right'
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/friends/list', {
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.json()) // Transform the data into json
|
|
||||||
.then(function(resp) {
|
|
||||||
var friendSelectParent = document.getElementById('friendSelect')
|
|
||||||
var keys = [];
|
|
||||||
for(var k in resp) keys.push(k);
|
|
||||||
|
|
||||||
friendSelectParent.appendChild(document.createElement('option'))
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
var option = document.createElement("option")
|
|
||||||
var name = resp[keys[i]]['name'] || ""
|
|
||||||
option.value = keys[i]
|
|
||||||
if (name.length == 0){
|
|
||||||
option.text = keys[i]
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
option.text = name
|
|
||||||
}
|
|
||||||
friendSelectParent.appendChild(option)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setActiveTab('inbox')
|
|
||||||
|
|
||||||
setInterval(function(){mailPing()}, 10000)
|
|
||||||
mailPing()
|
|
||||||
window.inboxInterval = setInterval(function(){refreshPms(true)}, 3000)
|
|
||||||
refreshPms(true)
|
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", function() {
|
|
||||||
if (document.visibilityState === 'visible') {
|
|
||||||
refreshPms()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
let mailStream = function(){
|
|
||||||
var streamSource = new EventSourcePolyfill('/mail/streaminbox', {
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}
|
|
||||||
})
|
|
||||||
streamSource.onmessage = function(e){
|
|
||||||
console.debug(e.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mailStream()
|
|
||||||
*/
|
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
This file handles the mail interface
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var sendbutton = document.getElementById('sendMail')
|
|
||||||
messageContent = document.getElementById('draftText')
|
|
||||||
to = document.getElementById('draftID')
|
|
||||||
subject = document.getElementById('draftSubject')
|
|
||||||
friendPicker = document.getElementById('friendSelect')
|
|
||||||
|
|
||||||
function utf8Length(s) {
|
|
||||||
var size = encodeURIComponent(s).match(/%[89ABab]/g);
|
|
||||||
return s.length + (size ? size.length : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function padString(string_data, round_nearest_byte_exponent = 3){
|
|
||||||
if (utf8Length(string_data) === 0){
|
|
||||||
string_data += '0'
|
|
||||||
}
|
|
||||||
let round_size = 10 ** round_nearest_byte_exponent
|
|
||||||
while (utf8Length(string_data) % round_size > 0){
|
|
||||||
string_data += '0'
|
|
||||||
}
|
|
||||||
return string_data
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMail(toData, message, subject){
|
|
||||||
let meta = {'subject': subject}
|
|
||||||
|
|
||||||
if (document.getElementById('messagePaddingSetting').checked){
|
|
||||||
message = padString(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('mailSignatureSetting').value !== false){
|
|
||||||
message += "\n"
|
|
||||||
message += document.getElementById('mailSignatureSetting').value
|
|
||||||
}
|
|
||||||
|
|
||||||
postData = {'message': message, 'to': toData, 'type': 'pm', 'encrypt': true, 'meta': JSON.stringify(meta)}
|
|
||||||
postData.forward = document.getElementById('forwardSecrecySetting').checked
|
|
||||||
postData = JSON.stringify(postData)
|
|
||||||
sendForm.style.display = 'none'
|
|
||||||
|
|
||||||
fetch('/insertblock', {
|
|
||||||
method: 'POST',
|
|
||||||
body: postData,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}}).then(function(resp){
|
|
||||||
if (!resp.ok){
|
|
||||||
PNotify.error({
|
|
||||||
text: 'Malformed input'
|
|
||||||
})
|
|
||||||
sendForm.style.display = 'block'
|
|
||||||
throw new Error("Malformed input in sendmail")
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
})
|
|
||||||
.then((resp) => resp.text()) // Transform the data into text
|
|
||||||
.then(function(data) {
|
|
||||||
sendForm.style.display = 'block'
|
|
||||||
PNotify.success({
|
|
||||||
text: 'Queued for sending!',
|
|
||||||
delay: 3500,
|
|
||||||
mouseReset: false
|
|
||||||
})
|
|
||||||
to.value = subject.value = messageContent.value = ""
|
|
||||||
friendPicker.value = ""
|
|
||||||
subject.value = ""
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var friendPicker = document.getElementById('friendSelect')
|
|
||||||
friendPicker.onchange = function(){
|
|
||||||
to.value = friendPicker.value
|
|
||||||
}
|
|
||||||
|
|
||||||
sendForm.onsubmit = function(){
|
|
||||||
let getInstances = function(string, word) {
|
|
||||||
return string.split(word).length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(getInstances(to.value, '-') != 15){
|
|
||||||
if (to.value.length != 56 && to.value.length != 52){
|
|
||||||
PNotify.error({
|
|
||||||
text: 'User ID is not valid'
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendMail(to.value, messageContent.value, subject.value)
|
|
||||||
return false
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Load human readable public keys into a cache
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
function loadHumanReadableToCache(key){
|
|
||||||
fetch('/getHumanReadable/' + key, {
|
|
||||||
headers: {
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.text())
|
|
||||||
.then(function(resp) {
|
|
||||||
humanReadableCache[key] = resp
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function setHumanReadableValue(el, key){
|
|
||||||
if (typeof humanReadableCache[key] != 'undefined'){
|
|
||||||
el.innerText = humanReadableCache[key].split('-').slice(0,3).join('-')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
loadHumanReadableToCache(key)
|
|
||||||
setTimeout(function(){setHumanReadableValue(el, key)}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
/*
|
|
||||||
Onionr - Private P2P Communication
|
|
||||||
|
|
||||||
Handle mail settings
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
var notificationSetting = document.getElementById('notificationSetting')
|
|
||||||
var friendOnlyNotification = document.getElementById('strangersNotification')
|
|
||||||
var notificationSound = document.getElementById('notificationSound')
|
|
||||||
var sigSetting = document.getElementById('mailSignatureSetting')
|
|
||||||
|
|
||||||
document.getElementById('forwardSecrecySetting').onchange = function(e){
|
|
||||||
postData = JSON.stringify({"default_forward_secrecy": e.target.checked})
|
|
||||||
fetch('/config/set/mail', {
|
|
||||||
method: 'POST',
|
|
||||||
body: postData,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then((resp) => resp.text())
|
|
||||||
.then(function(data) {
|
|
||||||
mailSettings['forwardSecrecy'] = document.getElementById('forwardSecrecySetting').checked
|
|
||||||
PNotify.success({
|
|
||||||
text: 'Successfully toggled default forward secrecy'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationSound.onchange = function(e){
|
|
||||||
var postData = JSON.stringify({"notificationSound": e.target.checked})
|
|
||||||
fetch('/config/set/mail', {
|
|
||||||
method: 'POST',
|
|
||||||
body: postData,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then(function(data) {
|
|
||||||
mailSettings['notificationSound'] = notificationSound.checked
|
|
||||||
PNotify.success({
|
|
||||||
text: 'Successfully notification sound'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
friendOnlyNotification.onchange = function(e){
|
|
||||||
var postData = JSON.stringify({"strangersNotification": e.target.checked})
|
|
||||||
fetch('/config/set/mail', {
|
|
||||||
method: 'POST',
|
|
||||||
body: postData,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then(function(data) {
|
|
||||||
mailSettings['strangersNotification'] = friendOnlyNotification.checked
|
|
||||||
PNotify.success({
|
|
||||||
text: 'Successfully toggled notifications from strangers'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
notificationSetting.onchange = function(e){
|
|
||||||
var notificationSettings = document.getElementsByClassName('notificationSetting')
|
|
||||||
if (e.target.checked){
|
|
||||||
for (i = 0; i < notificationSettings.length; i++){
|
|
||||||
notificationSettings[i].style.display = "flex"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
for (i = 0; i < notificationSettings.length; i++){
|
|
||||||
notificationSettings[i].style.display = "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var postData = JSON.stringify({"notificationSetting": e.target.checked})
|
|
||||||
fetch('/config/set/mail', {
|
|
||||||
method: 'POST',
|
|
||||||
body: postData,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then(function(data) {
|
|
||||||
mailSettings['notificationSetting'] = notificationSetting.checked
|
|
||||||
PNotify.success({
|
|
||||||
text: 'Successfully toggled default mail notifications'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sigSetting.onchange = function(){
|
|
||||||
var postData = JSON.stringify({"signature": sigSetting.value})
|
|
||||||
fetch('/config/set/mail', {
|
|
||||||
method: 'POST',
|
|
||||||
body: postData,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"token": webpass
|
|
||||||
}})
|
|
||||||
.then(function(data) {
|
|
||||||
mailSettings['signature'] = sigSetting.value
|
|
||||||
PNotify.success({
|
|
||||||
text: 'Set mail signature'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user