diff --git a/docs/README.md b/docs/README.md index 6c5f44e5..ccbb1b4c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,6 @@ -# Onionr Documentation + + +# Documentation The Onionr [whitepaper](whitepaper.md) is the best place to start both for users and developers. @@ -16,4 +18,5 @@ The Onionr [whitepaper](whitepaper.md) is the best place to start both for users * [Technical overview](dev/overview.md) * [Project layout](dev/layout.md) * [Plugin development guide](dev/plugins.md) -* [Testing](dev/testing.md) \ No newline at end of file +* [Testing](dev/testing.md) +* [Auto generated API docs (HTML)](html/onionr/index.html) \ No newline at end of file diff --git a/docs/html/index.html b/docs/html/index.html new file mode 100644 index 00000000..f739a8aa --- /dev/null +++ b/docs/html/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/html/onionr/apiservers/index.html b/docs/html/onionr/apiservers/index.html new file mode 100644 index 00000000..43bcd455 --- /dev/null +++ b/docs/html/onionr/apiservers/index.html @@ -0,0 +1,76 @@ + + + + + + +onionr.apiservers API documentation + + + + + + + + + +
+
+
+

Module onionr.apiservers

+
+
+
+Source code +
from . import public, private
+PublicAPI = public.PublicAPI
+ClientAPI = private.PrivateAPI
+
+
+
+

Sub-modules

+
+
onionr.apiservers.private
+
+

Onionr - Private P2P Communication …

+
+
onionr.apiservers.public
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/apiservers/private/index.html b/docs/html/onionr/apiservers/private/index.html new file mode 100644 index 00000000..5f157ef0 --- /dev/null +++ b/docs/html/onionr/apiservers/private/index.html @@ -0,0 +1,347 @@ + + + + + + +onionr.apiservers.private API documentation + + + + + + + + + +
+
+
+

Module onionr.apiservers.private

+
+
+

Onionr - Private P2P Communication

+

This file handles all incoming http requests to the client, using Flask

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file handles all incoming http requests to the client, using Flask
+'''
+'''
+    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/>.
+'''
+import base64, os, time
+import flask
+from gevent.pywsgi import WSGIServer
+from onionrutils import epoch
+import httpapi, filepaths, logger
+from . import register_private_blueprints
+from etc import waitforsetvar
+import serializeddata, config
+from .. import public
+class PrivateAPI:
+    '''
+        Client HTTP api
+    '''
+
+    callbacks = {'public' : {}, 'private' : {}}
+
+    def __init__(self):
+        '''
+            Initialize the api server, preping variables for later use
+
+            This initialization defines all of the API entry points and handlers for the endpoints and errors
+            This also saves the used host (random localhost IP address) to the data folder in host.txt
+        '''
+        self.config = config
+        self.startTime = epoch.get_epoch()
+        app = flask.Flask(__name__)
+        bindPort = int(config.get('client.client.port', 59496))
+        self.bindPort = bindPort
+
+        self.clientToken = config.get('client.webpassword')
+        self.timeBypassToken = base64.b16encode(os.urandom(32)).decode()
+
+        self.host = httpapi.apiutils.setbindip.set_bind_IP(filepaths.private_API_host_file)
+        logger.info('Running api on %s:%s' % (self.host, self.bindPort))
+        self.httpServer = ''
+
+        self.queueResponse = {}
+        self.get_block_data = httpapi.apiutils.GetBlockData(self)
+        register_private_blueprints.register_private_blueprints(self, app)
+        httpapi.load_plugin_blueprints(app)
+        self.app = app
+    
+    def start(self):
+        waitforsetvar.wait_for_set_var(self, "_too_many")
+        self.publicAPI = self._too_many.get(public.PublicAPI)
+        self.httpServer = WSGIServer((self.host, self.bindPort), self.app, log=None, handler_class=httpapi.fdsafehandler.FDSafeHandler)
+        self.httpServer.serve_forever()
+
+    def setPublicAPIInstance(self, inst):
+        self.publicAPI = inst
+
+    def validateToken(self, token):
+        '''
+            Validate that the client token matches the given token. Used to prevent CSRF and data exfiltration
+        '''
+        if not self.clientToken:
+            logger.error("client password needs to be set")
+            return False
+        try:
+            if not hmac.compare_digest(self.clientToken, token):
+                return False
+            else:
+                return True
+        except TypeError:
+            return False
+
+    def getUptime(self):
+        while True:
+            try:
+                return epoch.get_epoch() - self.startTime
+            except (AttributeError, NameError):
+                # Don't error on race condition with startup
+                pass
+
+    def getBlockData(self, bHash, decrypt=False, raw=False, headerOnly=False):
+        return self.get_block_data.get_block_data(bHash, decrypt=decrypt, raw=raw, headerOnly=headerOnly)
+
+
+
+

Sub-modules

+
+
onionr.apiservers.private.register_private_blueprints
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+

Classes

+
+
+class PrivateAPI +
+
+

Client HTTP api

+

Initialize the api server, preping variables for later use

+

This initialization defines all of the API entry points and handlers for the endpoints and errors +This also saves the used host (random localhost IP address) to the data folder in host.txt

+
+Source code +
class PrivateAPI:
+    '''
+        Client HTTP api
+    '''
+
+    callbacks = {'public' : {}, 'private' : {}}
+
+    def __init__(self):
+        '''
+            Initialize the api server, preping variables for later use
+
+            This initialization defines all of the API entry points and handlers for the endpoints and errors
+            This also saves the used host (random localhost IP address) to the data folder in host.txt
+        '''
+        self.config = config
+        self.startTime = epoch.get_epoch()
+        app = flask.Flask(__name__)
+        bindPort = int(config.get('client.client.port', 59496))
+        self.bindPort = bindPort
+
+        self.clientToken = config.get('client.webpassword')
+        self.timeBypassToken = base64.b16encode(os.urandom(32)).decode()
+
+        self.host = httpapi.apiutils.setbindip.set_bind_IP(filepaths.private_API_host_file)
+        logger.info('Running api on %s:%s' % (self.host, self.bindPort))
+        self.httpServer = ''
+
+        self.queueResponse = {}
+        self.get_block_data = httpapi.apiutils.GetBlockData(self)
+        register_private_blueprints.register_private_blueprints(self, app)
+        httpapi.load_plugin_blueprints(app)
+        self.app = app
+    
+    def start(self):
+        waitforsetvar.wait_for_set_var(self, "_too_many")
+        self.publicAPI = self._too_many.get(public.PublicAPI)
+        self.httpServer = WSGIServer((self.host, self.bindPort), self.app, log=None, handler_class=httpapi.fdsafehandler.FDSafeHandler)
+        self.httpServer.serve_forever()
+
+    def setPublicAPIInstance(self, inst):
+        self.publicAPI = inst
+
+    def validateToken(self, token):
+        '''
+            Validate that the client token matches the given token. Used to prevent CSRF and data exfiltration
+        '''
+        if not self.clientToken:
+            logger.error("client password needs to be set")
+            return False
+        try:
+            if not hmac.compare_digest(self.clientToken, token):
+                return False
+            else:
+                return True
+        except TypeError:
+            return False
+
+    def getUptime(self):
+        while True:
+            try:
+                return epoch.get_epoch() - self.startTime
+            except (AttributeError, NameError):
+                # Don't error on race condition with startup
+                pass
+
+    def getBlockData(self, bHash, decrypt=False, raw=False, headerOnly=False):
+        return self.get_block_data.get_block_data(bHash, decrypt=decrypt, raw=raw, headerOnly=headerOnly)
+
+

Class variables

+
+
var callbacks
+
+
+
+
+

Methods

+
+
+def getBlockData(self, bHash, decrypt=False, raw=False, headerOnly=False) +
+
+
+
+Source code +
def getBlockData(self, bHash, decrypt=False, raw=False, headerOnly=False):
+    return self.get_block_data.get_block_data(bHash, decrypt=decrypt, raw=raw, headerOnly=headerOnly)
+
+
+
+def getUptime(self) +
+
+
+
+Source code +
def getUptime(self):
+    while True:
+        try:
+            return epoch.get_epoch() - self.startTime
+        except (AttributeError, NameError):
+            # Don't error on race condition with startup
+            pass
+
+
+
+def setPublicAPIInstance(self, inst) +
+
+
+
+Source code +
def setPublicAPIInstance(self, inst):
+    self.publicAPI = inst
+
+
+
+def start(self) +
+
+
+
+Source code +
def start(self):
+    waitforsetvar.wait_for_set_var(self, "_too_many")
+    self.publicAPI = self._too_many.get(public.PublicAPI)
+    self.httpServer = WSGIServer((self.host, self.bindPort), self.app, log=None, handler_class=httpapi.fdsafehandler.FDSafeHandler)
+    self.httpServer.serve_forever()
+
+
+
+def validateToken(self, token) +
+
+

Validate that the client token matches the given token. Used to prevent CSRF and data exfiltration

+
+Source code +
def validateToken(self, token):
+    '''
+        Validate that the client token matches the given token. Used to prevent CSRF and data exfiltration
+    '''
+    if not self.clientToken:
+        logger.error("client password needs to be set")
+        return False
+    try:
+        if not hmac.compare_digest(self.clientToken, token):
+            return False
+        else:
+            return True
+    except TypeError:
+        return False
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/apiservers/private/register_private_blueprints.html b/docs/html/onionr/apiservers/private/register_private_blueprints.html new file mode 100644 index 00000000..fc08e473 --- /dev/null +++ b/docs/html/onionr/apiservers/private/register_private_blueprints.html @@ -0,0 +1,123 @@ + + + + + + +onionr.apiservers.private.register_private_blueprints API documentation + + + + + + + + + +
+
+
+

Module onionr.apiservers.private.register_private_blueprints

+
+
+

Onionr - Private P2P Communication

+

This file registers blueprints for the private api server

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file registers blueprints for the private api server
+'''
+'''
+    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/>.
+'''
+import os
+from httpapi import security, friendsapi, profilesapi, configapi, insertblock, miscclientapi, onionrsitesapi, apiutils
+from httpapi import directconnections
+def register_private_blueprints(private_api, app):
+    app.register_blueprint(security.client.ClientAPISecurity(private_api).client_api_security_bp)
+    app.register_blueprint(friendsapi.friends)
+    app.register_blueprint(profilesapi.profile_BP)
+    app.register_blueprint(configapi.config_BP)
+    app.register_blueprint(insertblock.ib)
+    app.register_blueprint(miscclientapi.getblocks.client_get_blocks)
+    app.register_blueprint(miscclientapi.endpoints.PrivateEndpoints(private_api).private_endpoints_bp)
+    app.register_blueprint(onionrsitesapi.site_api)
+    app.register_blueprint(apiutils.shutdown.shutdown_bp)
+    app.register_blueprint(miscclientapi.staticfiles.static_files_bp)
+    app.register_blueprint(directconnections.DirectConnectionManagement(private_api).direct_conn_management_bp)
+    return app
+
+
+
+
+
+
+
+

Functions

+
+
+def register_private_blueprints(private_api, app) +
+
+
+
+Source code +
def register_private_blueprints(private_api, app):
+    app.register_blueprint(security.client.ClientAPISecurity(private_api).client_api_security_bp)
+    app.register_blueprint(friendsapi.friends)
+    app.register_blueprint(profilesapi.profile_BP)
+    app.register_blueprint(configapi.config_BP)
+    app.register_blueprint(insertblock.ib)
+    app.register_blueprint(miscclientapi.getblocks.client_get_blocks)
+    app.register_blueprint(miscclientapi.endpoints.PrivateEndpoints(private_api).private_endpoints_bp)
+    app.register_blueprint(onionrsitesapi.site_api)
+    app.register_blueprint(apiutils.shutdown.shutdown_bp)
+    app.register_blueprint(miscclientapi.staticfiles.static_files_bp)
+    app.register_blueprint(directconnections.DirectConnectionManagement(private_api).direct_conn_management_bp)
+    return app
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/apiservers/public/index.html b/docs/html/onionr/apiservers/public/index.html new file mode 100644 index 00000000..8148baf4 --- /dev/null +++ b/docs/html/onionr/apiservers/public/index.html @@ -0,0 +1,189 @@ + + + + + + +onionr.apiservers.public API documentation + + + + + + + + + +
+
+
+

Module onionr.apiservers.public

+
+
+

Onionr - Private P2P Communication

+

This file handles all incoming http requests to the public api server, using Flask

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file handles all incoming http requests to the public api server, using Flask
+'''
+'''
+    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/>.
+'''
+import time
+import threading
+import flask
+from gevent.pywsgi import WSGIServer
+from httpapi import apiutils, security, fdsafehandler, miscpublicapi
+import logger, config, filepaths
+from utils import gettransports
+from etc import onionrvalues, waitforsetvar
+
+def _get_tor_adder(pub_api):
+    transports = []
+    while len(transports) == 0:
+        transports = gettransports.get()
+        time.sleep(0.3)
+    pub_api.torAdder = transports[0]
+
+class PublicAPI:
+    '''
+        The new client api server, isolated from the public api
+    '''
+    def __init__(self):
+        app = flask.Flask('PublicAPI')
+        app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
+        self.i2pEnabled = config.get('i2p.host', False)
+        self.hideBlocks = [] # Blocks to be denied sharing
+        self.host = apiutils.setbindip.set_bind_IP(filepaths.public_API_host_file)
+
+        threading.Thread(target=_get_tor_adder, args=[self], daemon=True).start()
+
+        self.torAdder = ""
+        self.bindPort = config.get('client.public.port')
+        self.lastRequest = 0
+        self.hitCount = 0 # total rec requests to public api since server started
+        self.config = config
+        self.API_VERSION = onionrvalues.API_VERSION
+        logger.info('Running public api on %s:%s' % (self.host, self.bindPort))
+
+        app.register_blueprint(security.public.PublicAPISecurity(self).public_api_security_bp)
+        app.register_blueprint(miscpublicapi.endpoints.PublicEndpoints(self).public_endpoints_bp)
+        self.app = app
+
+    def start(self):
+        waitforsetvar.wait_for_set_var(self, "_too_many")
+        self.httpServer = WSGIServer((self.host, self.bindPort), self.app, log=None, handler_class=fdsafehandler.FDSafeHandler)
+        self.httpServer.serve_forever()
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class PublicAPI +
+
+

The new client api server, isolated from the public api

+
+Source code +
class PublicAPI:
+    '''
+        The new client api server, isolated from the public api
+    '''
+    def __init__(self):
+        app = flask.Flask('PublicAPI')
+        app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
+        self.i2pEnabled = config.get('i2p.host', False)
+        self.hideBlocks = [] # Blocks to be denied sharing
+        self.host = apiutils.setbindip.set_bind_IP(filepaths.public_API_host_file)
+
+        threading.Thread(target=_get_tor_adder, args=[self], daemon=True).start()
+
+        self.torAdder = ""
+        self.bindPort = config.get('client.public.port')
+        self.lastRequest = 0
+        self.hitCount = 0 # total rec requests to public api since server started
+        self.config = config
+        self.API_VERSION = onionrvalues.API_VERSION
+        logger.info('Running public api on %s:%s' % (self.host, self.bindPort))
+
+        app.register_blueprint(security.public.PublicAPISecurity(self).public_api_security_bp)
+        app.register_blueprint(miscpublicapi.endpoints.PublicEndpoints(self).public_endpoints_bp)
+        self.app = app
+
+    def start(self):
+        waitforsetvar.wait_for_set_var(self, "_too_many")
+        self.httpServer = WSGIServer((self.host, self.bindPort), self.app, log=None, handler_class=fdsafehandler.FDSafeHandler)
+        self.httpServer.serve_forever()
+
+

Methods

+
+
+def start(self) +
+
+
+
+Source code +
def start(self):
+    waitforsetvar.wait_for_set_var(self, "_too_many")
+    self.httpServer = WSGIServer((self.host, self.bindPort), self.app, log=None, handler_class=fdsafehandler.FDSafeHandler)
+    self.httpServer.serve_forever()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/bootstrappeers.html b/docs/html/onionr/communicator/bootstrappeers.html new file mode 100644 index 00000000..de2d8d74 --- /dev/null +++ b/docs/html/onionr/communicator/bootstrappeers.html @@ -0,0 +1,113 @@ + + + + + + +onionr.communicator.bootstrappeers API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator.bootstrappeers

+
+
+

Onionr - Private P2P Communication

+

add bootstrap peers to the communicator peer list

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    add bootstrap peers to the communicator peer list
+'''
+'''
+    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 utils import readstatic, gettransports
+from coredb import keydb
+bootstrap_peers = readstatic.read_static('bootstrap-nodes.txt').split(',')
+def add_bootstrap_list_to_peer_list(comm_inst, peerList):
+    '''
+        Add the bootstrap list to the peer list (no duplicates)
+    '''
+    for i in bootstrap_peers:
+        if i not in peerList and i not in comm_inst.offlinePeers and not i in gettransports.get() and len(str(i).strip()) > 0:
+            peerList.append(i)
+            keydb.addkeys.add_address(i)
+
+
+
+
+
+
+
+

Functions

+
+
+def add_bootstrap_list_to_peer_list(comm_inst, peerList) +
+
+

Add the bootstrap list to the peer list (no duplicates)

+
+Source code +
def add_bootstrap_list_to_peer_list(comm_inst, peerList):
+    '''
+        Add the bootstrap list to the peer list (no duplicates)
+    '''
+    for i in bootstrap_peers:
+        if i not in peerList and i not in comm_inst.offlinePeers and not i in gettransports.get() and len(str(i).strip()) > 0:
+            peerList.append(i)
+            keydb.addkeys.add_address(i)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/index.html b/docs/html/onionr/communicator/index.html new file mode 100644 index 00000000..fd2c8ad7 --- /dev/null +++ b/docs/html/onionr/communicator/index.html @@ -0,0 +1,743 @@ + + + + + + +onionr.communicator API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator

+
+
+

Onionr - Private P2P Communication

+

This file contains both the OnionrCommunicate class for communcating with peers +and code to operate as a daemon, getting commands from the command queue database (see core.Core.daemonQueue)

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file contains both the OnionrCommunicate class for communcating with peers
+    and code to operate as a daemon, getting commands from the command queue database (see core.Core.daemonQueue)
+'''
+'''
+    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/>.
+'''
+import sys, os, time
+import config, logger
+import onionrexceptions, onionrpeers
+from onionrblocks import onionrblockapi as block
+from onionrplugins import onionrevents as events
+import onionrplugins as plugins
+from . import onlinepeers, uploadqueue
+from communicatorutils import servicecreator, onionrcommunicatortimers
+from communicatorutils import downloadblocks, lookupblocks, lookupadders
+from communicatorutils import servicecreator, connectnewpeers
+from communicatorutils import uploadblocks
+from communicatorutils import daemonqueuehandler, announcenode, deniableinserts
+from communicatorutils import cooldownpeer, housekeeping, netcheck
+from onionrutils import localcommand, epoch
+from etc import humanreadabletime
+import onionrservices, filepaths
+from onionrblocks import storagecounter
+from coredb import daemonqueue, dbfiles
+from utils import gettransports
+from netcontroller import NetController
+OnionrCommunicatorTimers = onionrcommunicatortimers.OnionrCommunicatorTimers
+
+config.reload()
+class OnionrCommunicatorDaemon:
+    def __init__(self, shared_state, developmentMode=config.get('general.dev_mode', False)):
+        # configure logger and stuff
+        self.config = config
+        self.storage_counter = storagecounter.StorageCounter()
+        self.isOnline = True # Assume we're connected to the internet
+        self.shared_state = shared_state # TooManyObjects module
+
+        # list of timer instances
+        self.timers = []
+
+        # initialize core with Tor socks port being 3rd argument
+        self.proxyPort = shared_state.get(NetController).socksPort
+
+        # Upload information, list of blocks to upload
+        self.blocksToUpload = []
+        self.upload_session_manager = self.shared_state.get(uploadblocks.sessionmanager.BlockUploadSessionManager)
+        self.shared_state.share_object()
+
+        # loop time.sleep delay in seconds
+        self.delay = 1
+
+        # lists of connected peers and peers we know we can't reach currently
+        self.onlinePeers = []
+        self.offlinePeers = []
+        self.cooldownPeer = {}
+        self.connectTimes = {}
+        self.peerProfiles = [] # list of peer's profiles (onionrpeers.PeerProfile instances)
+        self.newPeers = [] # Peers merged to us. Don't add to db until we know they're reachable
+        self.announceProgress = {}
+        self.announceCache = {}
+
+        self.generating_blocks = []
+
+        # amount of threads running by name, used to prevent too many
+        self.threadCounts = {}
+
+        # set true when shutdown command received
+        self.shutdown = False
+
+        # list of new blocks to download, added to when new block lists are fetched from peers
+        self.blockQueue = {}
+
+        # list of blocks currently downloading, avoid s
+        self.currentDownloading = []
+
+        # timestamp when the last online node was seen
+        self.lastNodeSeen = None
+
+        # Dict of time stamps for peer's block list lookup times, to avoid downloading full lists all the time
+        self.dbTimestamps = {}
+
+        # Clear the daemon queue for any dead messages
+        if os.path.exists(dbfiles.daemon_queue_db):
+            daemonqueue.clear_daemon_queue()
+
+        # Loads in and starts the enabled plugins
+        plugins.reload()
+
+        # time app started running for info/statistics purposes
+        self.startTime = epoch.get_epoch()
+
+        uploadqueue.UploadQueue(self) # extends our upload list and saves our list when Onionr exits
+
+        if developmentMode:
+            OnionrCommunicatorTimers(self, self.heartbeat, 30)
+
+        # Set timers, function reference, seconds
+        # requires_peer True means the timer function won't fire if we have no connected peers
+        peerPoolTimer = OnionrCommunicatorTimers(self, onlinepeers.get_online_peers, 60, max_threads=1, my_args=[self])
+        OnionrCommunicatorTimers(self, self.runCheck, 2, max_threads=1)
+
+        # Timers to periodically lookup new blocks and download them
+        lookup_blocks_timer = OnionrCommunicatorTimers(self, lookupblocks.lookup_blocks_from_communicator, config.get('timers.lookupBlocks', 25), my_args=[self], requires_peer=True, max_threads=1)
+        # The block download timer is accessed by the block lookup function to trigger faster download starts
+        self.download_blocks_timer = OnionrCommunicatorTimers(self, self.getBlocks, config.get('timers.getBlocks', 10), requires_peer=True, max_threads=5)
+
+        # Timer to reset the longest offline peer so contact can be attempted again
+        OnionrCommunicatorTimers(self, onlinepeers.clear_offline_peer, 58, my_args=[self])
+
+        # Timer to cleanup old blocks
+        blockCleanupTimer = OnionrCommunicatorTimers(self, housekeeping.clean_old_blocks, 20, my_args=[self])
+
+        # Timer to discover new peers
+        OnionrCommunicatorTimers(self, lookupadders.lookup_new_peer_transports_with_communicator, 60, requires_peer=True, my_args=[self], max_threads=2)
+
+        # Timer for adjusting which peers we actively communicate to at any given time, to avoid over-using peers
+        OnionrCommunicatorTimers(self, cooldownpeer.cooldown_peer, 30, my_args=[self], requires_peer=True)
+
+        # Timer to read the upload queue and upload the entries to peers
+        OnionrCommunicatorTimers(self, uploadblocks.upload_blocks_from_communicator, 5, my_args=[self], requires_peer=True, max_threads=1)
+
+        # Timer to process the daemon command queue
+        OnionrCommunicatorTimers(self, daemonqueuehandler.handle_daemon_commands, 6, my_args=[self], max_threads=3)
+
+        # Setup direct connections
+        if config.get('general.socket_servers', False):
+            self.services = onionrservices.OnionrServices()
+            self.active_services = []
+            self.service_greenlets = []
+            OnionrCommunicatorTimers(self, servicecreator.service_creator, 5, max_threads=50, my_args=[self])
+        else:
+            self.services = None
+        
+        # {peer_pubkey: ephemeral_address}, the address to reach them
+        self.direct_connection_clients = {}
+        
+        # This timer creates deniable blocks, in an attempt to further obfuscate block insertion metadata
+        if config.get('general.insert_deniable_blocks', True):
+            deniableBlockTimer = OnionrCommunicatorTimers(self, deniableinserts.insert_deniable_block, 180, my_args=[self], requires_peer=True, max_threads=1)
+            deniableBlockTimer.count = (deniableBlockTimer.frequency - 175)
+
+        # Timer to check for connectivity, through Tor to various high-profile onion services
+        netCheckTimer = OnionrCommunicatorTimers(self, netcheck.net_check, 500, my_args=[self], max_threads=1)
+
+        # Announce the public API server transport address to other nodes if security level allows
+        if config.get('general.security_level', 1) == 0 and config.get('general.announce_node', True):
+            # Default to high security level incase config breaks
+            announceTimer = OnionrCommunicatorTimers(self, announcenode.announce_node, 3600, my_args=[self], requires_peer=True, max_threads=1)
+            announceTimer.count = (announceTimer.frequency - 120)
+        else:
+            logger.debug('Will not announce node.')
+        
+        # Timer to delete malfunctioning or long-dead peers
+        cleanupTimer = OnionrCommunicatorTimers(self, self.peerCleanup, 300, requires_peer=True)
+
+        # Timer to cleanup dead ephemeral forward secrecy keys 
+        forwardSecrecyTimer = OnionrCommunicatorTimers(self, housekeeping.clean_keys, 15, my_args=[self], max_threads=1)
+
+        # Adjust initial timer triggers
+        peerPoolTimer.count = (peerPoolTimer.frequency - 1)
+        cleanupTimer.count = (cleanupTimer.frequency - 60)
+        blockCleanupTimer.count = (blockCleanupTimer.frequency - 2)
+        lookup_blocks_timer = (lookup_blocks_timer.frequency - 2)
+
+        shared_state.add(self)
+
+        # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking
+        try:
+            while not self.shutdown:
+                for i in self.timers:
+                    if self.shutdown:
+                        break
+                    i.processTimer()
+                time.sleep(self.delay)
+                # Debug to print out used FDs (regular and net)
+                #proc = psutil.Process()
+                #print(proc.open_files(), len(psutil.net_connections()))
+        except KeyboardInterrupt:
+            self.shutdown = True
+            pass
+
+        logger.info('Goodbye. (Onionr is cleaning up, and will exit)', terminal=True)
+        try:
+            self.service_greenlets
+        except AttributeError:
+            pass
+        else:
+            for server in self.service_greenlets:
+                server.stop()
+        localcommand.local_command('shutdown') # shutdown the api
+        try:
+            time.sleep(0.5)
+        except KeyboardInterrupt:
+            pass
+
+    def getBlocks(self):
+        '''download new blocks in queue'''
+        downloadblocks.download_blocks_from_communicator(self)
+
+    def decrementThreadCount(self, threadName):
+        '''Decrement amount of a thread name if more than zero, called when a function meant to be run in a thread ends'''
+        try:
+            if self.threadCounts[threadName] > 0:
+                self.threadCounts[threadName] -= 1
+        except KeyError:
+            pass
+
+    def connectNewPeer(self, peer='', useBootstrap=False):
+        '''Adds a new random online peer to self.onlinePeers'''
+        connectnewpeers.connect_new_peer_to_communicator(self, peer, useBootstrap)
+
+    def peerCleanup(self):
+        '''This just calls onionrpeers.cleanupPeers, which removes dead or bad peers (offline too long, too slow)'''
+        onionrpeers.peer_cleanup()
+        self.decrementThreadCount('peerCleanup')
+
+    def getPeerProfileInstance(self, peer):
+        '''Gets a peer profile instance from the list of profiles, by address name'''
+        for i in self.peerProfiles:
+            # if the peer's profile is already loaded, return that
+            if i.address == peer:
+                retData = i
+                break
+        else:
+            # if the peer's profile is not loaded, return a new one. connectNewPeer also adds it to the list on connect
+            retData = onionrpeers.PeerProfiles(peer)
+            self.peerProfiles.append(retData)
+        return retData
+
+    def getUptime(self):
+        return epoch.get_epoch() - self.startTime
+
+    def heartbeat(self):
+        '''Show a heartbeat debug message'''
+        logger.debug('Heartbeat. Node running for %s.' % humanreadabletime.human_readable_time(self.getUptime()))
+        self.decrementThreadCount('heartbeat')
+
+    def runCheck(self):
+        if run_file_exists(self):
+            logger.debug('Status check; looks good.')
+
+        self.decrementThreadCount('runCheck')
+
+def startCommunicator(shared_state):
+    OnionrCommunicatorDaemon(shared_state)
+
+def run_file_exists(daemon):
+    if os.path.isfile(filepaths.run_check_file):
+        os.remove(filepaths.run_check_file)
+        return True
+    return False
+
+
+
+

Sub-modules

+
+
onionr.communicator.bootstrappeers
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicator.onlinepeers
+
+
+
+
onionr.communicator.peeraction
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicator.uploadqueue
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+

Functions

+
+
+def run_file_exists(daemon) +
+
+
+
+Source code +
def run_file_exists(daemon):
+    if os.path.isfile(filepaths.run_check_file):
+        os.remove(filepaths.run_check_file)
+        return True
+    return False
+
+
+
+def startCommunicator(shared_state) +
+
+
+
+Source code +
def startCommunicator(shared_state):
+    OnionrCommunicatorDaemon(shared_state)
+
+
+
+
+
+

Classes

+
+
+class OnionrCommunicatorDaemon +(shared_state, developmentMode=True) +
+
+
+
+Source code +
class OnionrCommunicatorDaemon:
+    def __init__(self, shared_state, developmentMode=config.get('general.dev_mode', False)):
+        # configure logger and stuff
+        self.config = config
+        self.storage_counter = storagecounter.StorageCounter()
+        self.isOnline = True # Assume we're connected to the internet
+        self.shared_state = shared_state # TooManyObjects module
+
+        # list of timer instances
+        self.timers = []
+
+        # initialize core with Tor socks port being 3rd argument
+        self.proxyPort = shared_state.get(NetController).socksPort
+
+        # Upload information, list of blocks to upload
+        self.blocksToUpload = []
+        self.upload_session_manager = self.shared_state.get(uploadblocks.sessionmanager.BlockUploadSessionManager)
+        self.shared_state.share_object()
+
+        # loop time.sleep delay in seconds
+        self.delay = 1
+
+        # lists of connected peers and peers we know we can't reach currently
+        self.onlinePeers = []
+        self.offlinePeers = []
+        self.cooldownPeer = {}
+        self.connectTimes = {}
+        self.peerProfiles = [] # list of peer's profiles (onionrpeers.PeerProfile instances)
+        self.newPeers = [] # Peers merged to us. Don't add to db until we know they're reachable
+        self.announceProgress = {}
+        self.announceCache = {}
+
+        self.generating_blocks = []
+
+        # amount of threads running by name, used to prevent too many
+        self.threadCounts = {}
+
+        # set true when shutdown command received
+        self.shutdown = False
+
+        # list of new blocks to download, added to when new block lists are fetched from peers
+        self.blockQueue = {}
+
+        # list of blocks currently downloading, avoid s
+        self.currentDownloading = []
+
+        # timestamp when the last online node was seen
+        self.lastNodeSeen = None
+
+        # Dict of time stamps for peer's block list lookup times, to avoid downloading full lists all the time
+        self.dbTimestamps = {}
+
+        # Clear the daemon queue for any dead messages
+        if os.path.exists(dbfiles.daemon_queue_db):
+            daemonqueue.clear_daemon_queue()
+
+        # Loads in and starts the enabled plugins
+        plugins.reload()
+
+        # time app started running for info/statistics purposes
+        self.startTime = epoch.get_epoch()
+
+        uploadqueue.UploadQueue(self) # extends our upload list and saves our list when Onionr exits
+
+        if developmentMode:
+            OnionrCommunicatorTimers(self, self.heartbeat, 30)
+
+        # Set timers, function reference, seconds
+        # requires_peer True means the timer function won't fire if we have no connected peers
+        peerPoolTimer = OnionrCommunicatorTimers(self, onlinepeers.get_online_peers, 60, max_threads=1, my_args=[self])
+        OnionrCommunicatorTimers(self, self.runCheck, 2, max_threads=1)
+
+        # Timers to periodically lookup new blocks and download them
+        lookup_blocks_timer = OnionrCommunicatorTimers(self, lookupblocks.lookup_blocks_from_communicator, config.get('timers.lookupBlocks', 25), my_args=[self], requires_peer=True, max_threads=1)
+        # The block download timer is accessed by the block lookup function to trigger faster download starts
+        self.download_blocks_timer = OnionrCommunicatorTimers(self, self.getBlocks, config.get('timers.getBlocks', 10), requires_peer=True, max_threads=5)
+
+        # Timer to reset the longest offline peer so contact can be attempted again
+        OnionrCommunicatorTimers(self, onlinepeers.clear_offline_peer, 58, my_args=[self])
+
+        # Timer to cleanup old blocks
+        blockCleanupTimer = OnionrCommunicatorTimers(self, housekeeping.clean_old_blocks, 20, my_args=[self])
+
+        # Timer to discover new peers
+        OnionrCommunicatorTimers(self, lookupadders.lookup_new_peer_transports_with_communicator, 60, requires_peer=True, my_args=[self], max_threads=2)
+
+        # Timer for adjusting which peers we actively communicate to at any given time, to avoid over-using peers
+        OnionrCommunicatorTimers(self, cooldownpeer.cooldown_peer, 30, my_args=[self], requires_peer=True)
+
+        # Timer to read the upload queue and upload the entries to peers
+        OnionrCommunicatorTimers(self, uploadblocks.upload_blocks_from_communicator, 5, my_args=[self], requires_peer=True, max_threads=1)
+
+        # Timer to process the daemon command queue
+        OnionrCommunicatorTimers(self, daemonqueuehandler.handle_daemon_commands, 6, my_args=[self], max_threads=3)
+
+        # Setup direct connections
+        if config.get('general.socket_servers', False):
+            self.services = onionrservices.OnionrServices()
+            self.active_services = []
+            self.service_greenlets = []
+            OnionrCommunicatorTimers(self, servicecreator.service_creator, 5, max_threads=50, my_args=[self])
+        else:
+            self.services = None
+        
+        # {peer_pubkey: ephemeral_address}, the address to reach them
+        self.direct_connection_clients = {}
+        
+        # This timer creates deniable blocks, in an attempt to further obfuscate block insertion metadata
+        if config.get('general.insert_deniable_blocks', True):
+            deniableBlockTimer = OnionrCommunicatorTimers(self, deniableinserts.insert_deniable_block, 180, my_args=[self], requires_peer=True, max_threads=1)
+            deniableBlockTimer.count = (deniableBlockTimer.frequency - 175)
+
+        # Timer to check for connectivity, through Tor to various high-profile onion services
+        netCheckTimer = OnionrCommunicatorTimers(self, netcheck.net_check, 500, my_args=[self], max_threads=1)
+
+        # Announce the public API server transport address to other nodes if security level allows
+        if config.get('general.security_level', 1) == 0 and config.get('general.announce_node', True):
+            # Default to high security level incase config breaks
+            announceTimer = OnionrCommunicatorTimers(self, announcenode.announce_node, 3600, my_args=[self], requires_peer=True, max_threads=1)
+            announceTimer.count = (announceTimer.frequency - 120)
+        else:
+            logger.debug('Will not announce node.')
+        
+        # Timer to delete malfunctioning or long-dead peers
+        cleanupTimer = OnionrCommunicatorTimers(self, self.peerCleanup, 300, requires_peer=True)
+
+        # Timer to cleanup dead ephemeral forward secrecy keys 
+        forwardSecrecyTimer = OnionrCommunicatorTimers(self, housekeeping.clean_keys, 15, my_args=[self], max_threads=1)
+
+        # Adjust initial timer triggers
+        peerPoolTimer.count = (peerPoolTimer.frequency - 1)
+        cleanupTimer.count = (cleanupTimer.frequency - 60)
+        blockCleanupTimer.count = (blockCleanupTimer.frequency - 2)
+        lookup_blocks_timer = (lookup_blocks_timer.frequency - 2)
+
+        shared_state.add(self)
+
+        # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking
+        try:
+            while not self.shutdown:
+                for i in self.timers:
+                    if self.shutdown:
+                        break
+                    i.processTimer()
+                time.sleep(self.delay)
+                # Debug to print out used FDs (regular and net)
+                #proc = psutil.Process()
+                #print(proc.open_files(), len(psutil.net_connections()))
+        except KeyboardInterrupt:
+            self.shutdown = True
+            pass
+
+        logger.info('Goodbye. (Onionr is cleaning up, and will exit)', terminal=True)
+        try:
+            self.service_greenlets
+        except AttributeError:
+            pass
+        else:
+            for server in self.service_greenlets:
+                server.stop()
+        localcommand.local_command('shutdown') # shutdown the api
+        try:
+            time.sleep(0.5)
+        except KeyboardInterrupt:
+            pass
+
+    def getBlocks(self):
+        '''download new blocks in queue'''
+        downloadblocks.download_blocks_from_communicator(self)
+
+    def decrementThreadCount(self, threadName):
+        '''Decrement amount of a thread name if more than zero, called when a function meant to be run in a thread ends'''
+        try:
+            if self.threadCounts[threadName] > 0:
+                self.threadCounts[threadName] -= 1
+        except KeyError:
+            pass
+
+    def connectNewPeer(self, peer='', useBootstrap=False):
+        '''Adds a new random online peer to self.onlinePeers'''
+        connectnewpeers.connect_new_peer_to_communicator(self, peer, useBootstrap)
+
+    def peerCleanup(self):
+        '''This just calls onionrpeers.cleanupPeers, which removes dead or bad peers (offline too long, too slow)'''
+        onionrpeers.peer_cleanup()
+        self.decrementThreadCount('peerCleanup')
+
+    def getPeerProfileInstance(self, peer):
+        '''Gets a peer profile instance from the list of profiles, by address name'''
+        for i in self.peerProfiles:
+            # if the peer's profile is already loaded, return that
+            if i.address == peer:
+                retData = i
+                break
+        else:
+            # if the peer's profile is not loaded, return a new one. connectNewPeer also adds it to the list on connect
+            retData = onionrpeers.PeerProfiles(peer)
+            self.peerProfiles.append(retData)
+        return retData
+
+    def getUptime(self):
+        return epoch.get_epoch() - self.startTime
+
+    def heartbeat(self):
+        '''Show a heartbeat debug message'''
+        logger.debug('Heartbeat. Node running for %s.' % humanreadabletime.human_readable_time(self.getUptime()))
+        self.decrementThreadCount('heartbeat')
+
+    def runCheck(self):
+        if run_file_exists(self):
+            logger.debug('Status check; looks good.')
+
+        self.decrementThreadCount('runCheck')
+
+

Methods

+
+
+def connectNewPeer(self, peer='', useBootstrap=False) +
+
+

Adds a new random online peer to self.onlinePeers

+
+Source code +
def connectNewPeer(self, peer='', useBootstrap=False):
+    '''Adds a new random online peer to self.onlinePeers'''
+    connectnewpeers.connect_new_peer_to_communicator(self, peer, useBootstrap)
+
+
+
+def decrementThreadCount(self, threadName) +
+
+

Decrement amount of a thread name if more than zero, called when a function meant to be run in a thread ends

+
+Source code +
def decrementThreadCount(self, threadName):
+    '''Decrement amount of a thread name if more than zero, called when a function meant to be run in a thread ends'''
+    try:
+        if self.threadCounts[threadName] > 0:
+            self.threadCounts[threadName] -= 1
+    except KeyError:
+        pass
+
+
+
+def getBlocks(self) +
+
+

download new blocks in queue

+
+Source code +
def getBlocks(self):
+    '''download new blocks in queue'''
+    downloadblocks.download_blocks_from_communicator(self)
+
+
+
+def getPeerProfileInstance(self, peer) +
+
+

Gets a peer profile instance from the list of profiles, by address name

+
+Source code +
def getPeerProfileInstance(self, peer):
+    '''Gets a peer profile instance from the list of profiles, by address name'''
+    for i in self.peerProfiles:
+        # if the peer's profile is already loaded, return that
+        if i.address == peer:
+            retData = i
+            break
+    else:
+        # if the peer's profile is not loaded, return a new one. connectNewPeer also adds it to the list on connect
+        retData = onionrpeers.PeerProfiles(peer)
+        self.peerProfiles.append(retData)
+    return retData
+
+
+
+def getUptime(self) +
+
+
+
+Source code +
def getUptime(self):
+    return epoch.get_epoch() - self.startTime
+
+
+
+def heartbeat(self) +
+
+

Show a heartbeat debug message

+
+Source code +
def heartbeat(self):
+    '''Show a heartbeat debug message'''
+    logger.debug('Heartbeat. Node running for %s.' % humanreadabletime.human_readable_time(self.getUptime()))
+    self.decrementThreadCount('heartbeat')
+
+
+
+def peerCleanup(self) +
+
+

This just calls onionrpeers.cleanupPeers, which removes dead or bad peers (offline too long, too slow)

+
+Source code +
def peerCleanup(self):
+    '''This just calls onionrpeers.cleanupPeers, which removes dead or bad peers (offline too long, too slow)'''
+    onionrpeers.peer_cleanup()
+    self.decrementThreadCount('peerCleanup')
+
+
+
+def runCheck(self) +
+
+
+
+Source code +
def runCheck(self):
+    if run_file_exists(self):
+        logger.debug('Status check; looks good.')
+
+    self.decrementThreadCount('runCheck')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/onlinepeers/clearofflinepeer.html b/docs/html/onionr/communicator/onlinepeers/clearofflinepeer.html new file mode 100644 index 00000000..a7e8be99 --- /dev/null +++ b/docs/html/onionr/communicator/onlinepeers/clearofflinepeer.html @@ -0,0 +1,113 @@ + + + + + + +onionr.communicator.onlinepeers.clearofflinepeer API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator.onlinepeers.clearofflinepeer

+
+
+

Onionr - Private P2P Communication

+

clear offline peer in a communicator instance

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    clear offline peer in a communicator instance
+'''
+'''
+    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/>.
+'''
+import logger
+def clear_offline_peer(comm_inst):
+    '''Removes the longest offline peer to retry later'''
+    try:
+        removed = comm_inst.offlinePeers.pop(0)
+    except IndexError:
+        pass
+    else:
+        logger.debug('Removed ' + removed + ' from offline list, will try them again.')
+    comm_inst.decrementThreadCount('clear_offline_peer')
+
+
+
+
+
+
+
+

Functions

+
+
+def clear_offline_peer(comm_inst) +
+
+

Removes the longest offline peer to retry later

+
+Source code +
def clear_offline_peer(comm_inst):
+    '''Removes the longest offline peer to retry later'''
+    try:
+        removed = comm_inst.offlinePeers.pop(0)
+    except IndexError:
+        pass
+    else:
+        logger.debug('Removed ' + removed + ' from offline list, will try them again.')
+    comm_inst.decrementThreadCount('clear_offline_peer')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/onlinepeers/index.html b/docs/html/onionr/communicator/onlinepeers/index.html new file mode 100644 index 00000000..5e94f0b7 --- /dev/null +++ b/docs/html/onionr/communicator/onlinepeers/index.html @@ -0,0 +1,89 @@ + + + + + + +onionr.communicator.onlinepeers API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator.onlinepeers

+
+
+
+Source code +
from . import clearofflinepeer, onlinepeers, pickonlinepeers, removeonlinepeer
+
+clear_offline_peer = clearofflinepeer.clear_offline_peer
+get_online_peers = onlinepeers.get_online_peers
+pick_online_peer = pickonlinepeers.pick_online_peer
+remove_online_peer = removeonlinepeer.remove_online_peer
+
+
+
+

Sub-modules

+
+
onionr.communicator.onlinepeers.clearofflinepeer
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicator.onlinepeers.onlinepeers
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicator.onlinepeers.pickonlinepeers
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicator.onlinepeers.removeonlinepeer
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/onlinepeers/onlinepeers.html b/docs/html/onionr/communicator/onlinepeers/onlinepeers.html new file mode 100644 index 00000000..900a6393 --- /dev/null +++ b/docs/html/onionr/communicator/onlinepeers/onlinepeers.html @@ -0,0 +1,143 @@ + + + + + + +onionr.communicator.onlinepeers.onlinepeers API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator.onlinepeers.onlinepeers

+
+
+

Onionr - Private P2P Communication

+

get online peers in a communicator instance

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    get online peers in a communicator instance
+'''
+'''
+    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/>.
+'''
+import time
+from etc import humanreadabletime
+import logger
+def get_online_peers(comm_inst):
+    '''
+        Manages the comm_inst.onlinePeers attribute list, connects to more peers if we have none connected
+    '''
+    config = comm_inst.config
+    logger.debug('Refreshing peer pool...')
+    maxPeers = int(config.get('peers.max_connect', 10))
+    needed = maxPeers - len(comm_inst.onlinePeers)
+
+    for i in range(needed):
+        if len(comm_inst.onlinePeers) == 0:
+            comm_inst.connectNewPeer(useBootstrap=True)
+        else:
+            comm_inst.connectNewPeer()
+
+        if comm_inst.shutdown:
+            break
+    else:
+        if len(comm_inst.onlinePeers) == 0:
+            logger.debug('Couldn\'t connect to any peers.' + (' Last node seen %s ago.' % humanreadabletime.human_readable_time(time.time() - comm_inst.lastNodeSeen) if not comm_inst.lastNodeSeen is None else ''), terminal=True)
+        else:
+            comm_inst.lastNodeSeen = time.time()
+    comm_inst.decrementThreadCount('get_online_peers')
+
+
+
+
+
+
+
+

Functions

+
+
+def get_online_peers(comm_inst) +
+
+

Manages the comm_inst.onlinePeers attribute list, connects to more peers if we have none connected

+
+Source code +
def get_online_peers(comm_inst):
+    '''
+        Manages the comm_inst.onlinePeers attribute list, connects to more peers if we have none connected
+    '''
+    config = comm_inst.config
+    logger.debug('Refreshing peer pool...')
+    maxPeers = int(config.get('peers.max_connect', 10))
+    needed = maxPeers - len(comm_inst.onlinePeers)
+
+    for i in range(needed):
+        if len(comm_inst.onlinePeers) == 0:
+            comm_inst.connectNewPeer(useBootstrap=True)
+        else:
+            comm_inst.connectNewPeer()
+
+        if comm_inst.shutdown:
+            break
+    else:
+        if len(comm_inst.onlinePeers) == 0:
+            logger.debug('Couldn\'t connect to any peers.' + (' Last node seen %s ago.' % humanreadabletime.human_readable_time(time.time() - comm_inst.lastNodeSeen) if not comm_inst.lastNodeSeen is None else ''), terminal=True)
+        else:
+            comm_inst.lastNodeSeen = time.time()
+    comm_inst.decrementThreadCount('get_online_peers')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/onlinepeers/pickonlinepeers.html b/docs/html/onionr/communicator/onlinepeers/pickonlinepeers.html new file mode 100644 index 00000000..021befc8 --- /dev/null +++ b/docs/html/onionr/communicator/onlinepeers/pickonlinepeers.html @@ -0,0 +1,125 @@ + + + + + + +onionr.communicator.onlinepeers.pickonlinepeers API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator.onlinepeers.pickonlinepeers

+
+
+

Onionr - Private P2P Communication

+

pick online peers in a communicator instance

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    pick online peers in a communicator instance
+'''
+'''
+    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/>.
+'''
+import secrets
+def pick_online_peer(comm_inst):
+    '''randomly picks peer from pool without bias (using secrets module)'''
+    ret_data = ''
+    while True:
+        peer_length = len(comm_inst.onlinePeers)
+        if peer_length <= 0:
+            break
+        try:
+            # get a random online peer, securely. May get stuck in loop if network is lost or if all peers in pool magically disconnect at once
+            ret_data = comm_inst.onlinePeers[secrets.randbelow(peer_length)]
+        except IndexError:
+            pass
+        else:
+            break
+    return ret_data
+
+
+
+
+
+
+
+

Functions

+
+
+def pick_online_peer(comm_inst) +
+
+

randomly picks peer from pool without bias (using secrets module)

+
+Source code +
def pick_online_peer(comm_inst):
+    '''randomly picks peer from pool without bias (using secrets module)'''
+    ret_data = ''
+    while True:
+        peer_length = len(comm_inst.onlinePeers)
+        if peer_length <= 0:
+            break
+        try:
+            # get a random online peer, securely. May get stuck in loop if network is lost or if all peers in pool magically disconnect at once
+            ret_data = comm_inst.onlinePeers[secrets.randbelow(peer_length)]
+        except IndexError:
+            pass
+        else:
+            break
+    return ret_data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/onlinepeers/removeonlinepeer.html b/docs/html/onionr/communicator/onlinepeers/removeonlinepeer.html new file mode 100644 index 00000000..fe6d3250 --- /dev/null +++ b/docs/html/onionr/communicator/onlinepeers/removeonlinepeer.html @@ -0,0 +1,122 @@ + + + + + + +onionr.communicator.onlinepeers.removeonlinepeer API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator.onlinepeers.removeonlinepeer

+
+
+

Onionr - Private P2P Communication

+

remove an online peer from the pool in a communicator instance

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    remove an online peer from the pool in a communicator instance
+'''
+'''
+    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 remove_online_peer(comm_inst, peer):
+    '''Remove an online peer'''
+    try:
+        del comm_inst.connectTimes[peer]
+    except KeyError:
+        pass
+    try:
+        del comm_inst.dbTimestamps[peer]
+    except KeyError:
+        pass
+    try:
+        comm_inst.onlinePeers.remove(peer)
+    except ValueError:
+        pass
+
+
+
+
+
+
+
+

Functions

+
+
+def remove_online_peer(comm_inst, peer) +
+
+

Remove an online peer

+
+Source code +
def remove_online_peer(comm_inst, peer):
+    '''Remove an online peer'''
+    try:
+        del comm_inst.connectTimes[peer]
+    except KeyError:
+        pass
+    try:
+        del comm_inst.dbTimestamps[peer]
+    except KeyError:
+        pass
+    try:
+        comm_inst.onlinePeers.remove(peer)
+    except ValueError:
+        pass
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/peeraction.html b/docs/html/onionr/communicator/peeraction.html new file mode 100644 index 00000000..4e7dff99 --- /dev/null +++ b/docs/html/onionr/communicator/peeraction.html @@ -0,0 +1,160 @@ + + + + + + +onionr.communicator.peeraction API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator.peeraction

+
+
+

Onionr - Private P2P Communication

+

This file implements logic for performing requests to Onionr peers

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file implements logic for performing requests to Onionr peers
+'''
+'''
+    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/>.
+'''
+import streamedrequests
+import logger
+from onionrutils import epoch, basicrequests
+from coredb import keydb
+from . import onlinepeers
+
+def peer_action(comm_inst, peer, action, returnHeaders=False, max_resp_size=5242880):
+    '''Perform a get request to a peer'''
+    penalty_score = -10
+    if len(peer) == 0:
+        return False
+    url = 'http://%s/%s' % (peer, action)
+
+    try:
+        ret_data = basicrequests.do_get_request(url, port=comm_inst.proxyPort,
+                                                max_size=max_resp_size)
+    except streamedrequests.exceptions.ResponseLimitReached:
+        logger.warn('Request failed due to max response size being overflowed', terminal=True)
+        ret_data = False
+        penalty_score = -100
+    # if request failed, (error), mark peer offline
+    if ret_data == False: # For some reason "if not" breaks this. Prob has to do with empty string.
+        try:
+            comm_inst.getPeerProfileInstance(peer).addScore(penalty_score)
+            onlinepeers.remove_online_peer(comm_inst, peer)
+            keydb.transportinfo.set_address_info(peer, 'lastConnectAttempt', epoch.get_epoch())
+            if action != 'ping' and not comm_inst.shutdown:
+                logger.warn(f'Lost connection to {peer}', terminal=True)
+                onlinepeers.get_online_peers(comm_inst) # Will only add a new peer to pool if needed
+        except ValueError:
+            pass
+    else:
+        peer_profile = comm_inst.getPeerProfileInstance(peer)
+        peer_profile.update_connect_time()
+        peer_profile.addScore(1)
+    return ret_data # If returnHeaders, returns tuple of data, headers. if not, just data string
+
+
+
+
+
+
+
+

Functions

+
+
+def peer_action(comm_inst, peer, action, returnHeaders=False, max_resp_size=5242880) +
+
+

Perform a get request to a peer

+
+Source code +
def peer_action(comm_inst, peer, action, returnHeaders=False, max_resp_size=5242880):
+    '''Perform a get request to a peer'''
+    penalty_score = -10
+    if len(peer) == 0:
+        return False
+    url = 'http://%s/%s' % (peer, action)
+
+    try:
+        ret_data = basicrequests.do_get_request(url, port=comm_inst.proxyPort,
+                                                max_size=max_resp_size)
+    except streamedrequests.exceptions.ResponseLimitReached:
+        logger.warn('Request failed due to max response size being overflowed', terminal=True)
+        ret_data = False
+        penalty_score = -100
+    # if request failed, (error), mark peer offline
+    if ret_data == False: # For some reason "if not" breaks this. Prob has to do with empty string.
+        try:
+            comm_inst.getPeerProfileInstance(peer).addScore(penalty_score)
+            onlinepeers.remove_online_peer(comm_inst, peer)
+            keydb.transportinfo.set_address_info(peer, 'lastConnectAttempt', epoch.get_epoch())
+            if action != 'ping' and not comm_inst.shutdown:
+                logger.warn(f'Lost connection to {peer}', terminal=True)
+                onlinepeers.get_online_peers(comm_inst) # Will only add a new peer to pool if needed
+        except ValueError:
+            pass
+    else:
+        peer_profile = comm_inst.getPeerProfileInstance(peer)
+        peer_profile.update_connect_time()
+        peer_profile.addScore(1)
+    return ret_data # If returnHeaders, returns tuple of data, headers. if not, just data string
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicator/uploadqueue/index.html b/docs/html/onionr/communicator/uploadqueue/index.html new file mode 100644 index 00000000..d7382195 --- /dev/null +++ b/docs/html/onionr/communicator/uploadqueue/index.html @@ -0,0 +1,186 @@ + + + + + + +onionr.communicator.uploadqueue API documentation + + + + + + + + + +
+
+
+

Module onionr.communicator.uploadqueue

+
+
+

Onionr - Private P2P Communication

+

Class to remember blocks that need to be uploaded and not shared on startup/shutdown

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    Class to remember blocks that need to be uploaded and not shared on startup/shutdown
+"""
+"""
+    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/>.
+"""
+
+import atexit
+import json
+
+import deadsimplekv
+
+import filepaths
+from onionrutils import localcommand
+
+UPLOAD_MEMORY_FILE = filepaths.upload_list
+
+def _add_to_hidden_blocks(cache):
+    for bl in cache:
+        localcommand.local_command('waitforshare/' + bl, post=True)
+
+class UploadQueue:
+    """
+        Saves and loads block upload info from json file
+    """
+
+    def __init__(self, communicator: 'OnionrCommunicatorDaemon'):
+        """Start the UploadQueue object, loading left over uploads into queue 
+        and registering save shutdown function
+        """
+        self.communicator = communicator
+        cache = deadsimplekv.DeadSimpleKV(UPLOAD_MEMORY_FILE)
+        self.store_obj = cache
+        cache: list = cache.get('uploads')
+        if cache == None:
+            cache = []
+        
+        _add_to_hidden_blocks(cache)
+        self.communicator.blocksToUpload.extend(cache)
+
+        atexit.register(self.save)
+
+    def save(self):
+        """Saves to disk on shutdown or if called manually"""
+        bl: list = self.communicator.blocksToUpload
+        self.store_obj.put('uploads', bl)
+        self.store_obj.flush()
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class UploadQueue +(communicator) +
+
+

Saves and loads block upload info from json file

+

Start the UploadQueue object, loading left over uploads into queue +and registering save shutdown function

+
+Source code +
class UploadQueue:
+    """
+        Saves and loads block upload info from json file
+    """
+
+    def __init__(self, communicator: 'OnionrCommunicatorDaemon'):
+        """Start the UploadQueue object, loading left over uploads into queue 
+        and registering save shutdown function
+        """
+        self.communicator = communicator
+        cache = deadsimplekv.DeadSimpleKV(UPLOAD_MEMORY_FILE)
+        self.store_obj = cache
+        cache: list = cache.get('uploads')
+        if cache == None:
+            cache = []
+        
+        _add_to_hidden_blocks(cache)
+        self.communicator.blocksToUpload.extend(cache)
+
+        atexit.register(self.save)
+
+    def save(self):
+        """Saves to disk on shutdown or if called manually"""
+        bl: list = self.communicator.blocksToUpload
+        self.store_obj.put('uploads', bl)
+        self.store_obj.flush()
+
+

Methods

+
+
+def save(self) +
+
+

Saves to disk on shutdown or if called manually

+
+Source code +
def save(self):
+    """Saves to disk on shutdown or if called manually"""
+    bl: list = self.communicator.blocksToUpload
+    self.store_obj.put('uploads', bl)
+    self.store_obj.flush()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/announcenode.html b/docs/html/onionr/communicatorutils/announcenode.html new file mode 100644 index 00000000..feb47b3f --- /dev/null +++ b/docs/html/onionr/communicatorutils/announcenode.html @@ -0,0 +1,220 @@ + + + + + + +onionr.communicatorutils.announcenode API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.announcenode

+
+
+

Onionr - Private P2P Communication

+

Use a communicator instance to announce our transport address to connected nodes

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Use a communicator instance to announce our transport address to connected nodes
+'''
+'''
+    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/>.
+'''
+import base64
+import onionrproofs, logger
+from etc import onionrvalues
+from onionrutils import basicrequests, bytesconverter
+from utils import gettransports
+from netcontroller import NetController
+from communicator import onlinepeers
+from coredb import keydb
+def announce_node(daemon):
+    '''Announce our node to our peers'''
+    ret_data = False
+    announce_fail = False
+    
+    # Do not let announceCache get too large
+    if len(daemon.announceCache) >= 10000:
+        daemon.announceCache.popitem()
+
+    if daemon.config.get('general.security_level', 0) == 0:
+        # Announce to random online peers
+        for i in daemon.onlinePeers:
+            if not i in daemon.announceCache and not i in daemon.announceProgress:
+                peer = i
+                break
+        else:
+            peer = onlinepeers.pick_online_peer(daemon)
+
+        for x in range(1):
+            try:
+                ourID = gettransports.get()[0]
+            except IndexError:
+                break
+
+            url = 'http://' + peer + '/announce'
+            data = {'node': ourID}
+
+            combinedNodes = ourID + peer
+            if ourID != 1:
+                existingRand = bytesconverter.bytes_to_str(keydb.transportinfo.get_address_info(peer, 'powValue'))
+                # Reset existingRand if it no longer meets the minimum POW
+                if type(existingRand) is type(None) or not existingRand.endswith('0' * onionrvalues.ANNOUNCE_POW):
+                    existingRand = ''
+
+            if peer in daemon.announceCache:
+                data['random'] = daemon.announceCache[peer]
+            elif len(existingRand) > 0:
+                data['random'] = existingRand
+            else:
+                daemon.announceProgress[peer] = True
+                proof = onionrproofs.DataPOW(combinedNodes, minDifficulty=onionrvalues.ANNOUNCE_POW)
+                del daemon.announceProgress[peer]
+                try:
+                    data['random'] = base64.b64encode(proof.waitForResult()[1])
+                except TypeError:
+                    # Happens when we failed to produce a proof
+                    logger.error("Failed to produce a pow for announcing to " + peer)
+                    announce_fail = True
+                else:
+                    daemon.announceCache[peer] = data['random']
+            if not announce_fail:
+                logger.info('Announcing node to ' + url)
+                if basicrequests.do_post_request(url, data, port=daemon.shared_state.get(NetController).socksPort) == 'Success':
+                    logger.info('Successfully introduced node to ' + peer, terminal=True)
+                    ret_data = True
+                    keydb.transportinfo.set_address_info(peer, 'introduced', 1)
+                    keydb.transportinfo.set_address_info(peer, 'powValue', data['random'])
+    daemon.decrementThreadCount('announce_node')
+    return ret_data
+
+
+
+
+
+
+
+

Functions

+
+
+def announce_node(daemon) +
+
+

Announce our node to our peers

+
+Source code +
def announce_node(daemon):
+    '''Announce our node to our peers'''
+    ret_data = False
+    announce_fail = False
+    
+    # Do not let announceCache get too large
+    if len(daemon.announceCache) >= 10000:
+        daemon.announceCache.popitem()
+
+    if daemon.config.get('general.security_level', 0) == 0:
+        # Announce to random online peers
+        for i in daemon.onlinePeers:
+            if not i in daemon.announceCache and not i in daemon.announceProgress:
+                peer = i
+                break
+        else:
+            peer = onlinepeers.pick_online_peer(daemon)
+
+        for x in range(1):
+            try:
+                ourID = gettransports.get()[0]
+            except IndexError:
+                break
+
+            url = 'http://' + peer + '/announce'
+            data = {'node': ourID}
+
+            combinedNodes = ourID + peer
+            if ourID != 1:
+                existingRand = bytesconverter.bytes_to_str(keydb.transportinfo.get_address_info(peer, 'powValue'))
+                # Reset existingRand if it no longer meets the minimum POW
+                if type(existingRand) is type(None) or not existingRand.endswith('0' * onionrvalues.ANNOUNCE_POW):
+                    existingRand = ''
+
+            if peer in daemon.announceCache:
+                data['random'] = daemon.announceCache[peer]
+            elif len(existingRand) > 0:
+                data['random'] = existingRand
+            else:
+                daemon.announceProgress[peer] = True
+                proof = onionrproofs.DataPOW(combinedNodes, minDifficulty=onionrvalues.ANNOUNCE_POW)
+                del daemon.announceProgress[peer]
+                try:
+                    data['random'] = base64.b64encode(proof.waitForResult()[1])
+                except TypeError:
+                    # Happens when we failed to produce a proof
+                    logger.error("Failed to produce a pow for announcing to " + peer)
+                    announce_fail = True
+                else:
+                    daemon.announceCache[peer] = data['random']
+            if not announce_fail:
+                logger.info('Announcing node to ' + url)
+                if basicrequests.do_post_request(url, data, port=daemon.shared_state.get(NetController).socksPort) == 'Success':
+                    logger.info('Successfully introduced node to ' + peer, terminal=True)
+                    ret_data = True
+                    keydb.transportinfo.set_address_info(peer, 'introduced', 1)
+                    keydb.transportinfo.set_address_info(peer, 'powValue', data['random'])
+    daemon.decrementThreadCount('announce_node')
+    return ret_data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/connectnewpeers.html b/docs/html/onionr/communicatorutils/connectnewpeers.html new file mode 100644 index 00000000..ce33a512 --- /dev/null +++ b/docs/html/onionr/communicatorutils/connectnewpeers.html @@ -0,0 +1,234 @@ + + + + + + +onionr.communicatorutils.connectnewpeers API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.connectnewpeers

+
+
+

Onionr - Private P2P Communication

+

Connect a new peer to our communicator instance. Does so randomly if no peer is specified

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Connect a new peer to our communicator instance. Does so randomly if no peer is specified
+'''
+'''
+    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/>.
+'''
+import time, sys, secrets
+import onionrexceptions, logger, onionrpeers
+from utils import networkmerger, gettransports
+from onionrutils import stringvalidators, epoch
+from communicator import peeraction, bootstrappeers
+from coredb import keydb
+def connect_new_peer_to_communicator(comm_inst, peer='', useBootstrap=False):
+    config = comm_inst.config
+    retData = False
+    tried = comm_inst.offlinePeers
+    transports = gettransports.get()
+    if peer != '':
+        if stringvalidators.validate_transport(peer):
+            peerList = [peer]
+        else:
+            raise onionrexceptions.InvalidAddress('Will not attempt connection test to invalid address')
+    else:
+        peerList = keydb.listkeys.list_adders()
+
+    mainPeerList = keydb.listkeys.list_adders()
+    peerList = onionrpeers.get_score_sorted_peer_list()
+
+    # If we don't have enough peers connected or random chance, select new peers to try
+    if len(peerList) < 8 or secrets.randbelow(4) == 3:
+        tryingNew = []
+        for x in comm_inst.newPeers:
+            if x not in peerList:
+                peerList.append(x)
+                tryingNew.append(x)
+        for i in tryingNew:
+            comm_inst.newPeers.remove(i)
+    
+    if len(peerList) == 0 or useBootstrap:
+        # Avoid duplicating bootstrap addresses in peerList
+        if config.get('general.use_bootstrap_list', True):
+            bootstrappeers.add_bootstrap_list_to_peer_list(comm_inst, peerList)
+
+    for address in peerList:
+        if not config.get('tor.v3onions') and len(address) == 62:
+            continue
+        # Don't connect to our own address
+        if address in transports:
+            continue
+        # Don't connect to invalid address or if its already been tried/connected, or if its cooled down
+        if len(address) == 0 or address in tried or address in comm_inst.onlinePeers or address in comm_inst.cooldownPeer:
+            continue
+        if comm_inst.shutdown:
+            return
+        # Ping a peer,
+        ret = peeraction.peer_action(comm_inst, address, 'ping')
+        if ret == 'pong!':
+            time.sleep(0.1)
+            if address not in mainPeerList:
+                # Add a peer to our list if it isn't already since it successfully connected
+                networkmerger.mergeAdders(address)
+            if address not in comm_inst.onlinePeers:
+                logger.info('Connected to ' + address, terminal=True)
+                comm_inst.onlinePeers.append(address)
+                comm_inst.connectTimes[address] = epoch.get_epoch()
+            retData = address
+
+            # add peer to profile list if they're not in it
+            for profile in comm_inst.peerProfiles:
+                if profile.address == address:
+                    break
+            else:
+                comm_inst.peerProfiles.append(onionrpeers.PeerProfiles(address))
+            break
+        else:
+            # Mark a peer as tried if they failed to respond to ping
+            tried.append(address)
+            logger.debug('Failed to connect to %s: %s ' % (address, ret))
+    return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def connect_new_peer_to_communicator(comm_inst, peer='', useBootstrap=False) +
+
+
+
+Source code +
def connect_new_peer_to_communicator(comm_inst, peer='', useBootstrap=False):
+    config = comm_inst.config
+    retData = False
+    tried = comm_inst.offlinePeers
+    transports = gettransports.get()
+    if peer != '':
+        if stringvalidators.validate_transport(peer):
+            peerList = [peer]
+        else:
+            raise onionrexceptions.InvalidAddress('Will not attempt connection test to invalid address')
+    else:
+        peerList = keydb.listkeys.list_adders()
+
+    mainPeerList = keydb.listkeys.list_adders()
+    peerList = onionrpeers.get_score_sorted_peer_list()
+
+    # If we don't have enough peers connected or random chance, select new peers to try
+    if len(peerList) < 8 or secrets.randbelow(4) == 3:
+        tryingNew = []
+        for x in comm_inst.newPeers:
+            if x not in peerList:
+                peerList.append(x)
+                tryingNew.append(x)
+        for i in tryingNew:
+            comm_inst.newPeers.remove(i)
+    
+    if len(peerList) == 0 or useBootstrap:
+        # Avoid duplicating bootstrap addresses in peerList
+        if config.get('general.use_bootstrap_list', True):
+            bootstrappeers.add_bootstrap_list_to_peer_list(comm_inst, peerList)
+
+    for address in peerList:
+        if not config.get('tor.v3onions') and len(address) == 62:
+            continue
+        # Don't connect to our own address
+        if address in transports:
+            continue
+        # Don't connect to invalid address or if its already been tried/connected, or if its cooled down
+        if len(address) == 0 or address in tried or address in comm_inst.onlinePeers or address in comm_inst.cooldownPeer:
+            continue
+        if comm_inst.shutdown:
+            return
+        # Ping a peer,
+        ret = peeraction.peer_action(comm_inst, address, 'ping')
+        if ret == 'pong!':
+            time.sleep(0.1)
+            if address not in mainPeerList:
+                # Add a peer to our list if it isn't already since it successfully connected
+                networkmerger.mergeAdders(address)
+            if address not in comm_inst.onlinePeers:
+                logger.info('Connected to ' + address, terminal=True)
+                comm_inst.onlinePeers.append(address)
+                comm_inst.connectTimes[address] = epoch.get_epoch()
+            retData = address
+
+            # add peer to profile list if they're not in it
+            for profile in comm_inst.peerProfiles:
+                if profile.address == address:
+                    break
+            else:
+                comm_inst.peerProfiles.append(onionrpeers.PeerProfiles(address))
+            break
+        else:
+            # Mark a peer as tried if they failed to respond to ping
+            tried.append(address)
+            logger.debug('Failed to connect to %s: %s ' % (address, ret))
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/cooldownpeer.html b/docs/html/onionr/communicatorutils/cooldownpeer.html new file mode 100644 index 00000000..d19f96e3 --- /dev/null +++ b/docs/html/onionr/communicatorutils/cooldownpeer.html @@ -0,0 +1,162 @@ + + + + + + +onionr.communicatorutils.cooldownpeer API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.cooldownpeer

+
+
+

Onionr - Private P2P Communication

+

Select a random online peer in a communicator instance and have them "cool down"

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Select a random online peer in a communicator instance and have them "cool down"
+'''
+'''
+    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 onionrutils import epoch
+from communicator import onlinepeers
+def cooldown_peer(comm_inst):
+    '''Randomly add an online peer to cooldown, so we can connect a new one'''
+    config = comm_inst.config
+    onlinePeerAmount = len(comm_inst.onlinePeers)
+    minTime = 300
+    cooldownTime = 600
+    toCool = ''
+    tempConnectTimes = dict(comm_inst.connectTimes)
+
+    # Remove peers from cooldown that have been there long enough
+    tempCooldown = dict(comm_inst.cooldownPeer)
+    for peer in tempCooldown:
+        if (epoch.get_epoch() - tempCooldown[peer]) >= cooldownTime:
+            del comm_inst.cooldownPeer[peer]
+
+    # Cool down a peer, if we have max connections alive for long enough
+    if onlinePeerAmount >= config.get('peers.max_connect', 10, save = True):
+        finding = True
+
+        while finding:
+            try:
+                toCool = min(tempConnectTimes, key=tempConnectTimes.get)
+                if (epoch.get_epoch() - tempConnectTimes[toCool]) < minTime:
+                    del tempConnectTimes[toCool]
+                else:
+                    finding = False
+            except ValueError:
+                break
+        else:
+            onlinepeers.remove_online_peer(comm_inst, toCool)
+            comm_inst.cooldownPeer[toCool] = epoch.get_epoch()
+
+    comm_inst.decrementThreadCount('cooldown_peer')
+
+
+
+
+
+
+
+

Functions

+
+
+def cooldown_peer(comm_inst) +
+
+

Randomly add an online peer to cooldown, so we can connect a new one

+
+Source code +
def cooldown_peer(comm_inst):
+    '''Randomly add an online peer to cooldown, so we can connect a new one'''
+    config = comm_inst.config
+    onlinePeerAmount = len(comm_inst.onlinePeers)
+    minTime = 300
+    cooldownTime = 600
+    toCool = ''
+    tempConnectTimes = dict(comm_inst.connectTimes)
+
+    # Remove peers from cooldown that have been there long enough
+    tempCooldown = dict(comm_inst.cooldownPeer)
+    for peer in tempCooldown:
+        if (epoch.get_epoch() - tempCooldown[peer]) >= cooldownTime:
+            del comm_inst.cooldownPeer[peer]
+
+    # Cool down a peer, if we have max connections alive for long enough
+    if onlinePeerAmount >= config.get('peers.max_connect', 10, save = True):
+        finding = True
+
+        while finding:
+            try:
+                toCool = min(tempConnectTimes, key=tempConnectTimes.get)
+                if (epoch.get_epoch() - tempConnectTimes[toCool]) < minTime:
+                    del tempConnectTimes[toCool]
+                else:
+                    finding = False
+            except ValueError:
+                break
+        else:
+            onlinepeers.remove_online_peer(comm_inst, toCool)
+            comm_inst.cooldownPeer[toCool] = epoch.get_epoch()
+
+    comm_inst.decrementThreadCount('cooldown_peer')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/daemonqueuehandler.html b/docs/html/onionr/communicatorutils/daemonqueuehandler.html new file mode 100644 index 00000000..3dcd7dcc --- /dev/null +++ b/docs/html/onionr/communicatorutils/daemonqueuehandler.html @@ -0,0 +1,192 @@ + + + + + + +onionr.communicatorutils.daemonqueuehandler API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.daemonqueuehandler

+
+
+

Onionr - P2P Anonymous Storage Network

+

Handle daemon queue commands in the communicator

+
+Source code +
'''
+    Onionr - P2P Anonymous Storage Network
+
+    Handle daemon queue commands in the communicator
+'''
+'''
+    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/>.
+'''
+import logger
+from onionrplugins import onionrevents as events
+from onionrutils import localcommand
+from coredb import daemonqueue
+import filepaths
+from . import restarttor
+def handle_daemon_commands(comm_inst):
+    cmd = daemonqueue.daemon_queue()
+    response = ''
+    if cmd is not False:
+        events.event('daemon_command', data = {'cmd' : cmd})
+        if cmd[0] == 'shutdown':
+            comm_inst.shutdown = True
+        elif cmd[0] == 'remove_from_insert_list':
+            try:
+                comm_inst.generating_blocks.remove(cmd[1])
+            except ValueError:
+                pass
+        elif cmd[0] == 'announceNode':
+            if len(comm_inst.onlinePeers) > 0:
+                comm_inst.announce(cmd[1])
+            else:
+                logger.debug("No nodes connected. Will not introduce node.")
+        elif cmd[0] == 'runCheck': # deprecated
+            logger.debug('Status check; looks good.')
+            open(filepaths.run_check_file + '.runcheck', 'w+').close()
+        elif cmd[0] == 'connectedPeers':
+            response = '\n'.join(list(comm_inst.onlinePeers)).strip()
+            if response == '':
+                response = 'none'
+        elif cmd[0] == 'localCommand':
+            response = localcommand.local_command(cmd[1])
+        elif cmd[0] == 'clearOffline':
+            comm_inst.offlinePeers = []
+        elif cmd[0] == 'restartTor':
+            restarttor.restart(comm_inst)
+            comm_inst.offlinePeers = []
+        elif cmd[0] == 'pex':
+            for i in comm_inst.timers:
+                if i.timerFunction.__name__ == 'lookupAdders':
+                    i.count = (i.frequency - 1)
+        elif cmd[0] == 'uploadBlock':
+            comm_inst.blocksToUpload.append(cmd[1])
+        else:
+            logger.debug('Received daemon queue command unable to be handled: %s' % (cmd[0],))
+
+        if cmd[0] not in ('', None):
+            if response != '':
+                localcommand.local_command('queueResponseAdd/' + cmd[4], post=True, postData={'data': response})
+        response = ''
+
+    comm_inst.decrementThreadCount('handle_daemon_commands')
+
+
+
+
+
+
+
+

Functions

+
+
+def handle_daemon_commands(comm_inst) +
+
+
+
+Source code +
def handle_daemon_commands(comm_inst):
+    cmd = daemonqueue.daemon_queue()
+    response = ''
+    if cmd is not False:
+        events.event('daemon_command', data = {'cmd' : cmd})
+        if cmd[0] == 'shutdown':
+            comm_inst.shutdown = True
+        elif cmd[0] == 'remove_from_insert_list':
+            try:
+                comm_inst.generating_blocks.remove(cmd[1])
+            except ValueError:
+                pass
+        elif cmd[0] == 'announceNode':
+            if len(comm_inst.onlinePeers) > 0:
+                comm_inst.announce(cmd[1])
+            else:
+                logger.debug("No nodes connected. Will not introduce node.")
+        elif cmd[0] == 'runCheck': # deprecated
+            logger.debug('Status check; looks good.')
+            open(filepaths.run_check_file + '.runcheck', 'w+').close()
+        elif cmd[0] == 'connectedPeers':
+            response = '\n'.join(list(comm_inst.onlinePeers)).strip()
+            if response == '':
+                response = 'none'
+        elif cmd[0] == 'localCommand':
+            response = localcommand.local_command(cmd[1])
+        elif cmd[0] == 'clearOffline':
+            comm_inst.offlinePeers = []
+        elif cmd[0] == 'restartTor':
+            restarttor.restart(comm_inst)
+            comm_inst.offlinePeers = []
+        elif cmd[0] == 'pex':
+            for i in comm_inst.timers:
+                if i.timerFunction.__name__ == 'lookupAdders':
+                    i.count = (i.frequency - 1)
+        elif cmd[0] == 'uploadBlock':
+            comm_inst.blocksToUpload.append(cmd[1])
+        else:
+            logger.debug('Received daemon queue command unable to be handled: %s' % (cmd[0],))
+
+        if cmd[0] not in ('', None):
+            if response != '':
+                localcommand.local_command('queueResponseAdd/' + cmd[4], post=True, postData={'data': response})
+        response = ''
+
+    comm_inst.decrementThreadCount('handle_daemon_commands')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/deniableinserts.html b/docs/html/onionr/communicatorutils/deniableinserts.html new file mode 100644 index 00000000..183303ec --- /dev/null +++ b/docs/html/onionr/communicatorutils/deniableinserts.html @@ -0,0 +1,117 @@ + + + + + + +onionr.communicatorutils.deniableinserts API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.deniableinserts

+
+
+

Onionr - Private P2P Communication

+

Use the communicator to insert fake mail messages

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Use the communicator to insert fake mail messages
+'''
+'''
+    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/>.
+'''
+import secrets
+from etc import onionrvalues
+import onionrblocks
+def insert_deniable_block(comm_inst):
+    '''Insert a fake block in order to make it more difficult to track real blocks'''
+    fakePeer = ''
+    chance = 10
+    if secrets.randbelow(chance) == (chance - 1):
+        # This assumes on the libsodium primitives to have key-privacy
+        fakePeer = onionrvalues.DENIABLE_PEER_ADDRESS
+        data = secrets.token_hex(secrets.randbelow(1024) + 1)
+        onionrblocks.insert(data, header='pm', encryptType='asym', asymPeer=fakePeer, disableForward=True, meta={'subject': 'foo'})
+    comm_inst.decrementThreadCount('insert_deniable_block')
+
+
+
+
+
+
+
+

Functions

+
+
+def insert_deniable_block(comm_inst) +
+
+

Insert a fake block in order to make it more difficult to track real blocks

+
+Source code +
def insert_deniable_block(comm_inst):
+    '''Insert a fake block in order to make it more difficult to track real blocks'''
+    fakePeer = ''
+    chance = 10
+    if secrets.randbelow(chance) == (chance - 1):
+        # This assumes on the libsodium primitives to have key-privacy
+        fakePeer = onionrvalues.DENIABLE_PEER_ADDRESS
+        data = secrets.token_hex(secrets.randbelow(1024) + 1)
+        onionrblocks.insert(data, header='pm', encryptType='asym', asymPeer=fakePeer, disableForward=True, meta={'subject': 'foo'})
+    comm_inst.decrementThreadCount('insert_deniable_block')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/downloadblocks/index.html b/docs/html/onionr/communicatorutils/downloadblocks/index.html new file mode 100644 index 00000000..25c40534 --- /dev/null +++ b/docs/html/onionr/communicatorutils/downloadblocks/index.html @@ -0,0 +1,326 @@ + + + + + + +onionr.communicatorutils.downloadblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.downloadblocks

+
+
+

Onionr - Private P2P Communication

+

Download blocks using the communicator instance

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Download blocks using the communicator instance
+'''
+'''
+    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/>.
+'''
+import communicator, onionrexceptions
+import logger, onionrpeers
+from onionrutils import blockmetadata, stringvalidators, validatemetadata
+from coredb import blockmetadb
+from . import shoulddownload
+from communicator import peeraction, onlinepeers
+import onionrcrypto, onionrstorage
+from onionrblocks import onionrblacklist, storagecounter
+def download_blocks_from_communicator(comm_inst):
+    '''Use Onionr communicator instance to download blocks in the communicator's queue'''
+    assert isinstance(comm_inst, communicator.OnionrCommunicatorDaemon)
+    blacklist = onionrblacklist.OnionrBlackList()
+    storage_counter = storagecounter.StorageCounter()
+    LOG_SKIP_COUNT = 50 # for how many iterations we skip logging the counter
+    count: int = 0
+    metadata_validation_result: bool = False
+    # Iterate the block queue in the communicator
+    for blockHash in list(comm_inst.blockQueue):
+        count += 1
+        if len(comm_inst.onlinePeers) == 0:
+            break
+        triedQueuePeers = [] # List of peers we've tried for a block
+        try:
+            blockPeers = list(comm_inst.blockQueue[blockHash])
+        except KeyError:
+            blockPeers = []
+        removeFromQueue = True
+
+        if not shoulddownload.should_download(comm_inst, blockHash):
+            continue
+
+        if comm_inst.shutdown or not comm_inst.isOnline or storage_counter.is_full():
+            # Exit loop if shutting down or offline, or disk allocation reached
+            break
+        # Do not download blocks being downloaded
+        if blockHash in comm_inst.currentDownloading:
+            #logger.debug('Already downloading block %s...' % blockHash)
+            continue
+
+        comm_inst.currentDownloading.append(blockHash) # So we can avoid concurrent downloading in other threads of same block
+        if len(blockPeers) == 0:
+            peerUsed = onlinepeers.pick_online_peer(comm_inst)
+        else:
+            blockPeers = onionrcrypto.cryptoutils.random_shuffle(blockPeers)
+            peerUsed = blockPeers.pop(0)
+
+        if not comm_inst.shutdown and peerUsed.strip() != '':
+            logger.info("Attempting to download %s from %s..." % (blockHash[:12], peerUsed))
+        content = peeraction.peer_action(comm_inst, peerUsed, 'getdata/' + blockHash, max_resp_size=3000000) # block content from random peer (includes metadata)
+
+        if content != False and len(content) > 0:
+            try:
+                content = content.encode()
+            except AttributeError:
+                pass
+
+            realHash = onionrcrypto.hashers.sha3_hash(content)
+            try:
+                realHash = realHash.decode() # bytes on some versions for some reason
+            except AttributeError:
+                pass
+            if realHash == blockHash:
+                #content = content.decode() # decode here because sha3Hash needs bytes above
+                metas = blockmetadata.get_block_metadata_from_data(content) # returns tuple(metadata, meta), meta is also in metadata
+                metadata = metas[0]
+                try:
+                    metadata_validation_result = validatemetadata.validate_metadata(metadata, metas[2])
+                except onionrexceptions.DataExists:
+                    metadata_validation_result = False
+                if metadata_validation_result: # check if metadata is valid, and verify nonce
+                    if onionrcrypto.cryptoutils.verify_POW(content): # check if POW is enough/correct
+                        logger.info('Attempting to save block %s...' % blockHash[:12])
+                        try:
+                            onionrstorage.set_data(content)
+                        except onionrexceptions.DataExists:
+                            logger.warn('Data is already set for %s ' % (blockHash,))
+                        except onionrexceptions.DiskAllocationReached:
+                            logger.error('Reached disk allocation allowance, cannot save block %s.' % (blockHash,))
+                            removeFromQueue = False
+                        else:
+                            blockmetadb.add_to_block_DB(blockHash, dataSaved=True) # add block to meta db
+                            blockmetadata.process_block_metadata(blockHash) # caches block metadata values to block database
+                    else:
+                        logger.warn('POW failed for block %s.' % (blockHash,))
+                else:
+                    if blacklist.inBlacklist(realHash):
+                        logger.warn('Block %s is blacklisted.' % (realHash,))
+                    else:
+                        logger.warn('Metadata for block %s is invalid.' % (blockHash,))
+                        blacklist.addToDB(blockHash)
+            else:
+                # if block didn't meet expected hash
+                tempHash = onionrcrypto.hashers.sha3_hash(content) # lazy hack, TODO use var
+                try:
+                    tempHash = tempHash.decode()
+                except AttributeError:
+                    pass
+                # Punish peer for sharing invalid block (not always malicious, but is bad regardless)
+                onionrpeers.PeerProfiles(peerUsed).addScore(-50)
+                if tempHash != 'ed55e34cb828232d6c14da0479709bfa10a0923dca2b380496e6b2ed4f7a0253':
+                    # Dumb hack for 404 response from peer. Don't log it if 404 since its likely not malicious or a critical error.
+                    logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
+                else:
+                    removeFromQueue = False # Don't remove from queue if 404
+            if removeFromQueue:
+                try:
+                    del comm_inst.blockQueue[blockHash] # remove from block queue both if success or false
+                    if count == LOG_SKIP_COUNT:
+                        logger.info('%s blocks remaining in queue' % [len(comm_inst.blockQueue)], terminal=True)
+                        count = 0
+                except KeyError:
+                    pass
+        comm_inst.currentDownloading.remove(blockHash)
+    comm_inst.decrementThreadCount('getBlocks')
+
+
+
+

Sub-modules

+
+
onionr.communicatorutils.downloadblocks.shoulddownload
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+

Functions

+
+
+def download_blocks_from_communicator(comm_inst) +
+
+

Use Onionr communicator instance to download blocks in the communicator's queue

+
+Source code +
def download_blocks_from_communicator(comm_inst):
+    '''Use Onionr communicator instance to download blocks in the communicator's queue'''
+    assert isinstance(comm_inst, communicator.OnionrCommunicatorDaemon)
+    blacklist = onionrblacklist.OnionrBlackList()
+    storage_counter = storagecounter.StorageCounter()
+    LOG_SKIP_COUNT = 50 # for how many iterations we skip logging the counter
+    count: int = 0
+    metadata_validation_result: bool = False
+    # Iterate the block queue in the communicator
+    for blockHash in list(comm_inst.blockQueue):
+        count += 1
+        if len(comm_inst.onlinePeers) == 0:
+            break
+        triedQueuePeers = [] # List of peers we've tried for a block
+        try:
+            blockPeers = list(comm_inst.blockQueue[blockHash])
+        except KeyError:
+            blockPeers = []
+        removeFromQueue = True
+
+        if not shoulddownload.should_download(comm_inst, blockHash):
+            continue
+
+        if comm_inst.shutdown or not comm_inst.isOnline or storage_counter.is_full():
+            # Exit loop if shutting down or offline, or disk allocation reached
+            break
+        # Do not download blocks being downloaded
+        if blockHash in comm_inst.currentDownloading:
+            #logger.debug('Already downloading block %s...' % blockHash)
+            continue
+
+        comm_inst.currentDownloading.append(blockHash) # So we can avoid concurrent downloading in other threads of same block
+        if len(blockPeers) == 0:
+            peerUsed = onlinepeers.pick_online_peer(comm_inst)
+        else:
+            blockPeers = onionrcrypto.cryptoutils.random_shuffle(blockPeers)
+            peerUsed = blockPeers.pop(0)
+
+        if not comm_inst.shutdown and peerUsed.strip() != '':
+            logger.info("Attempting to download %s from %s..." % (blockHash[:12], peerUsed))
+        content = peeraction.peer_action(comm_inst, peerUsed, 'getdata/' + blockHash, max_resp_size=3000000) # block content from random peer (includes metadata)
+
+        if content != False and len(content) > 0:
+            try:
+                content = content.encode()
+            except AttributeError:
+                pass
+
+            realHash = onionrcrypto.hashers.sha3_hash(content)
+            try:
+                realHash = realHash.decode() # bytes on some versions for some reason
+            except AttributeError:
+                pass
+            if realHash == blockHash:
+                #content = content.decode() # decode here because sha3Hash needs bytes above
+                metas = blockmetadata.get_block_metadata_from_data(content) # returns tuple(metadata, meta), meta is also in metadata
+                metadata = metas[0]
+                try:
+                    metadata_validation_result = validatemetadata.validate_metadata(metadata, metas[2])
+                except onionrexceptions.DataExists:
+                    metadata_validation_result = False
+                if metadata_validation_result: # check if metadata is valid, and verify nonce
+                    if onionrcrypto.cryptoutils.verify_POW(content): # check if POW is enough/correct
+                        logger.info('Attempting to save block %s...' % blockHash[:12])
+                        try:
+                            onionrstorage.set_data(content)
+                        except onionrexceptions.DataExists:
+                            logger.warn('Data is already set for %s ' % (blockHash,))
+                        except onionrexceptions.DiskAllocationReached:
+                            logger.error('Reached disk allocation allowance, cannot save block %s.' % (blockHash,))
+                            removeFromQueue = False
+                        else:
+                            blockmetadb.add_to_block_DB(blockHash, dataSaved=True) # add block to meta db
+                            blockmetadata.process_block_metadata(blockHash) # caches block metadata values to block database
+                    else:
+                        logger.warn('POW failed for block %s.' % (blockHash,))
+                else:
+                    if blacklist.inBlacklist(realHash):
+                        logger.warn('Block %s is blacklisted.' % (realHash,))
+                    else:
+                        logger.warn('Metadata for block %s is invalid.' % (blockHash,))
+                        blacklist.addToDB(blockHash)
+            else:
+                # if block didn't meet expected hash
+                tempHash = onionrcrypto.hashers.sha3_hash(content) # lazy hack, TODO use var
+                try:
+                    tempHash = tempHash.decode()
+                except AttributeError:
+                    pass
+                # Punish peer for sharing invalid block (not always malicious, but is bad regardless)
+                onionrpeers.PeerProfiles(peerUsed).addScore(-50)
+                if tempHash != 'ed55e34cb828232d6c14da0479709bfa10a0923dca2b380496e6b2ed4f7a0253':
+                    # Dumb hack for 404 response from peer. Don't log it if 404 since its likely not malicious or a critical error.
+                    logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
+                else:
+                    removeFromQueue = False # Don't remove from queue if 404
+            if removeFromQueue:
+                try:
+                    del comm_inst.blockQueue[blockHash] # remove from block queue both if success or false
+                    if count == LOG_SKIP_COUNT:
+                        logger.info('%s blocks remaining in queue' % [len(comm_inst.blockQueue)], terminal=True)
+                        count = 0
+                except KeyError:
+                    pass
+        comm_inst.currentDownloading.remove(blockHash)
+    comm_inst.decrementThreadCount('getBlocks')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/downloadblocks/shoulddownload.html b/docs/html/onionr/communicatorutils/downloadblocks/shoulddownload.html new file mode 100644 index 00000000..a69a4374 --- /dev/null +++ b/docs/html/onionr/communicatorutils/downloadblocks/shoulddownload.html @@ -0,0 +1,127 @@ + + + + + + +onionr.communicatorutils.downloadblocks.shoulddownload API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.downloadblocks.shoulddownload

+
+
+

Onionr - Private P2P Communication

+

Check if a block should be downloaded (if we already have it or its blacklisted or not)

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Check if a block should be downloaded (if we already have it or its blacklisted or not)
+'''
+'''
+    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 coredb import blockmetadb
+from onionrblocks import onionrblacklist
+
+def should_download(comm_inst, block_hash):
+    blacklist = onionrblacklist.OnionrBlackList()
+    ret_data = True
+    if block_hash in blockmetadb.get_block_list(): # Dont download block we have
+        ret_data = False
+    else:
+        if blacklist.inBlacklist(block_hash): # Dont download blacklisted block
+            ret_data = False
+    if ret_data is False:
+        # Remove block from communicator queue if it shouldnt be downloaded
+        try:
+            del comm_inst.blockQueue[block_hash]
+        except KeyError:
+            pass
+    return ret_data
+
+
+
+
+
+
+
+

Functions

+
+
+def should_download(comm_inst, block_hash) +
+
+
+
+Source code +
def should_download(comm_inst, block_hash):
+    blacklist = onionrblacklist.OnionrBlackList()
+    ret_data = True
+    if block_hash in blockmetadb.get_block_list(): # Dont download block we have
+        ret_data = False
+    else:
+        if blacklist.inBlacklist(block_hash): # Dont download blacklisted block
+            ret_data = False
+    if ret_data is False:
+        # Remove block from communicator queue if it shouldnt be downloaded
+        try:
+            del comm_inst.blockQueue[block_hash]
+        except KeyError:
+            pass
+    return ret_data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/housekeeping.html b/docs/html/onionr/communicatorutils/housekeeping.html new file mode 100644 index 00000000..6d8e7976 --- /dev/null +++ b/docs/html/onionr/communicatorutils/housekeeping.html @@ -0,0 +1,200 @@ + + + + + + +onionr.communicatorutils.housekeeping API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.housekeeping

+
+
+

Onionr - Private P2P Communication

+

Cleanup old Onionr blocks and forward secrecy keys using the communicator. Ran from a timer usually

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Cleanup old Onionr blocks and forward secrecy keys using the communicator. Ran from a timer usually
+'''
+'''
+    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/>.
+'''
+import sqlite3
+import logger
+from onionrusers import onionrusers
+from onionrutils import epoch
+from coredb import blockmetadb, dbfiles
+import onionrstorage
+from onionrstorage import removeblock
+from onionrblocks import onionrblacklist
+
+def __remove_from_upload(comm_inst, block_hash: str):
+    try:
+        comm_inst.blocksToUpload.remove(block_hash)
+    except ValueError:
+        pass
+
+def clean_old_blocks(comm_inst):
+    '''Delete old blocks if our disk allocation is full/near full, and also expired blocks'''
+    blacklist = onionrblacklist.OnionrBlackList()
+    # Delete expired blocks
+    for bHash in blockmetadb.expiredblocks.get_expired_blocks():
+        blacklist.addToDB(bHash)
+        removeblock.remove_block(bHash)
+        onionrstorage.deleteBlock(bHash)
+        __remove_from_upload(comm_inst, bHash)
+        logger.info('Deleted block: %s' % (bHash,))
+
+    while comm_inst.storage_counter.is_full():
+        oldest = blockmetadb.get_block_list()[0]
+        blacklist.addToDB(oldest)
+        removeblock.remove_block(oldest)
+        onionrstorage.deleteBlock(oldest)
+        __remove_from_upload.remove(comm_inst, oldest)
+        logger.info('Deleted block: %s' % (oldest,))
+
+    comm_inst.decrementThreadCount('clean_old_blocks')
+
+def clean_keys(comm_inst):
+    '''Delete expired forward secrecy keys'''
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+    c = conn.cursor()
+    time = epoch.get_epoch()
+    deleteKeys = []
+
+    for entry in c.execute("SELECT * FROM forwardKeys WHERE expire <= ?", (time,)):
+        logger.debug('Forward key: %s' % entry[1])
+        deleteKeys.append(entry[1])
+
+    for key in deleteKeys:
+        logger.debug('Deleting forward key %s' % key)
+        c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
+    conn.commit()
+    conn.close()
+
+    onionrusers.deleteExpiredKeys()
+
+    comm_inst.decrementThreadCount('clean_keys')
+
+
+
+
+
+
+
+

Functions

+
+
+def clean_keys(comm_inst) +
+
+

Delete expired forward secrecy keys

+
+Source code +
def clean_keys(comm_inst):
+    '''Delete expired forward secrecy keys'''
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+    c = conn.cursor()
+    time = epoch.get_epoch()
+    deleteKeys = []
+
+    for entry in c.execute("SELECT * FROM forwardKeys WHERE expire <= ?", (time,)):
+        logger.debug('Forward key: %s' % entry[1])
+        deleteKeys.append(entry[1])
+
+    for key in deleteKeys:
+        logger.debug('Deleting forward key %s' % key)
+        c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
+    conn.commit()
+    conn.close()
+
+    onionrusers.deleteExpiredKeys()
+
+    comm_inst.decrementThreadCount('clean_keys')
+
+
+
+def clean_old_blocks(comm_inst) +
+
+

Delete old blocks if our disk allocation is full/near full, and also expired blocks

+
+Source code +
def clean_old_blocks(comm_inst):
+    '''Delete old blocks if our disk allocation is full/near full, and also expired blocks'''
+    blacklist = onionrblacklist.OnionrBlackList()
+    # Delete expired blocks
+    for bHash in blockmetadb.expiredblocks.get_expired_blocks():
+        blacklist.addToDB(bHash)
+        removeblock.remove_block(bHash)
+        onionrstorage.deleteBlock(bHash)
+        __remove_from_upload(comm_inst, bHash)
+        logger.info('Deleted block: %s' % (bHash,))
+
+    while comm_inst.storage_counter.is_full():
+        oldest = blockmetadb.get_block_list()[0]
+        blacklist.addToDB(oldest)
+        removeblock.remove_block(oldest)
+        onionrstorage.deleteBlock(oldest)
+        __remove_from_upload.remove(comm_inst, oldest)
+        logger.info('Deleted block: %s' % (oldest,))
+
+    comm_inst.decrementThreadCount('clean_old_blocks')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/index.html b/docs/html/onionr/communicatorutils/index.html new file mode 100644 index 00000000..4b265539 --- /dev/null +++ b/docs/html/onionr/communicatorutils/index.html @@ -0,0 +1,135 @@ + + + + + + +onionr.communicatorutils API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils

+
+
+
+
+

Sub-modules

+
+
onionr.communicatorutils.announcenode
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.connectnewpeers
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.cooldownpeer
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.daemonqueuehandler
+
+

Onionr - P2P Anonymous Storage Network …

+
+
onionr.communicatorutils.deniableinserts
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.downloadblocks
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.housekeeping
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.lookupadders
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.lookupblocks
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.netcheck
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.onionrcommunicatortimers
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.proxypicker
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.restarttor
+
+
+
+
onionr.communicatorutils.servicecreator
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.uploadblocks
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/lookupadders.html b/docs/html/onionr/communicatorutils/lookupadders.html new file mode 100644 index 00000000..1d9bf029 --- /dev/null +++ b/docs/html/onionr/communicatorutils/lookupadders.html @@ -0,0 +1,162 @@ + + + + + + +onionr.communicatorutils.lookupadders API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.lookupadders

+
+
+

Onionr - Private P2P Communication

+

Lookup new peer transport addresses using the communicator

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Lookup new peer transport addresses using the communicator
+'''
+'''
+    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/>.
+'''
+import logger
+from onionrutils import stringvalidators
+from communicator import peeraction, onlinepeers
+from utils import gettransports
+def lookup_new_peer_transports_with_communicator(comm_inst):
+    logger.info('Looking up new addresses...')
+    tryAmount = 1
+    newPeers = []
+    transports = gettransports.get()
+
+    for i in range(tryAmount):
+        # Download new peer address list from random online peers
+        if len(newPeers) > 10000:
+            # Don't get new peers if we have too many queued up
+            break
+        peer = onlinepeers.pick_online_peer(comm_inst)
+        newAdders = peeraction.peer_action(comm_inst, peer, action='pex')
+        try:
+            newPeers = newAdders.split(',')
+        except AttributeError:
+            pass
+    else:
+        # Validate new peers are good format and not already in queue
+        invalid = []
+        for x in newPeers:
+            x = x.strip()
+            if not stringvalidators.validate_transport(x) or x in comm_inst.newPeers or x in transports:
+                # avoid adding if its our address
+                invalid.append(x)
+        for x in invalid:
+            try:
+                newPeers.remove(x)
+            except ValueError:
+                pass
+        comm_inst.newPeers.extend(newPeers)
+    comm_inst.decrementThreadCount('lookup_new_peer_transports_with_communicator')
+
+
+
+
+
+
+
+

Functions

+
+
+def lookup_new_peer_transports_with_communicator(comm_inst) +
+
+
+
+Source code +
def lookup_new_peer_transports_with_communicator(comm_inst):
+    logger.info('Looking up new addresses...')
+    tryAmount = 1
+    newPeers = []
+    transports = gettransports.get()
+
+    for i in range(tryAmount):
+        # Download new peer address list from random online peers
+        if len(newPeers) > 10000:
+            # Don't get new peers if we have too many queued up
+            break
+        peer = onlinepeers.pick_online_peer(comm_inst)
+        newAdders = peeraction.peer_action(comm_inst, peer, action='pex')
+        try:
+            newPeers = newAdders.split(',')
+        except AttributeError:
+            pass
+    else:
+        # Validate new peers are good format and not already in queue
+        invalid = []
+        for x in newPeers:
+            x = x.strip()
+            if not stringvalidators.validate_transport(x) or x in comm_inst.newPeers or x in transports:
+                # avoid adding if its our address
+                invalid.append(x)
+        for x in invalid:
+            try:
+                newPeers.remove(x)
+            except ValueError:
+                pass
+        comm_inst.newPeers.extend(newPeers)
+    comm_inst.decrementThreadCount('lookup_new_peer_transports_with_communicator')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/lookupblocks.html b/docs/html/onionr/communicatorutils/lookupblocks.html new file mode 100644 index 00000000..9f74b529 --- /dev/null +++ b/docs/html/onionr/communicatorutils/lookupblocks.html @@ -0,0 +1,237 @@ + + + + + + +onionr.communicatorutils.lookupblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.lookupblocks

+
+
+

Onionr - Private P2P Communication

+

Lookup new blocks with the communicator using a random connected peer

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Lookup new blocks with the communicator using a random connected peer
+'''
+'''
+    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/>.
+'''
+import logger, onionrproofs
+from onionrutils import stringvalidators, epoch
+from communicator import peeraction, onlinepeers
+from coredb import blockmetadb
+from utils import reconstructhash
+from onionrblocks import onionrblacklist
+blacklist = onionrblacklist.OnionrBlackList()
+def lookup_blocks_from_communicator(comm_inst):
+    logger.info('Looking up new blocks')
+    tryAmount = 2
+    newBlocks = ''
+    existingBlocks = blockmetadb.get_block_list() # List of existing saved blocks
+    triedPeers = [] # list of peers we've tried this time around
+    maxBacklog = 1560 # Max amount of *new* block hashes to have already in queue, to avoid memory exhaustion
+    lastLookupTime = 0 # Last time we looked up a particular peer's list
+    new_block_count = 0
+    for i in range(tryAmount):
+        listLookupCommand = 'getblocklist' # This is defined here to reset it each time
+        if len(comm_inst.blockQueue) >= maxBacklog:
+            break
+        if not comm_inst.isOnline:
+            break
+        # check if disk allocation is used
+        if comm_inst.storage_counter.is_full():
+            logger.debug('Not looking up new blocks due to maximum amount of allowed disk space used')
+            break
+        peer = onlinepeers.pick_online_peer(comm_inst) # select random online peer
+        # if we've already tried all the online peers this time around, stop
+        if peer in triedPeers:
+            if len(comm_inst.onlinePeers) == len(triedPeers):
+                break
+            else:
+                continue
+        triedPeers.append(peer)
+
+        # Get the last time we looked up a peer's stamp to only fetch blocks since then.
+        # Saved in memory only for privacy reasons
+        try:
+            lastLookupTime = comm_inst.dbTimestamps[peer]
+        except KeyError:
+            lastLookupTime = 0
+        else:
+            listLookupCommand += '?date=%s' % (lastLookupTime,)
+        try:
+            newBlocks = peeraction.peer_action(comm_inst, peer, listLookupCommand) # get list of new block hashes
+        except Exception as error:
+            logger.warn('Could not get new blocks from %s.' % peer, error = error)
+            newBlocks = False
+        else:
+            comm_inst.dbTimestamps[peer] = epoch.get_rounded_epoch(roundS=60)
+        if newBlocks != False:
+            # if request was a success
+            for i in newBlocks.split('\n'):
+                if stringvalidators.validate_hash(i):
+                    i = reconstructhash.reconstruct_hash(i)
+                    # if newline seperated string is valid hash
+                    if not i in existingBlocks:
+                        # if block does not exist on disk and is not already in block queue
+                        if i not in comm_inst.blockQueue:
+                            if onionrproofs.hashMeetsDifficulty(i) and not blacklist.inBlacklist(i):
+                                if len(comm_inst.blockQueue) <= 1000000:
+                                    comm_inst.blockQueue[i] = [peer] # add blocks to download queue
+                                    new_block_count += 1
+                        else:
+                            if peer not in comm_inst.blockQueue[i]:
+                                if len(comm_inst.blockQueue[i]) < 10:
+                                    comm_inst.blockQueue[i].append(peer)
+    if new_block_count > 0:
+        block_string = ""
+        if new_block_count > 1:
+            block_string = "s"
+        logger.info('Discovered %s new block%s' % (new_block_count, block_string), terminal=True)
+        comm_inst.download_blocks_timer.count = int(comm_inst.download_blocks_timer.frequency * 0.99)
+    comm_inst.decrementThreadCount('lookup_blocks_from_communicator')
+    return
+
+
+
+
+
+
+
+

Functions

+
+
+def lookup_blocks_from_communicator(comm_inst) +
+
+
+
+Source code +
def lookup_blocks_from_communicator(comm_inst):
+    logger.info('Looking up new blocks')
+    tryAmount = 2
+    newBlocks = ''
+    existingBlocks = blockmetadb.get_block_list() # List of existing saved blocks
+    triedPeers = [] # list of peers we've tried this time around
+    maxBacklog = 1560 # Max amount of *new* block hashes to have already in queue, to avoid memory exhaustion
+    lastLookupTime = 0 # Last time we looked up a particular peer's list
+    new_block_count = 0
+    for i in range(tryAmount):
+        listLookupCommand = 'getblocklist' # This is defined here to reset it each time
+        if len(comm_inst.blockQueue) >= maxBacklog:
+            break
+        if not comm_inst.isOnline:
+            break
+        # check if disk allocation is used
+        if comm_inst.storage_counter.is_full():
+            logger.debug('Not looking up new blocks due to maximum amount of allowed disk space used')
+            break
+        peer = onlinepeers.pick_online_peer(comm_inst) # select random online peer
+        # if we've already tried all the online peers this time around, stop
+        if peer in triedPeers:
+            if len(comm_inst.onlinePeers) == len(triedPeers):
+                break
+            else:
+                continue
+        triedPeers.append(peer)
+
+        # Get the last time we looked up a peer's stamp to only fetch blocks since then.
+        # Saved in memory only for privacy reasons
+        try:
+            lastLookupTime = comm_inst.dbTimestamps[peer]
+        except KeyError:
+            lastLookupTime = 0
+        else:
+            listLookupCommand += '?date=%s' % (lastLookupTime,)
+        try:
+            newBlocks = peeraction.peer_action(comm_inst, peer, listLookupCommand) # get list of new block hashes
+        except Exception as error:
+            logger.warn('Could not get new blocks from %s.' % peer, error = error)
+            newBlocks = False
+        else:
+            comm_inst.dbTimestamps[peer] = epoch.get_rounded_epoch(roundS=60)
+        if newBlocks != False:
+            # if request was a success
+            for i in newBlocks.split('\n'):
+                if stringvalidators.validate_hash(i):
+                    i = reconstructhash.reconstruct_hash(i)
+                    # if newline seperated string is valid hash
+                    if not i in existingBlocks:
+                        # if block does not exist on disk and is not already in block queue
+                        if i not in comm_inst.blockQueue:
+                            if onionrproofs.hashMeetsDifficulty(i) and not blacklist.inBlacklist(i):
+                                if len(comm_inst.blockQueue) <= 1000000:
+                                    comm_inst.blockQueue[i] = [peer] # add blocks to download queue
+                                    new_block_count += 1
+                        else:
+                            if peer not in comm_inst.blockQueue[i]:
+                                if len(comm_inst.blockQueue[i]) < 10:
+                                    comm_inst.blockQueue[i].append(peer)
+    if new_block_count > 0:
+        block_string = ""
+        if new_block_count > 1:
+            block_string = "s"
+        logger.info('Discovered %s new block%s' % (new_block_count, block_string), terminal=True)
+        comm_inst.download_blocks_timer.count = int(comm_inst.download_blocks_timer.frequency * 0.99)
+    comm_inst.decrementThreadCount('lookup_blocks_from_communicator')
+    return
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/netcheck.html b/docs/html/onionr/communicatorutils/netcheck.html new file mode 100644 index 00000000..fa091a4c --- /dev/null +++ b/docs/html/onionr/communicatorutils/netcheck.html @@ -0,0 +1,138 @@ + + + + + + +onionr.communicatorutils.netcheck API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.netcheck

+
+
+

Onionr - Private P2P Communication

+

Determine if our node is able to use Tor based on the status of a communicator instance +and the result of pinging onion http servers

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Determine if our node is able to use Tor based on the status of a communicator instance
+    and the result of pinging onion http servers
+'''
+'''
+    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/>.
+'''
+import logger
+from utils import netutils
+from onionrutils import localcommand, epoch
+from . import restarttor
+def net_check(comm_inst):
+    '''Check if we are connected to the internet or not when we can't connect to any peers'''
+    rec = False # for detecting if we have received incoming connections recently
+    if len(comm_inst.onlinePeers) == 0:
+        try:
+            if (epoch.get_epoch() - int(localcommand.local_command('/lastconnect'))) <= 60:
+                comm_inst.isOnline = True
+                rec = True
+        except ValueError:
+            pass
+        if not rec and not netutils.checkNetwork(torPort=comm_inst.proxyPort):
+            if not comm_inst.shutdown:
+                logger.warn('Network check failed, are you connected to the Internet, and is Tor working?', terminal=True)
+                restarttor.restart(comm_inst)
+                comm_inst.offlinePeers = []
+            comm_inst.isOnline = False
+        else:
+            comm_inst.isOnline = True
+    comm_inst.decrementThreadCount('net_check')
+
+
+
+
+
+
+
+

Functions

+
+
+def net_check(comm_inst) +
+
+

Check if we are connected to the internet or not when we can't connect to any peers

+
+Source code +
def net_check(comm_inst):
+    '''Check if we are connected to the internet or not when we can't connect to any peers'''
+    rec = False # for detecting if we have received incoming connections recently
+    if len(comm_inst.onlinePeers) == 0:
+        try:
+            if (epoch.get_epoch() - int(localcommand.local_command('/lastconnect'))) <= 60:
+                comm_inst.isOnline = True
+                rec = True
+        except ValueError:
+            pass
+        if not rec and not netutils.checkNetwork(torPort=comm_inst.proxyPort):
+            if not comm_inst.shutdown:
+                logger.warn('Network check failed, are you connected to the Internet, and is Tor working?', terminal=True)
+                restarttor.restart(comm_inst)
+                comm_inst.offlinePeers = []
+            comm_inst.isOnline = False
+        else:
+            comm_inst.isOnline = True
+    comm_inst.decrementThreadCount('net_check')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/onionrcommunicatortimers.html b/docs/html/onionr/communicatorutils/onionrcommunicatortimers.html new file mode 100644 index 00000000..4d399de5 --- /dev/null +++ b/docs/html/onionr/communicatorutils/onionrcommunicatortimers.html @@ -0,0 +1,247 @@ + + + + + + +onionr.communicatorutils.onionrcommunicatortimers API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.onionrcommunicatortimers

+
+
+

Onionr - Private P2P Communication

+

This file contains timer control for the communicator

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file contains timer control for the communicator
+'''
+from __future__ import annotations  # thank you python, very cool
+'''
+    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/>.
+'''
+
+import uuid
+import threading
+
+import onionrexceptions, logger
+
+from typing import TYPE_CHECKING
+from typing import Callable, NewType, Iterable
+if TYPE_CHECKING:
+    from communicator import OnionrCommunicatorDaemon
+
+CallFreqSeconds = NewType('CallFreqSeconds', int)
+
+class OnionrCommunicatorTimers:
+    def __init__(self, daemon_inst: OnionrCommunicatorDaemon, 
+                timer_function: Callable, frequency: CallFreqSeconds, 
+                make_thread:bool=True, thread_amount:int=1, max_threads:int=5, 
+                requires_peer:bool=False, my_args:Iterable=[]):
+        self.timer_function = timer_function
+        self.frequency = frequency
+        self.thread_amount = thread_amount
+        self.make_thread = make_thread
+        self.requires_peer = requires_peer
+        self.daemon_inst = daemon_inst
+        self.max_threads = max_threads
+        self.args = my_args
+
+        self.daemon_inst.timers.append(self)
+        self.count = 0
+
+    def processTimer(self):
+
+        # mark how many instances of a thread we have (decremented at thread end)
+        try:
+            self.daemon_inst.threadCounts[self.timer_function.__name__]
+        except KeyError:
+            self.daemon_inst.threadCounts[self.timer_function.__name__] = 0
+
+        # execute thread if it is time, and we are not missing *required* online peer
+        if self.count == self.frequency and not self.daemon_inst.shutdown:
+            try:
+                if self.requires_peer and len(self.daemon_inst.onlinePeers) == 0:
+                    raise onionrexceptions.OnlinePeerNeeded
+            except onionrexceptions.OnlinePeerNeeded:
+                return
+            else:
+                if self.make_thread:
+                    for i in range(self.thread_amount):
+                        if self.daemon_inst.threadCounts[self.timer_function.__name__] >= self.max_threads:
+                            logger.debug('%s is currently using the maximum number of threads, not starting another.' % self.timer_function.__name__, terminal=True)
+                        else:
+                            self.daemon_inst.threadCounts[self.timer_function.__name__] += 1
+                            newThread = threading.Thread(target=self.timer_function, args=self.args, daemon=True, 
+                                                        name=self.timer_function.__name__ + ' - ' + str(uuid.uuid4()))
+                            newThread.start()
+                else:
+                    self.timer_function()
+            self.count = -1 # negative 1 because its incremented at bottom
+        self.count += 1
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class OnionrCommunicatorTimers +(daemon_inst, timer_function, frequency, make_thread=True, thread_amount=1, max_threads=5, requires_peer=False, my_args=[]) +
+
+
+
+Source code +
class OnionrCommunicatorTimers:
+    def __init__(self, daemon_inst: OnionrCommunicatorDaemon, 
+                timer_function: Callable, frequency: CallFreqSeconds, 
+                make_thread:bool=True, thread_amount:int=1, max_threads:int=5, 
+                requires_peer:bool=False, my_args:Iterable=[]):
+        self.timer_function = timer_function
+        self.frequency = frequency
+        self.thread_amount = thread_amount
+        self.make_thread = make_thread
+        self.requires_peer = requires_peer
+        self.daemon_inst = daemon_inst
+        self.max_threads = max_threads
+        self.args = my_args
+
+        self.daemon_inst.timers.append(self)
+        self.count = 0
+
+    def processTimer(self):
+
+        # mark how many instances of a thread we have (decremented at thread end)
+        try:
+            self.daemon_inst.threadCounts[self.timer_function.__name__]
+        except KeyError:
+            self.daemon_inst.threadCounts[self.timer_function.__name__] = 0
+
+        # execute thread if it is time, and we are not missing *required* online peer
+        if self.count == self.frequency and not self.daemon_inst.shutdown:
+            try:
+                if self.requires_peer and len(self.daemon_inst.onlinePeers) == 0:
+                    raise onionrexceptions.OnlinePeerNeeded
+            except onionrexceptions.OnlinePeerNeeded:
+                return
+            else:
+                if self.make_thread:
+                    for i in range(self.thread_amount):
+                        if self.daemon_inst.threadCounts[self.timer_function.__name__] >= self.max_threads:
+                            logger.debug('%s is currently using the maximum number of threads, not starting another.' % self.timer_function.__name__, terminal=True)
+                        else:
+                            self.daemon_inst.threadCounts[self.timer_function.__name__] += 1
+                            newThread = threading.Thread(target=self.timer_function, args=self.args, daemon=True, 
+                                                        name=self.timer_function.__name__ + ' - ' + str(uuid.uuid4()))
+                            newThread.start()
+                else:
+                    self.timer_function()
+            self.count = -1 # negative 1 because its incremented at bottom
+        self.count += 1
+
+

Methods

+
+
+def processTimer(self) +
+
+
+
+Source code +
def processTimer(self):
+
+    # mark how many instances of a thread we have (decremented at thread end)
+    try:
+        self.daemon_inst.threadCounts[self.timer_function.__name__]
+    except KeyError:
+        self.daemon_inst.threadCounts[self.timer_function.__name__] = 0
+
+    # execute thread if it is time, and we are not missing *required* online peer
+    if self.count == self.frequency and not self.daemon_inst.shutdown:
+        try:
+            if self.requires_peer and len(self.daemon_inst.onlinePeers) == 0:
+                raise onionrexceptions.OnlinePeerNeeded
+        except onionrexceptions.OnlinePeerNeeded:
+            return
+        else:
+            if self.make_thread:
+                for i in range(self.thread_amount):
+                    if self.daemon_inst.threadCounts[self.timer_function.__name__] >= self.max_threads:
+                        logger.debug('%s is currently using the maximum number of threads, not starting another.' % self.timer_function.__name__, terminal=True)
+                    else:
+                        self.daemon_inst.threadCounts[self.timer_function.__name__] += 1
+                        newThread = threading.Thread(target=self.timer_function, args=self.args, daemon=True, 
+                                                    name=self.timer_function.__name__ + ' - ' + str(uuid.uuid4()))
+                        newThread.start()
+            else:
+                self.timer_function()
+        self.count = -1 # negative 1 because its incremented at bottom
+    self.count += 1
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/proxypicker.html b/docs/html/onionr/communicatorutils/proxypicker.html new file mode 100644 index 00000000..fb18a7d4 --- /dev/null +++ b/docs/html/onionr/communicatorutils/proxypicker.html @@ -0,0 +1,107 @@ + + + + + + +onionr.communicatorutils.proxypicker API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.proxypicker

+
+
+

Onionr - Private P2P Communication

+

Just picks a proxy to use based on a peer's address

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Just picks a proxy to use based on a peer's address
+'''
+'''
+    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 pick_proxy(peer_address):
+    if peer_address.endswith('.onion'):
+        return 'tor'
+    elif peer_address.endswith('.i2p'):
+        return 'i2p'
+    raise ValueError("Peer address was not string ending with acceptable value")
+
+
+
+
+
+
+
+

Functions

+
+
+def pick_proxy(peer_address) +
+
+
+
+Source code +
def pick_proxy(peer_address):
+    if peer_address.endswith('.onion'):
+        return 'tor'
+    elif peer_address.endswith('.i2p'):
+        return 'i2p'
+    raise ValueError("Peer address was not string ending with acceptable value")
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/restarttor.html b/docs/html/onionr/communicatorutils/restarttor.html new file mode 100644 index 00000000..02688945 --- /dev/null +++ b/docs/html/onionr/communicatorutils/restarttor.html @@ -0,0 +1,82 @@ + + + + + + +onionr.communicatorutils.restarttor API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.restarttor

+
+
+
+Source code +
import netcontroller
+def restart(comm_inst):
+    net = comm_inst.shared_state.get(netcontroller.NetController)
+    net.killTor()
+    net.startTor()
+
+
+
+
+
+
+
+

Functions

+
+
+def restart(comm_inst) +
+
+
+
+Source code +
def restart(comm_inst):
+    net = comm_inst.shared_state.get(netcontroller.NetController)
+    net.killTor()
+    net.startTor()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/servicecreator.html b/docs/html/onionr/communicatorutils/servicecreator.html new file mode 100644 index 00000000..93b6f909 --- /dev/null +++ b/docs/html/onionr/communicatorutils/servicecreator.html @@ -0,0 +1,140 @@ + + + + + + +onionr.communicatorutils.servicecreator API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.servicecreator

+
+
+

Onionr - Private P2P Communication

+

Creates an onionr direct connection service by scanning all connection blocks

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Creates an onionr direct connection service by scanning all connection 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/>.
+'''
+import communicator
+from onionrblocks import onionrblockapi
+import logger
+from onionrutils import stringvalidators, bytesconverter
+from coredb import blockmetadb
+from onionrservices import server_exists
+def service_creator(daemon):
+    assert isinstance(daemon, communicator.OnionrCommunicatorDaemon)
+    
+    # Find socket connection blocks
+    # TODO cache blocks and only look at recently received ones
+    con_blocks = blockmetadb.get_blocks_by_type('con')
+    for b in con_blocks:
+        if not b in daemon.active_services:
+            bl = onionrblockapi.Block(b, decrypt=True)
+            bs = bytesconverter.bytes_to_str(bl.bcontent) + '.onion'
+            if server_exists(bl.signer):
+                continue
+            if stringvalidators.validate_pub_key(bl.signer) and stringvalidators.validate_transport(bs):
+                signer = bytesconverter.bytes_to_str(bl.signer)
+                daemon.active_services.append(b)
+                daemon.active_services.append(signer)
+                if not daemon.services.create_server(signer, bs, daemon):
+                    daemon.active_services.remove(b)
+                    daemon.active_services.remove(signer)
+    daemon.decrementThreadCount('service_creator')
+
+
+
+
+
+
+
+

Functions

+
+
+def service_creator(daemon) +
+
+
+
+Source code +
def service_creator(daemon):
+    assert isinstance(daemon, communicator.OnionrCommunicatorDaemon)
+    
+    # Find socket connection blocks
+    # TODO cache blocks and only look at recently received ones
+    con_blocks = blockmetadb.get_blocks_by_type('con')
+    for b in con_blocks:
+        if not b in daemon.active_services:
+            bl = onionrblockapi.Block(b, decrypt=True)
+            bs = bytesconverter.bytes_to_str(bl.bcontent) + '.onion'
+            if server_exists(bl.signer):
+                continue
+            if stringvalidators.validate_pub_key(bl.signer) and stringvalidators.validate_transport(bs):
+                signer = bytesconverter.bytes_to_str(bl.signer)
+                daemon.active_services.append(b)
+                daemon.active_services.append(signer)
+                if not daemon.services.create_server(signer, bs, daemon):
+                    daemon.active_services.remove(b)
+                    daemon.active_services.remove(signer)
+    daemon.decrementThreadCount('service_creator')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/uploadblocks/index.html b/docs/html/onionr/communicatorutils/uploadblocks/index.html new file mode 100644 index 00000000..d83db240 --- /dev/null +++ b/docs/html/onionr/communicatorutils/uploadblocks/index.html @@ -0,0 +1,242 @@ + + + + + + +onionr.communicatorutils.uploadblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.uploadblocks

+
+
+

Onionr - Private P2P Communication

+

Upload blocks in the upload queue to peers from the communicator

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Upload blocks in the upload queue to peers from the communicator
+'''
+from __future__ import annotations
+'''
+    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 typing import Union, TYPE_CHECKING
+import logger
+from communicatorutils import proxypicker
+import onionrexceptions
+from onionrblocks import onionrblockapi as block
+from onionrutils import localcommand, stringvalidators, basicrequests
+from communicator import onlinepeers
+import onionrcrypto
+from . import sessionmanager
+
+def upload_blocks_from_communicator(comm_inst: OnionrCommunicatorDaemon):
+    """Accepts a communicator instance and uploads blocks from its upload queue"""
+    """when inserting a block, we try to upload
+     it to a few peers to add some deniability & increase functionality"""
+    TIMER_NAME = "upload_blocks_from_communicator"
+
+    session_manager: sessionmanager.BlockUploadSessionManager = comm_inst.shared_state.get(sessionmanager.BlockUploadSessionManager)
+    triedPeers = []
+    finishedUploads = []
+    comm_inst.blocksToUpload = onionrcrypto.cryptoutils.random_shuffle(comm_inst.blocksToUpload)
+    if len(comm_inst.blocksToUpload) != 0:
+        for bl in comm_inst.blocksToUpload:
+            if not stringvalidators.validate_hash(bl):
+                logger.warn('Requested to upload invalid block', terminal=True)
+                comm_inst.decrementThreadCount(TIMER_NAME)
+                return
+            session = session_manager.add_session(bl)
+            for i in range(min(len(comm_inst.onlinePeers), 6)):
+                peer = onlinepeers.pick_online_peer(comm_inst)
+                try:
+                    session.peer_exists[peer]
+                    continue
+                except KeyError:
+                    pass
+                try:
+                    if session.peer_fails[peer] > 3: continue
+                except KeyError:
+                    pass
+                if peer in triedPeers: continue
+                triedPeers.append(peer)
+                url = f'http://{peer}/upload'
+                try:
+                    data = block.Block(bl).getRaw()
+                except onionrexceptions.NoDataAvailable:
+                    finishedUploads.append(bl)
+                    break
+                proxyType = proxypicker.pick_proxy(peer)
+                logger.info(f"Uploading block {bl[:8]} to {peer}", terminal=True)
+                resp = basicrequests.do_post_request(url, data=data, proxyType=proxyType, content_type='application/octet-stream')
+                if not resp == False:
+                    if resp == 'success':
+                        session.success()
+                        session.peer_exists[peer] = True
+                    elif resp == 'exists':
+                        session.success()
+                        session.peer_exists[peer] = True
+                    else:
+                        session.fail()
+                        session.fail_peer(peer)
+                        comm_inst.getPeerProfileInstance(peer).addScore(-5)
+                        logger.warn(f'Failed to upload {bl[:8]}, reason: {resp[:15]}', terminal=True)
+                else:
+                    session.fail()
+        session_manager.clean_session()
+    for x in finishedUploads:
+        try:
+            comm_inst.blocksToUpload.remove(x)
+        except ValueError:
+            pass
+    comm_inst.decrementThreadCount(TIMER_NAME)
+
+
+
+

Sub-modules

+
+
onionr.communicatorutils.uploadblocks.session
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils.uploadblocks.sessionmanager
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+

Functions

+
+
+def upload_blocks_from_communicator(comm_inst) +
+
+

Accepts a communicator instance and uploads blocks from its upload queue

+
+Source code +
def upload_blocks_from_communicator(comm_inst: OnionrCommunicatorDaemon):
+    """Accepts a communicator instance and uploads blocks from its upload queue"""
+    """when inserting a block, we try to upload
+     it to a few peers to add some deniability & increase functionality"""
+    TIMER_NAME = "upload_blocks_from_communicator"
+
+    session_manager: sessionmanager.BlockUploadSessionManager = comm_inst.shared_state.get(sessionmanager.BlockUploadSessionManager)
+    triedPeers = []
+    finishedUploads = []
+    comm_inst.blocksToUpload = onionrcrypto.cryptoutils.random_shuffle(comm_inst.blocksToUpload)
+    if len(comm_inst.blocksToUpload) != 0:
+        for bl in comm_inst.blocksToUpload:
+            if not stringvalidators.validate_hash(bl):
+                logger.warn('Requested to upload invalid block', terminal=True)
+                comm_inst.decrementThreadCount(TIMER_NAME)
+                return
+            session = session_manager.add_session(bl)
+            for i in range(min(len(comm_inst.onlinePeers), 6)):
+                peer = onlinepeers.pick_online_peer(comm_inst)
+                try:
+                    session.peer_exists[peer]
+                    continue
+                except KeyError:
+                    pass
+                try:
+                    if session.peer_fails[peer] > 3: continue
+                except KeyError:
+                    pass
+                if peer in triedPeers: continue
+                triedPeers.append(peer)
+                url = f'http://{peer}/upload'
+                try:
+                    data = block.Block(bl).getRaw()
+                except onionrexceptions.NoDataAvailable:
+                    finishedUploads.append(bl)
+                    break
+                proxyType = proxypicker.pick_proxy(peer)
+                logger.info(f"Uploading block {bl[:8]} to {peer}", terminal=True)
+                resp = basicrequests.do_post_request(url, data=data, proxyType=proxyType, content_type='application/octet-stream')
+                if not resp == False:
+                    if resp == 'success':
+                        session.success()
+                        session.peer_exists[peer] = True
+                    elif resp == 'exists':
+                        session.success()
+                        session.peer_exists[peer] = True
+                    else:
+                        session.fail()
+                        session.fail_peer(peer)
+                        comm_inst.getPeerProfileInstance(peer).addScore(-5)
+                        logger.warn(f'Failed to upload {bl[:8]}, reason: {resp[:15]}', terminal=True)
+                else:
+                    session.fail()
+        session_manager.clean_session()
+    for x in finishedUploads:
+        try:
+            comm_inst.blocksToUpload.remove(x)
+        except ValueError:
+            pass
+    comm_inst.decrementThreadCount(TIMER_NAME)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/uploadblocks/session.html b/docs/html/onionr/communicatorutils/uploadblocks/session.html new file mode 100644 index 00000000..d6d7c652 --- /dev/null +++ b/docs/html/onionr/communicatorutils/uploadblocks/session.html @@ -0,0 +1,204 @@ + + + + + + +onionr.communicatorutils.uploadblocks.session API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.uploadblocks.session

+
+
+

Onionr - Private P2P Communication

+

Virtual upload "sessions" for blocks

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    Virtual upload "sessions" for blocks
+"""
+from __future__ import annotations
+"""
+    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 typing import Union
+
+from onionrutils import stringvalidators
+from onionrutils import bytesconverter
+from onionrutils import epoch
+from utils import reconstructhash
+
+class UploadSession:
+    """Manages statistics for an Onionr block upload session
+    
+    accepting a block hash (incl. unpadded) as an argument""" 
+    def __init__(self, block_hash: Union[str, bytes]):
+        block_hash = bytesconverter.bytes_to_str(block_hash)
+        block_hash = reconstructhash.reconstruct_hash(block_hash)
+        if not stringvalidators.validate_hash(block_hash): raise ValueError
+
+        self.start_time = epoch.get_epoch()
+        self.block_hash = reconstructhash.deconstruct_hash(block_hash)
+        self.total_fail_count: int = 0
+        self.total_success_count: int = 0
+        self.peer_fails = {}
+        self.peer_exists = {}
+    
+    def fail_peer(self, peer):
+        try:
+            self.peer_fails[peer] += 1
+        except KeyError:
+            self.peer_fails[peer] = 0
+    
+    def fail(self):
+        self.total_fail_count += 1
+    
+    def success(self):
+        self.total_success_count += 1
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class UploadSession +(block_hash) +
+
+

Manages statistics for an Onionr block upload session

+

accepting a block hash (incl. unpadded) as an argument

+
+Source code +
class UploadSession:
+    """Manages statistics for an Onionr block upload session
+    
+    accepting a block hash (incl. unpadded) as an argument""" 
+    def __init__(self, block_hash: Union[str, bytes]):
+        block_hash = bytesconverter.bytes_to_str(block_hash)
+        block_hash = reconstructhash.reconstruct_hash(block_hash)
+        if not stringvalidators.validate_hash(block_hash): raise ValueError
+
+        self.start_time = epoch.get_epoch()
+        self.block_hash = reconstructhash.deconstruct_hash(block_hash)
+        self.total_fail_count: int = 0
+        self.total_success_count: int = 0
+        self.peer_fails = {}
+        self.peer_exists = {}
+    
+    def fail_peer(self, peer):
+        try:
+            self.peer_fails[peer] += 1
+        except KeyError:
+            self.peer_fails[peer] = 0
+    
+    def fail(self):
+        self.total_fail_count += 1
+    
+    def success(self):
+        self.total_success_count += 1
+
+

Methods

+
+
+def fail(self) +
+
+
+
+Source code +
def fail(self):
+    self.total_fail_count += 1
+
+
+
+def fail_peer(self, peer) +
+
+
+
+Source code +
def fail_peer(self, peer):
+    try:
+        self.peer_fails[peer] += 1
+    except KeyError:
+        self.peer_fails[peer] = 0
+
+
+
+def success(self) +
+
+
+
+Source code +
def success(self):
+    self.total_success_count += 1
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/communicatorutils/uploadblocks/sessionmanager.html b/docs/html/onionr/communicatorutils/uploadblocks/sessionmanager.html new file mode 100644 index 00000000..1db7b4db --- /dev/null +++ b/docs/html/onionr/communicatorutils/uploadblocks/sessionmanager.html @@ -0,0 +1,296 @@ + + + + + + +onionr.communicatorutils.uploadblocks.sessionmanager API documentation + + + + + + + + + +
+
+
+

Module onionr.communicatorutils.uploadblocks.sessionmanager

+
+
+

Onionr - Private P2P Communication

+

Manager for upload 'sessions'

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    Manager for upload 'sessions'
+"""
+from __future__ import annotations
+"""
+    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 typing import Iterable, Union
+
+from onionrutils import bytesconverter
+from onionrutils import localcommand
+from etc import onionrvalues
+from etc import waitforsetvar
+from utils import reconstructhash
+
+from . import session
+
+class BlockUploadSessionManager:
+    """Holds block UploadSession instances. Optionally accepts iterable of sessions to added on init
+
+    Arguments: old_session: iterable of old UploadSession objects"""
+    def __init__(self, old_sessions:Iterable=None):
+        #self._too_many: TooMany = None
+        if old_sessions is None:
+            self.sessions = []
+        else:
+            self.sessions = old_session
+    
+    def add_session(self, session_or_block: Union(str, bytes, session.UploadSession))->session.UploadSession:
+        """Create (or add existing) block upload session from a str/bytes block hex hash, existing UploadSession"""
+        if isinstance(session_or_block, session.UploadSession): 
+            if not session_or_block in self.sessions:
+                self.sessions.append(session_or_block)
+            return session_or_block
+        try:
+            return self.get_session(session_or_block)
+        except KeyError:
+            pass
+        # convert bytes hash to str
+        if isinstance(session_or_block, bytes): session_or_block = bytesconverter.bytes_to_str(session_or_block)
+        # intentionally not elif
+        if isinstance(session_or_block, str):
+            new_session = session.UploadSession(session_or_block)
+            self.sessions.append(new_session)
+            return new_session
+
+    def get_session(self, block_hash: Union(str, bytes))->session.UploadSession:
+        block_hash = reconstructhash.deconstruct_hash(bytesconverter.bytes_to_str(block_hash))
+        for session in self.sessions:
+            if session.block_hash == block_hash: return session
+        raise KeyError
+
+    def clean_session(self, specific_session: Union[str, UploadSession]=None):
+        comm_inst: OnionrCommunicatorDaemon = self._too_many.get_by_string("OnionrCommunicatorDaemon")
+        sessions_to_delete = []
+        if comm_inst.getUptime() < 120: return
+        for session in self.sessions:
+            if (session.total_success_count / len(comm_inst.onlinePeers)) >= onionrvalues.MIN_BLOCK_UPLOAD_PEER_PERCENT:
+                sessions_to_delete.append(session)
+        for session in sessions_to_delete:
+            self.sessions.remove(session)
+            # TODO cleanup to one round of search
+            # Remove the blocks from the sessions, upload list, and waitforshare list
+            try:
+                comm_inst.blocksToUpload.remove(reconstructhash.reconstruct_hash(session.block_hash))
+            except ValueError:
+                pass
+            try:
+                comm_inst.blocksToUpload.remove(session.block_hash)
+            except ValueError:
+                pass
+            localcommand.local_command('waitforshare/{session.block_hash}')
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class BlockUploadSessionManager +(old_sessions=None) +
+
+

Holds block UploadSession instances. Optionally accepts iterable of sessions to added on init

+

Arguments: old_session: iterable of old UploadSession objects

+
+Source code +
class BlockUploadSessionManager:
+    """Holds block UploadSession instances. Optionally accepts iterable of sessions to added on init
+
+    Arguments: old_session: iterable of old UploadSession objects"""
+    def __init__(self, old_sessions:Iterable=None):
+        #self._too_many: TooMany = None
+        if old_sessions is None:
+            self.sessions = []
+        else:
+            self.sessions = old_session
+    
+    def add_session(self, session_or_block: Union(str, bytes, session.UploadSession))->session.UploadSession:
+        """Create (or add existing) block upload session from a str/bytes block hex hash, existing UploadSession"""
+        if isinstance(session_or_block, session.UploadSession): 
+            if not session_or_block in self.sessions:
+                self.sessions.append(session_or_block)
+            return session_or_block
+        try:
+            return self.get_session(session_or_block)
+        except KeyError:
+            pass
+        # convert bytes hash to str
+        if isinstance(session_or_block, bytes): session_or_block = bytesconverter.bytes_to_str(session_or_block)
+        # intentionally not elif
+        if isinstance(session_or_block, str):
+            new_session = session.UploadSession(session_or_block)
+            self.sessions.append(new_session)
+            return new_session
+
+    def get_session(self, block_hash: Union(str, bytes))->session.UploadSession:
+        block_hash = reconstructhash.deconstruct_hash(bytesconverter.bytes_to_str(block_hash))
+        for session in self.sessions:
+            if session.block_hash == block_hash: return session
+        raise KeyError
+
+    def clean_session(self, specific_session: Union[str, UploadSession]=None):
+        comm_inst: OnionrCommunicatorDaemon = self._too_many.get_by_string("OnionrCommunicatorDaemon")
+        sessions_to_delete = []
+        if comm_inst.getUptime() < 120: return
+        for session in self.sessions:
+            if (session.total_success_count / len(comm_inst.onlinePeers)) >= onionrvalues.MIN_BLOCK_UPLOAD_PEER_PERCENT:
+                sessions_to_delete.append(session)
+        for session in sessions_to_delete:
+            self.sessions.remove(session)
+            # TODO cleanup to one round of search
+            # Remove the blocks from the sessions, upload list, and waitforshare list
+            try:
+                comm_inst.blocksToUpload.remove(reconstructhash.reconstruct_hash(session.block_hash))
+            except ValueError:
+                pass
+            try:
+                comm_inst.blocksToUpload.remove(session.block_hash)
+            except ValueError:
+                pass
+            localcommand.local_command('waitforshare/{session.block_hash}')
+
+

Methods

+
+
+def add_session(self, session_or_block) +
+
+

Create (or add existing) block upload session from a str/bytes block hex hash, existing UploadSession

+
+Source code +
def add_session(self, session_or_block: Union(str, bytes, session.UploadSession))->session.UploadSession:
+    """Create (or add existing) block upload session from a str/bytes block hex hash, existing UploadSession"""
+    if isinstance(session_or_block, session.UploadSession): 
+        if not session_or_block in self.sessions:
+            self.sessions.append(session_or_block)
+        return session_or_block
+    try:
+        return self.get_session(session_or_block)
+    except KeyError:
+        pass
+    # convert bytes hash to str
+    if isinstance(session_or_block, bytes): session_or_block = bytesconverter.bytes_to_str(session_or_block)
+    # intentionally not elif
+    if isinstance(session_or_block, str):
+        new_session = session.UploadSession(session_or_block)
+        self.sessions.append(new_session)
+        return new_session
+
+
+
+def clean_session(self, specific_session=None) +
+
+
+
+Source code +
def clean_session(self, specific_session: Union[str, UploadSession]=None):
+    comm_inst: OnionrCommunicatorDaemon = self._too_many.get_by_string("OnionrCommunicatorDaemon")
+    sessions_to_delete = []
+    if comm_inst.getUptime() < 120: return
+    for session in self.sessions:
+        if (session.total_success_count / len(comm_inst.onlinePeers)) >= onionrvalues.MIN_BLOCK_UPLOAD_PEER_PERCENT:
+            sessions_to_delete.append(session)
+    for session in sessions_to_delete:
+        self.sessions.remove(session)
+        # TODO cleanup to one round of search
+        # Remove the blocks from the sessions, upload list, and waitforshare list
+        try:
+            comm_inst.blocksToUpload.remove(reconstructhash.reconstruct_hash(session.block_hash))
+        except ValueError:
+            pass
+        try:
+            comm_inst.blocksToUpload.remove(session.block_hash)
+        except ValueError:
+            pass
+        localcommand.local_command('waitforshare/{session.block_hash}')
+
+
+
+def get_session(self, block_hash) +
+
+
+
+Source code +
def get_session(self, block_hash: Union(str, bytes))->session.UploadSession:
+    block_hash = reconstructhash.deconstruct_hash(bytesconverter.bytes_to_str(block_hash))
+    for session in self.sessions:
+        if session.block_hash == block_hash: return session
+    raise KeyError
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/config.html b/docs/html/onionr/config.html new file mode 100644 index 00000000..ccc061e9 --- /dev/null +++ b/docs/html/onionr/config.html @@ -0,0 +1,421 @@ + + + + + + +onionr.config API documentation + + + + + + + + + +
+
+
+

Module onionr.config

+
+
+

Onionr - Private P2P Communication

+

This file deals with configuration management.

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file deals with configuration management.
+'''
+'''
+    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/>.
+'''
+
+import os, json, logger
+import filepaths
+
+_configfile = filepaths.config_file
+_config = {}
+
+def get(key, default = None, save = False):
+    '''
+        Gets the key from configuration, or returns `default`
+    '''
+
+    key = str(key).split('.')
+    data = _config
+
+    last = key.pop()
+
+    for item in key:
+        if (not item in data) or (not type(data[item]) == dict):
+            return default
+        data = data[item]
+
+    if not last in data:
+        if save:
+            set(key, default, savefile = True)
+        return default
+
+    return data[last]
+
+def set(key, value = None, savefile = False):
+    '''
+        Sets the key in configuration to `value`
+    '''
+
+    global _config
+
+    key = str(key).split('.')
+    data = _config
+
+    last = key.pop()
+
+    for item in key:
+        if (not item in data) or (not type(data[item]) == dict):
+            data[item] = dict()
+        data = data[item]
+
+    if value is None:
+        del data[last]
+    else:
+        data[last] = value
+
+    if savefile:
+        save()
+
+def is_set(key):
+    key = str(key).split('.')
+    data = _config
+
+    last = key.pop()
+
+    for item in key:
+        if (not item in data) or (not type(data[item]) == dict):
+            return False
+        data = data[item]
+
+    if not last in data:
+        return False
+
+    return True
+
+def check():
+    '''
+        Checks if the configuration file exists, creates it if not
+    '''
+
+    if not os.path.exists(os.path.dirname(get_config_file())):
+        os.makedirs(os.path.dirname(get_config_file()))
+
+def save():
+    '''
+        Saves the configuration data to the configuration file
+    '''
+
+    check()
+    try:
+        with open(get_config_file(), 'w', encoding="utf8") as configfile:
+            json.dump(get_config(), configfile, indent=2)
+    except json.JSONDecodeError:
+        logger.warn('Failed to write to configuration file.')
+
+def reload():
+    '''
+        Reloads the configuration data in memory from the file
+    '''
+    check()
+    try:
+        with open(get_config_file(), 'r', encoding="utf8") as configfile:
+            set_config(json.loads(configfile.read()))
+    except (FileNotFoundError, json.JSONDecodeError) as e:
+        pass
+        #logger.debug('Failed to parse configuration file.')
+
+def get_config():
+    '''
+        Gets the entire configuration as an array
+    '''
+    return _config
+
+def set_config(config):
+    '''
+        Sets the configuration to the array in arguments
+    '''
+    global _config
+    _config = config
+
+def get_config_file():
+    '''
+        Returns the absolute path to the configuration file
+    '''
+    return _configfile
+
+def set_config_file(configfile):
+    '''
+        Sets the path to the configuration file
+    '''
+    global _configfile
+    _configfile = os.abs.abspath(configfile)
+
+
+
+
+
+
+
+

Functions

+
+
+def check() +
+
+

Checks if the configuration file exists, creates it if not

+
+Source code +
def check():
+    '''
+        Checks if the configuration file exists, creates it if not
+    '''
+
+    if not os.path.exists(os.path.dirname(get_config_file())):
+        os.makedirs(os.path.dirname(get_config_file()))
+
+
+
+def get(key, default=None, save=False) +
+
+

Gets the key from configuration, or returns default

+
+Source code +
def get(key, default = None, save = False):
+    '''
+        Gets the key from configuration, or returns `default`
+    '''
+
+    key = str(key).split('.')
+    data = _config
+
+    last = key.pop()
+
+    for item in key:
+        if (not item in data) or (not type(data[item]) == dict):
+            return default
+        data = data[item]
+
+    if not last in data:
+        if save:
+            set(key, default, savefile = True)
+        return default
+
+    return data[last]
+
+
+
+def get_config() +
+
+

Gets the entire configuration as an array

+
+Source code +
def get_config():
+    '''
+        Gets the entire configuration as an array
+    '''
+    return _config
+
+
+
+def get_config_file() +
+
+

Returns the absolute path to the configuration file

+
+Source code +
def get_config_file():
+    '''
+        Returns the absolute path to the configuration file
+    '''
+    return _configfile
+
+
+
+def is_set(key) +
+
+
+
+Source code +
def is_set(key):
+    key = str(key).split('.')
+    data = _config
+
+    last = key.pop()
+
+    for item in key:
+        if (not item in data) or (not type(data[item]) == dict):
+            return False
+        data = data[item]
+
+    if not last in data:
+        return False
+
+    return True
+
+
+
+def reload() +
+
+

Reloads the configuration data in memory from the file

+
+Source code +
def reload():
+    '''
+        Reloads the configuration data in memory from the file
+    '''
+    check()
+    try:
+        with open(get_config_file(), 'r', encoding="utf8") as configfile:
+            set_config(json.loads(configfile.read()))
+    except (FileNotFoundError, json.JSONDecodeError) as e:
+        pass
+
+
+
+def save() +
+
+

Saves the configuration data to the configuration file

+
+Source code +
def save():
+    '''
+        Saves the configuration data to the configuration file
+    '''
+
+    check()
+    try:
+        with open(get_config_file(), 'w', encoding="utf8") as configfile:
+            json.dump(get_config(), configfile, indent=2)
+    except json.JSONDecodeError:
+        logger.warn('Failed to write to configuration file.')
+
+
+
+def set(key, value=None, savefile=False) +
+
+

Sets the key in configuration to value

+
+Source code +
def set(key, value = None, savefile = False):
+    '''
+        Sets the key in configuration to `value`
+    '''
+
+    global _config
+
+    key = str(key).split('.')
+    data = _config
+
+    last = key.pop()
+
+    for item in key:
+        if (not item in data) or (not type(data[item]) == dict):
+            data[item] = dict()
+        data = data[item]
+
+    if value is None:
+        del data[last]
+    else:
+        data[last] = value
+
+    if savefile:
+        save()
+
+
+
+def set_config(config) +
+
+

Sets the configuration to the array in arguments

+
+Source code +
def set_config(config):
+    '''
+        Sets the configuration to the array in arguments
+    '''
+    global _config
+    _config = config
+
+
+
+def set_config_file(configfile) +
+
+

Sets the path to the configuration file

+
+Source code +
def set_config_file(configfile):
+    '''
+        Sets the path to the configuration file
+    '''
+    global _configfile
+    _configfile = os.abs.abspath(configfile)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/blockmetadb/add.html b/docs/html/onionr/coredb/blockmetadb/add.html new file mode 100644 index 00000000..2722000c --- /dev/null +++ b/docs/html/onionr/coredb/blockmetadb/add.html @@ -0,0 +1,138 @@ + + + + + + +onionr.coredb.blockmetadb.add API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.blockmetadb.add

+
+
+

Onionr - Private P2P Communication

+

Add an entry to the block metadata database

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Add an entry to the block metadata database
+'''
+'''
+    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/>.
+'''
+import os, sqlite3, secrets
+from onionrutils import epoch, blockmetadata
+from .. import dbfiles
+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):
+        return
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    c = conn.cursor()
+    currentTime = epoch.get_epoch() + secrets.randbelow(301)
+    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()
+
+
+
+
+
+
+
+

Functions

+
+
+def add_to_block_DB(newHash, selfInsert=False, dataSaved=False) +
+
+

Add a hash value to the block db

+

Should be in hex format!

+
+Source code +
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):
+        return
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    c = conn.cursor()
+    currentTime = epoch.get_epoch() + secrets.randbelow(301)
+    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()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/blockmetadb/expiredblocks.html b/docs/html/onionr/coredb/blockmetadb/expiredblocks.html new file mode 100644 index 00000000..952cd175 --- /dev/null +++ b/docs/html/onionr/coredb/blockmetadb/expiredblocks.html @@ -0,0 +1,127 @@ + + + + + + +onionr.coredb.blockmetadb.expiredblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.blockmetadb.expiredblocks

+
+
+

Onionr - Private P2P Communication

+

Get a list of expired blocks still stored

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Get a list of expired blocks still stored
+'''
+'''
+    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/>.
+'''
+import sqlite3
+from onionrutils import epoch
+from .. import dbfiles
+def get_expired_blocks():
+    '''Returns a list of expired blocks'''
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    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
+
+
+
+
+
+
+
+

Functions

+
+
+def get_expired_blocks() +
+
+

Returns a list of expired blocks

+
+Source code +
def get_expired_blocks():
+    '''Returns a list of expired blocks'''
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    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
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/blockmetadb/index.html b/docs/html/onionr/coredb/blockmetadb/index.html new file mode 100644 index 00000000..74cda827 --- /dev/null +++ b/docs/html/onionr/coredb/blockmetadb/index.html @@ -0,0 +1,251 @@ + + + + + + +onionr.coredb.blockmetadb API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.blockmetadb

+
+
+

Onionr - Private P2P Communication

+

This module works with information relating to blocks stored on the node

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This module works with information relating to blocks stored on the node
+'''
+'''
+    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/>.
+'''
+import sqlite3
+from . import expiredblocks, updateblockinfo, add
+from .. import dbfiles
+update_block_info = updateblockinfo.update_block_info
+add_to_block_DB = add.add_to_block_DB
+def get_block_list(dateRec = None, unsaved = False):
+    '''
+        Get list of our blocks
+    '''
+    if dateRec == None:
+        dateRec = 0
+
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    c = conn.cursor()
+
+    execute = 'SELECT hash FROM hashes WHERE dateReceived >= ? ORDER BY dateReceived ASC;'
+    args = (dateRec,)
+    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):
+    '''
+        Returns the date a block was received
+    '''
+
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    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):
+    '''
+        Returns a list of blocks by the type
+    '''
+
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    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
+
+
+
+

Sub-modules

+
+
onionr.coredb.blockmetadb.add
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb.blockmetadb.expiredblocks
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb.blockmetadb.updateblockinfo
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+

Functions

+
+
+def get_block_date(blockHash) +
+
+

Returns the date a block was received

+
+Source code +
def get_block_date(blockHash):
+    '''
+        Returns the date a block was received
+    '''
+
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    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_block_list(dateRec=None, unsaved=False) +
+
+

Get list of our blocks

+
+Source code +
def get_block_list(dateRec = None, unsaved = False):
+    '''
+        Get list of our blocks
+    '''
+    if dateRec == None:
+        dateRec = 0
+
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    c = conn.cursor()
+
+    execute = 'SELECT hash FROM hashes WHERE dateReceived >= ? ORDER BY dateReceived ASC;'
+    args = (dateRec,)
+    rows = list()
+    for row in c.execute(execute, args):
+        for i in row:
+            rows.append(i)
+    conn.close()
+    return rows
+
+
+
+def get_blocks_by_type(blockType, orderDate=True) +
+
+

Returns a list of blocks by the type

+
+Source code +
def get_blocks_by_type(blockType, orderDate=True):
+    '''
+        Returns a list of blocks by the type
+    '''
+
+    conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+    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
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/blockmetadb/updateblockinfo.html b/docs/html/onionr/coredb/blockmetadb/updateblockinfo.html new file mode 100644 index 00000000..1833ccc9 --- /dev/null +++ b/docs/html/onionr/coredb/blockmetadb/updateblockinfo.html @@ -0,0 +1,171 @@ + + + + + + +onionr.coredb.blockmetadb.updateblockinfo API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.blockmetadb.updateblockinfo

+
+
+

Onionr - Private P2P Communication

+

Update block information in the metadata database by a field name

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Update block information in the metadata database by a field name
+'''
+'''
+    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/>.
+'''
+import sqlite3
+from .. import dbfiles
+def update_block_info(hash, key, data):
+    '''
+        sets 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 (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       - 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=30)
+    c = conn.cursor()
+    args = (data, hash)
+    # Unfortunately, not really possible
+    c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args)
+    conn.commit()
+    conn.close()
+
+    return True
+
+
+
+
+
+
+
+

Functions

+
+
+def update_block_info(hash, key, data) +
+
+

sets 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 (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 +- expire date for a block

+
+Source code +
def update_block_info(hash, key, data):
+    '''
+        sets 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 (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       - 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=30)
+    c = conn.cursor()
+    args = (data, hash)
+    # Unfortunately, not really possible
+    c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args)
+    conn.commit()
+    conn.close()
+
+    return True
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/daemonqueue/index.html b/docs/html/onionr/coredb/daemonqueue/index.html new file mode 100644 index 00000000..08de235a --- /dev/null +++ b/docs/html/onionr/coredb/daemonqueue/index.html @@ -0,0 +1,262 @@ + + + + + + +onionr.coredb.daemonqueue API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.daemonqueue

+
+
+

Onionr - Private P2P Communication

+

Write and read the daemon queue, which is how messages are passed into the onionr daemon in a more +direct way than the http api

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Write and read the daemon queue, which is how messages are passed into the onionr daemon in a more
+    direct way than the http api
+'''
+'''
+    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/>.
+'''
+import sqlite3, os
+from onionrplugins import onionrevents as events
+from onionrutils import localcommand, epoch
+from .. import dbfiles
+from onionrsetup import dbcreator
+
+def daemon_queue()->str:
+    '''
+        Gives commands to the communication proccess/daemon by reading an sqlite3 database
+
+        This function intended to be used by the client. Queue to exchange data between "client" and server.
+    '''
+
+    retData = False
+    if not os.path.exists(dbfiles.daemon_queue_db):
+        dbcreator.createDaemonDB()
+    else:
+        conn = sqlite3.connect(dbfiles.daemon_queue_db, timeout=30)
+        c = conn.cursor()
+        try:
+            for row in c.execute('SELECT command, data, date, min(ID), responseID FROM commands group by id'):
+                retData = row
+                break
+        except sqlite3.OperationalError:
+            dbcreator.createDaemonDB()
+        else:
+            if retData != False:
+                c.execute('DELETE FROM commands WHERE id=?;', (retData[3],))
+        conn.commit()
+        conn.close()
+
+    return retData
+
+def daemon_queue_add(command: str, data='', responseID: str =''):
+    '''
+        Add a command to the daemon queue, used by the communication daemon (communicator.py)
+    '''
+
+    retData = True
+
+    date = epoch.get_epoch()
+    conn = sqlite3.connect(dbfiles.daemon_queue_db, timeout=30)
+    c = conn.cursor()
+    t = (command, data, date, responseID)
+    try:
+        c.execute('INSERT INTO commands (command, data, date, responseID) VALUES(?, ?, ?, ?)', t)
+        conn.commit()
+    except sqlite3.OperationalError:
+        retData = False
+        daemon_queue()
+    conn.close()
+    return retData
+
+def daemon_queue_get_response(responseID=''):
+    '''
+        Get a response sent by communicator to the API, by requesting to the API
+    '''
+    if len(responseID) == 0: raise ValueError('ResponseID should not be empty')
+    resp = localcommand.local_command(dbfiles.daemon_queue_db, 'queueResponse/' + responseID)
+    return resp
+
+def clear_daemon_queue():
+    '''
+        Clear the daemon queue (somewhat dangerous)
+    '''
+    conn = sqlite3.connect(dbfiles.daemon_queue_db, timeout=30)
+    c = conn.cursor()
+
+    c.execute('DELETE FROM commands;')
+    conn.commit()
+
+    conn.close()
+
+
+
+
+
+
+
+

Functions

+
+
+def clear_daemon_queue() +
+
+

Clear the daemon queue (somewhat dangerous)

+
+Source code +
def clear_daemon_queue():
+    '''
+        Clear the daemon queue (somewhat dangerous)
+    '''
+    conn = sqlite3.connect(dbfiles.daemon_queue_db, timeout=30)
+    c = conn.cursor()
+
+    c.execute('DELETE FROM commands;')
+    conn.commit()
+
+    conn.close()
+
+
+
+def daemon_queue() +
+
+

Gives commands to the communication proccess/daemon by reading an sqlite3 database

+

This function intended to be used by the client. Queue to exchange data between "client" and server.

+
+Source code +
def daemon_queue()->str:
+    '''
+        Gives commands to the communication proccess/daemon by reading an sqlite3 database
+
+        This function intended to be used by the client. Queue to exchange data between "client" and server.
+    '''
+
+    retData = False
+    if not os.path.exists(dbfiles.daemon_queue_db):
+        dbcreator.createDaemonDB()
+    else:
+        conn = sqlite3.connect(dbfiles.daemon_queue_db, timeout=30)
+        c = conn.cursor()
+        try:
+            for row in c.execute('SELECT command, data, date, min(ID), responseID FROM commands group by id'):
+                retData = row
+                break
+        except sqlite3.OperationalError:
+            dbcreator.createDaemonDB()
+        else:
+            if retData != False:
+                c.execute('DELETE FROM commands WHERE id=?;', (retData[3],))
+        conn.commit()
+        conn.close()
+
+    return retData
+
+
+
+def daemon_queue_add(command, data='', responseID='') +
+
+

Add a command to the daemon queue, used by the communication daemon (communicator.py)

+
+Source code +
def daemon_queue_add(command: str, data='', responseID: str =''):
+    '''
+        Add a command to the daemon queue, used by the communication daemon (communicator.py)
+    '''
+
+    retData = True
+
+    date = epoch.get_epoch()
+    conn = sqlite3.connect(dbfiles.daemon_queue_db, timeout=30)
+    c = conn.cursor()
+    t = (command, data, date, responseID)
+    try:
+        c.execute('INSERT INTO commands (command, data, date, responseID) VALUES(?, ?, ?, ?)', t)
+        conn.commit()
+    except sqlite3.OperationalError:
+        retData = False
+        daemon_queue()
+    conn.close()
+    return retData
+
+
+
+def daemon_queue_get_response(responseID='') +
+
+

Get a response sent by communicator to the API, by requesting to the API

+
+Source code +
def daemon_queue_get_response(responseID=''):
+    '''
+        Get a response sent by communicator to the API, by requesting to the API
+    '''
+    if len(responseID) == 0: raise ValueError('ResponseID should not be empty')
+    resp = localcommand.local_command(dbfiles.daemon_queue_db, 'queueResponse/' + responseID)
+    return resp
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/dbfiles.html b/docs/html/onionr/coredb/dbfiles.html new file mode 100644 index 00000000..8461d397 --- /dev/null +++ b/docs/html/onionr/coredb/dbfiles.html @@ -0,0 +1,68 @@ + + + + + + +onionr.coredb.dbfiles API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.dbfiles

+
+
+
+Source code +
from utils import identifyhome
+import filepaths
+home = identifyhome.identify_home()
+if not home.endswith('/'): home += '/'
+
+block_meta_db = '%sblock-metadata.db' % (home)
+block_data_db = '%s/block-data.db' % (filepaths.block_data_location,)
+daemon_queue_db = '%sdaemon-queue.db' % (home,)
+address_info_db = '%saddress.db' % (home,)
+user_id_info_db = '%susers.db' % (home,)
+forward_keys_db = '%sforward-keys.db' % (home,)
+blacklist_db = '%sblacklist.db' % (home,)
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/index.html b/docs/html/onionr/coredb/index.html new file mode 100644 index 00000000..01fadb74 --- /dev/null +++ b/docs/html/onionr/coredb/index.html @@ -0,0 +1,84 @@ + + + + + + +onionr.coredb API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb

+
+
+
+Source code +
from . import keydb, blockmetadb, daemonqueue
+
+
+
+

Sub-modules

+
+
onionr.coredb.blockmetadb
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb.daemonqueue
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb.dbfiles
+
+
+
+
onionr.coredb.keydb
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/keydb/addkeys.html b/docs/html/onionr/coredb/keydb/addkeys.html new file mode 100644 index 00000000..8c5a112f --- /dev/null +++ b/docs/html/onionr/coredb/keydb/addkeys.html @@ -0,0 +1,246 @@ + + + + + + +onionr.coredb.keydb.addkeys API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.keydb.addkeys

+
+
+

Onionr - Private P2P Communication

+

add user keys or transport addresses

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    add user keys or transport addresses
+'''
+'''
+    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/>.
+'''
+import sqlite3
+from onionrplugins import onionrevents as events
+from onionrutils import stringvalidators
+from . import listkeys
+from utils import gettransports
+from .. import dbfiles
+import onionrcrypto
+def add_peer(peerID, name=''):
+    '''
+        Adds a public key to the key database (misleading function name)
+    '''
+    if peerID in listkeys.list_peers() or peerID == onionrcrypto.pub_key:
+        raise ValueError("specified id is already known")
+
+    # This function simply adds a peer to the DB
+    if not stringvalidators.validate_pub_key(peerID):
+        return False
+
+    #events.event('pubkey_add', data = {'key': peerID}, onionr = core_inst.onionrInst)
+
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    hashID = ""
+    c = conn.cursor()
+    t = (peerID, name, 'unknown', hashID, 0)
+
+    for i in c.execute("SELECT * FROM peers WHERE id = ?;", (peerID,)):
+        try:
+            if i[0] == peerID:
+                conn.close()
+                return False
+        except ValueError:
+            pass
+        except IndexError:
+            pass
+    c.execute('INSERT INTO peers (id, name, dateSeen, hashID, trust) VALUES(?, ?, ?, ?, ?);', t)
+    conn.commit()
+    conn.close()
+
+    return True
+
+def add_address(address):
+    '''
+        Add an address to the address database (only tor currently)
+    '''
+
+    if type(address) is None or len(address) == 0:
+        return False
+    if stringvalidators.validate_transport(address):
+        if address in gettransports.get():
+            return False
+        conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+        c = conn.cursor()
+        # check if address is in database
+        # this is safe to do because the address is validated above, but we strip some chars here too just in case
+        address = address.replace('\'', '').replace(';', '').replace('"', '').replace('\\', '')
+        for i in c.execute("SELECT * FROM adders WHERE address = ?;", (address,)):
+            try:
+                if i[0] == address:
+                    conn.close()
+                    return False
+            except ValueError:
+                pass
+            except IndexError:
+                pass
+
+        t = (address, 1)
+        c.execute('INSERT INTO adders (address, type) VALUES(?, ?);', t)
+        conn.commit()
+        conn.close()
+
+        #events.event('address_add', data = {'address': address}, onionr = core_inst.onionrInst)
+
+        return True
+    else:
+        return False
+
+
+
+
+
+
+
+

Functions

+
+
+def add_address(address) +
+
+

Add an address to the address database (only tor currently)

+
+Source code +
def add_address(address):
+    '''
+        Add an address to the address database (only tor currently)
+    '''
+
+    if type(address) is None or len(address) == 0:
+        return False
+    if stringvalidators.validate_transport(address):
+        if address in gettransports.get():
+            return False
+        conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+        c = conn.cursor()
+        # check if address is in database
+        # this is safe to do because the address is validated above, but we strip some chars here too just in case
+        address = address.replace('\'', '').replace(';', '').replace('"', '').replace('\\', '')
+        for i in c.execute("SELECT * FROM adders WHERE address = ?;", (address,)):
+            try:
+                if i[0] == address:
+                    conn.close()
+                    return False
+            except ValueError:
+                pass
+            except IndexError:
+                pass
+
+        t = (address, 1)
+        c.execute('INSERT INTO adders (address, type) VALUES(?, ?);', t)
+        conn.commit()
+        conn.close()
+
+        #events.event('address_add', data = {'address': address}, onionr = core_inst.onionrInst)
+
+        return True
+    else:
+        return False
+
+
+
+def add_peer(peerID, name='') +
+
+

Adds a public key to the key database (misleading function name)

+
+Source code +
def add_peer(peerID, name=''):
+    '''
+        Adds a public key to the key database (misleading function name)
+    '''
+    if peerID in listkeys.list_peers() or peerID == onionrcrypto.pub_key:
+        raise ValueError("specified id is already known")
+
+    # This function simply adds a peer to the DB
+    if not stringvalidators.validate_pub_key(peerID):
+        return False
+
+    #events.event('pubkey_add', data = {'key': peerID}, onionr = core_inst.onionrInst)
+
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    hashID = ""
+    c = conn.cursor()
+    t = (peerID, name, 'unknown', hashID, 0)
+
+    for i in c.execute("SELECT * FROM peers WHERE id = ?;", (peerID,)):
+        try:
+            if i[0] == peerID:
+                conn.close()
+                return False
+        except ValueError:
+            pass
+        except IndexError:
+            pass
+    c.execute('INSERT INTO peers (id, name, dateSeen, hashID, trust) VALUES(?, ?, ?, ?, ?);', t)
+    conn.commit()
+    conn.close()
+
+    return True
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/keydb/index.html b/docs/html/onionr/coredb/keydb/index.html new file mode 100644 index 00000000..0d94b0f2 --- /dev/null +++ b/docs/html/onionr/coredb/keydb/index.html @@ -0,0 +1,89 @@ + + + + + + +onionr.coredb.keydb API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.keydb

+
+
+
+Source code +
from . import addkeys, listkeys, removekeys, userinfo, transportinfo
+
+
+
+

Sub-modules

+
+
onionr.coredb.keydb.addkeys
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb.keydb.listkeys
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb.keydb.removekeys
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb.keydb.transportinfo
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb.keydb.userinfo
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/keydb/listkeys.html b/docs/html/onionr/coredb/keydb/listkeys.html new file mode 100644 index 00000000..2b5732ae --- /dev/null +++ b/docs/html/onionr/coredb/keydb/listkeys.html @@ -0,0 +1,232 @@ + + + + + + +onionr.coredb.keydb.listkeys API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.keydb.listkeys

+
+
+

Onionr - Private P2P Communication

+

get lists for user keys or transport addresses

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    get lists for user keys or transport addresses
+'''
+'''
+    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/>.
+'''
+import sqlite3
+import logger
+from onionrutils import epoch
+from .. import dbfiles
+from . import userinfo, transportinfo
+def list_peers(randomOrder=True, getPow=False, trust=0):
+    '''
+        Return a list of public keys (misleading function name)
+
+        randomOrder determines if the list should be in a random order
+        trust sets the minimum trust to list
+    '''
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    c = conn.cursor()
+
+    payload = ''
+
+    if trust not in (0, 1, 2):
+        logger.error('Tried to select invalid trust.')
+        return
+
+    if randomOrder:
+        payload = 'SELECT * FROM peers WHERE trust >= ? ORDER BY RANDOM();'
+    else:
+        payload = 'SELECT * FROM peers WHERE trust >= ?;'
+
+    peerList = []
+
+    for i in c.execute(payload, (trust,)):
+        try:
+            if len(i[0]) != 0:
+                if getPow:
+                    peerList.append(i[0] + '-' + i[1])
+                else:
+                    peerList.append(i[0])
+        except TypeError:
+            pass
+
+    conn.close()
+
+    return peerList
+
+def list_adders(randomOrder=True, i2p=True, recent=0):
+    '''
+        Return a list of transport addresses
+    '''
+    conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+    c = conn.cursor()
+    if randomOrder:
+        addresses = c.execute('SELECT * FROM adders ORDER BY RANDOM();')
+    else:
+        addresses = c.execute('SELECT * FROM adders;')
+    addressList = []
+    for i in addresses:
+        if len(i[0].strip()) == 0:
+            continue
+        addressList.append(i[0])
+    conn.close()
+    testList = list(addressList) # create new list to iterate
+    for address in testList:
+        try:
+            if recent > 0 and (epoch.get_epoch() - transportinfo.get_address_info(address, 'lastConnect')) > recent:
+                raise TypeError # If there is no last-connected date or it was too long ago, don't add peer to list if recent is not 0
+        except TypeError:
+            addressList.remove(address)
+    return addressList
+
+
+
+
+
+
+
+

Functions

+
+
+def list_adders(randomOrder=True, i2p=True, recent=0) +
+
+

Return a list of transport addresses

+
+Source code +
def list_adders(randomOrder=True, i2p=True, recent=0):
+    '''
+        Return a list of transport addresses
+    '''
+    conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+    c = conn.cursor()
+    if randomOrder:
+        addresses = c.execute('SELECT * FROM adders ORDER BY RANDOM();')
+    else:
+        addresses = c.execute('SELECT * FROM adders;')
+    addressList = []
+    for i in addresses:
+        if len(i[0].strip()) == 0:
+            continue
+        addressList.append(i[0])
+    conn.close()
+    testList = list(addressList) # create new list to iterate
+    for address in testList:
+        try:
+            if recent > 0 and (epoch.get_epoch() - transportinfo.get_address_info(address, 'lastConnect')) > recent:
+                raise TypeError # If there is no last-connected date or it was too long ago, don't add peer to list if recent is not 0
+        except TypeError:
+            addressList.remove(address)
+    return addressList
+
+
+
+def list_peers(randomOrder=True, getPow=False, trust=0) +
+
+

Return a list of public keys (misleading function name)

+

randomOrder determines if the list should be in a random order +trust sets the minimum trust to list

+
+Source code +
def list_peers(randomOrder=True, getPow=False, trust=0):
+    '''
+        Return a list of public keys (misleading function name)
+
+        randomOrder determines if the list should be in a random order
+        trust sets the minimum trust to list
+    '''
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    c = conn.cursor()
+
+    payload = ''
+
+    if trust not in (0, 1, 2):
+        logger.error('Tried to select invalid trust.')
+        return
+
+    if randomOrder:
+        payload = 'SELECT * FROM peers WHERE trust >= ? ORDER BY RANDOM();'
+    else:
+        payload = 'SELECT * FROM peers WHERE trust >= ?;'
+
+    peerList = []
+
+    for i in c.execute(payload, (trust,)):
+        try:
+            if len(i[0]) != 0:
+                if getPow:
+                    peerList.append(i[0] + '-' + i[1])
+                else:
+                    peerList.append(i[0])
+        except TypeError:
+            pass
+
+    conn.close()
+
+    return peerList
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/keydb/removekeys.html b/docs/html/onionr/coredb/keydb/removekeys.html new file mode 100644 index 00000000..fbd1e5ab --- /dev/null +++ b/docs/html/onionr/coredb/keydb/removekeys.html @@ -0,0 +1,132 @@ + + + + + + +onionr.coredb.keydb.removekeys API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.keydb.removekeys

+
+
+

Onionr - Private P2P Communication

+

Remove a transport address but don't ban them

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Remove a transport address but don't ban them
+'''
+'''
+    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/>.
+'''
+import sqlite3
+from onionrplugins import onionrevents as events
+from onionrutils import stringvalidators
+from .. import dbfiles
+def remove_address(address):
+    '''
+        Remove an address from the address database
+    '''
+
+    if stringvalidators.validate_transport(address):
+        conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+        c = conn.cursor()
+        t = (address,)
+        c.execute('Delete from adders where address=?;', t)
+        conn.commit()
+        conn.close()
+
+        #events.event('address_remove', data = {'address': address}, onionr = core_inst.onionrInst)
+        return True
+    else:
+        return False
+
+
+
+
+
+
+
+

Functions

+
+
+def remove_address(address) +
+
+

Remove an address from the address database

+
+Source code +
def remove_address(address):
+    '''
+        Remove an address from the address database
+    '''
+
+    if stringvalidators.validate_transport(address):
+        conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+        c = conn.cursor()
+        t = (address,)
+        c.execute('Delete from adders where address=?;', t)
+        conn.commit()
+        conn.close()
+
+        #events.event('address_remove', data = {'address': address}, onionr = core_inst.onionrInst)
+        return True
+    else:
+        return False
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/keydb/transportinfo.html b/docs/html/onionr/coredb/keydb/transportinfo.html new file mode 100644 index 00000000..a8c201e1 --- /dev/null +++ b/docs/html/onionr/coredb/keydb/transportinfo.html @@ -0,0 +1,222 @@ + + + + + + +onionr.coredb.keydb.transportinfo API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.keydb.transportinfo

+
+
+

Onionr - Private P2P Communication

+

get or set transport address meta information

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    get or set transport address meta information
+'''
+'''
+    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/>.
+'''
+import sqlite3
+from .. import dbfiles
+def get_address_info(address, info):
+    '''
+        Get info about an address from its database entry
+
+        address text, 0
+        type int, 1
+        knownPeer text, 2
+        speed int, 3
+        success int, 4
+        powValue    5
+        failure int 6
+        lastConnect 7
+        trust       8
+        introduced  9
+    '''
+
+    conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (address,)
+    infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'powValue': 5, 'failure': 6, 'lastConnect': 7, 'trust': 8, 'introduced': 9}
+    info = infoNumbers[info]
+    iterCount = 0
+    retVal = ''
+
+    for row in c.execute('SELECT * FROM adders WHERE address=?;', command):
+        for i in row:
+            if iterCount == info:
+                retVal = i
+                break
+            else:
+                iterCount += 1
+    conn.close()
+
+    return retVal
+
+def set_address_info(address, key, data):
+    '''
+        Update an address for a key
+    '''
+
+    conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (data, address)
+
+    if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'failure', 'powValue', 'lastConnect', 'lastConnectAttempt', 'trust', 'introduced'):
+        raise ValueError("Got invalid database key when setting address info, must be in whitelist")
+    else:
+        c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
+        conn.commit()
+    conn.close()
+
+
+
+
+
+
+
+

Functions

+
+
+def get_address_info(address, info) +
+
+

Get info about an address from its database entry

+

address text, 0 +type int, 1 +knownPeer text, 2 +speed int, 3 +success int, 4 +powValue +5 +failure int 6 +lastConnect 7 +trust +8 +introduced +9

+
+Source code +
def get_address_info(address, info):
+    '''
+        Get info about an address from its database entry
+
+        address text, 0
+        type int, 1
+        knownPeer text, 2
+        speed int, 3
+        success int, 4
+        powValue    5
+        failure int 6
+        lastConnect 7
+        trust       8
+        introduced  9
+    '''
+
+    conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (address,)
+    infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'powValue': 5, 'failure': 6, 'lastConnect': 7, 'trust': 8, 'introduced': 9}
+    info = infoNumbers[info]
+    iterCount = 0
+    retVal = ''
+
+    for row in c.execute('SELECT * FROM adders WHERE address=?;', command):
+        for i in row:
+            if iterCount == info:
+                retVal = i
+                break
+            else:
+                iterCount += 1
+    conn.close()
+
+    return retVal
+
+
+
+def set_address_info(address, key, data) +
+
+

Update an address for a key

+
+Source code +
def set_address_info(address, key, data):
+    '''
+        Update an address for a key
+    '''
+
+    conn = sqlite3.connect(dbfiles.address_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (data, address)
+
+    if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'failure', 'powValue', 'lastConnect', 'lastConnectAttempt', 'trust', 'introduced'):
+        raise ValueError("Got invalid database key when setting address info, must be in whitelist")
+    else:
+        c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
+        conn.commit()
+    conn.close()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/coredb/keydb/userinfo.html b/docs/html/onionr/coredb/keydb/userinfo.html new file mode 100644 index 00000000..cb79d4b7 --- /dev/null +++ b/docs/html/onionr/coredb/keydb/userinfo.html @@ -0,0 +1,241 @@ + + + + + + +onionr.coredb.keydb.userinfo API documentation + + + + + + + + + +
+
+
+

Module onionr.coredb.keydb.userinfo

+
+
+

Onionr - Private P2P Communication

+

get or set information about a user id

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    get or set information about a user id
+'''
+'''
+    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/>.
+'''
+import sqlite3
+from .. import dbfiles
+def get_user_info(peer, info):
+    '''
+        Get info about a peer from their database entry
+
+        id text             0
+        name text,          1
+        adders text,        2
+        dateSeen not null,  3
+        trust int           4
+        hashID text         5
+    '''
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (peer,)
+    infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'dateSeen': 3, 'trust': 4, 'hashID': 5}
+    info = infoNumbers[info]
+    iterCount = 0
+    retVal = ''
+
+    for row in c.execute('SELECT * FROM peers WHERE id=?;', command):
+        for i in row:
+            if iterCount == info:
+                retVal = i
+                break
+            else:
+                iterCount += 1
+
+    conn.close()
+
+    return retVal
+
+def set_peer_info(peer, key, data):
+    '''
+        Update a peer for a key
+    '''
+
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (data, peer)
+
+    if key not in ('id', 'name', 'pubkey', 'forwardKey', 'dateSeen', 'trust'):
+        raise ValueError("Got invalid database key when setting peer info")
+
+    c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command)
+    conn.commit()
+    conn.close()
+
+set_user_info = set_peer_info
+
+
+
+
+
+
+
+

Functions

+
+
+def get_user_info(peer, info) +
+
+

Get info about a peer from their database entry

+

id text +0 +name text, +1 +adders text, +2 +dateSeen not null, +3 +trust int +4 +hashID text +5

+
+Source code +
def get_user_info(peer, info):
+    '''
+        Get info about a peer from their database entry
+
+        id text             0
+        name text,          1
+        adders text,        2
+        dateSeen not null,  3
+        trust int           4
+        hashID text         5
+    '''
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (peer,)
+    infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'dateSeen': 3, 'trust': 4, 'hashID': 5}
+    info = infoNumbers[info]
+    iterCount = 0
+    retVal = ''
+
+    for row in c.execute('SELECT * FROM peers WHERE id=?;', command):
+        for i in row:
+            if iterCount == info:
+                retVal = i
+                break
+            else:
+                iterCount += 1
+
+    conn.close()
+
+    return retVal
+
+
+
+def set_peer_info(peer, key, data) +
+
+

Update a peer for a key

+
+Source code +
def set_peer_info(peer, key, data):
+    '''
+        Update a peer for a key
+    '''
+
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (data, peer)
+
+    if key not in ('id', 'name', 'pubkey', 'forwardKey', 'dateSeen', 'trust'):
+        raise ValueError("Got invalid database key when setting peer info")
+
+    c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command)
+    conn.commit()
+    conn.close()
+
+
+
+def set_user_info(peer, key, data) +
+
+

Update a peer for a key

+
+Source code +
def set_peer_info(peer, key, data):
+    '''
+        Update a peer for a key
+    '''
+
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=30)
+    c = conn.cursor()
+
+    command = (data, peer)
+
+    if key not in ('id', 'name', 'pubkey', 'forwardKey', 'dateSeen', 'trust'):
+        raise ValueError("Got invalid database key when setting peer info")
+
+    c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command)
+    conn.commit()
+    conn.close()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/etc/cleanup/index.html b/docs/html/onionr/etc/cleanup/index.html new file mode 100644 index 00000000..6edffb1f --- /dev/null +++ b/docs/html/onionr/etc/cleanup/index.html @@ -0,0 +1,89 @@ + + + + + + +onionr.etc.cleanup API documentation + + + + + + + + + +
+
+
+

Module onionr.etc.cleanup

+
+
+
+Source code +
import os, filepaths
+
+def _safe_remove(path):
+    try:
+        os.remove(path)
+    except FileNotFoundError:
+        pass
+
+def delete_run_files():
+     _safe_remove(filepaths.public_API_host_file)
+     _safe_remove(filepaths.private_API_host_file)
+     _safe_remove(filepaths.daemon_mark_file)
+
+
+
+
+
+
+
+

Functions

+
+
+def delete_run_files() +
+
+
+
+Source code +
def delete_run_files():
+     _safe_remove(filepaths.public_API_host_file)
+     _safe_remove(filepaths.private_API_host_file)
+     _safe_remove(filepaths.daemon_mark_file)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/etc/humanreadabletime.html b/docs/html/onionr/etc/humanreadabletime.html new file mode 100644 index 00000000..6367f8b3 --- /dev/null +++ b/docs/html/onionr/etc/humanreadabletime.html @@ -0,0 +1,132 @@ + + + + + + +onionr.etc.humanreadabletime API documentation + + + + + + + + + +
+
+
+

Module onionr.etc.humanreadabletime

+
+
+

Onionr - Private P2P Communication

+

human_readable_time takes integer seconds and returns a human readable string

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    human_readable_time takes integer seconds and returns a human readable string
+'''
+'''
+    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 human_readable_time(seconds):
+    build = ''
+
+    units = {
+        'year' : 31557600,
+        'month' : (31557600 / 12),
+        'day' : 86400,
+        'hour' : 3600,
+        'minute' : 60,
+        'second' : 1
+    }
+
+    for unit in units:
+        amnt_unit = int(seconds / units[unit])
+        if amnt_unit >= 1:
+            seconds -= amnt_unit * units[unit]
+            build += '%s %s' % (amnt_unit, unit) + ('s' if amnt_unit != 1 else '') + ' '
+
+    return build.strip()
+
+
+
+
+
+
+
+

Functions

+
+
+def human_readable_time(seconds) +
+
+
+
+Source code +
def human_readable_time(seconds):
+    build = ''
+
+    units = {
+        'year' : 31557600,
+        'month' : (31557600 / 12),
+        'day' : 86400,
+        'hour' : 3600,
+        'minute' : 60,
+        'second' : 1
+    }
+
+    for unit in units:
+        amnt_unit = int(seconds / units[unit])
+        if amnt_unit >= 1:
+            seconds -= amnt_unit * units[unit]
+            build += '%s %s' % (amnt_unit, unit) + ('s' if amnt_unit != 1 else '') + ' '
+
+    return build.strip()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/etc/index.html b/docs/html/onionr/etc/index.html new file mode 100644 index 00000000..1db9f74e --- /dev/null +++ b/docs/html/onionr/etc/index.html @@ -0,0 +1,85 @@ + + + + + + +onionr.etc API documentation + + + + + + + + + +
+
+
+

Module onionr.etc

+
+
+
+
+

Sub-modules

+
+
onionr.etc.cleanup
+
+
+
+
onionr.etc.humanreadabletime
+
+

Onionr - Private P2P Communication …

+
+
onionr.etc.onionrvalues
+
+

Onionr - Private P2P Communication …

+
+
onionr.etc.powchoice
+
+

Onionr - Private P2P Communication …

+
+
onionr.etc.waitforsetvar
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/etc/onionrvalues.html b/docs/html/onionr/etc/onionrvalues.html new file mode 100644 index 00000000..f925b049 --- /dev/null +++ b/docs/html/onionr/etc/onionrvalues.html @@ -0,0 +1,111 @@ + + + + + + +onionr.etc.onionrvalues API documentation + + + + + + + + + +
+
+
+

Module onionr.etc.onionrvalues

+
+
+

Onionr - Private P2P Communication

+

This file defines values and requirements used by Onionr

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file defines values and requirements used by Onionr
+'''
+'''
+    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/>.
+'''
+import platform
+import os
+
+import filepaths
+
+DENIABLE_PEER_ADDRESS = "OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA===="
+PASSWORD_LENGTH = 25
+ONIONR_TAGLINE = 'Private P2P Communication - GPLv3 - https://Onionr.net'
+ONIONR_VERSION = '0.0.0' # for debugging and stuff
+ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION)
+API_VERSION = '0' # increments of 1; only change when something fundamental about how the API works changes. This way other nodes know how to communicate without learning too much information about you.
+MIN_PY_VERSION = 7
+DEVELOPMENT_MODE = True
+MAX_BLOCK_TYPE_LENGTH = 15
+MAX_BLOCK_CLOCK_SKEW = 120
+MAIN_PUBLIC_KEY_SIZE = 32
+ORIG_RUN_DIR_ENV_VAR = 'ORIG_ONIONR_RUN_DIR'
+
+# Block creation anonymization requirements
+MIN_BLOCK_UPLOAD_PEER_PERCENT = 0.1
+
+# Begin OnionrValues migrated values
+ANNOUNCE_POW = 5
+DEFAULT_EXPIRE = 2592000
+BLOCK_METADATA_LENGTHS = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10, 'pow': 1000, 'encryptType': 4, 'expire': 14}
+
+platform = platform.system()
+if platform == 'Windows':
+    SCRIPT_NAME = 'run-windows.bat'
+else:
+    if os.path.exists(filepaths.daemon_mark_file):
+        SCRIPT_NAME = 'start-daemon.sh'
+    else:
+        SCRIPT_NAME = 'onionr.sh'
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/etc/powchoice.html b/docs/html/onionr/etc/powchoice.html new file mode 100644 index 00000000..1803c159 --- /dev/null +++ b/docs/html/onionr/etc/powchoice.html @@ -0,0 +1,109 @@ + + + + + + +onionr.etc.powchoice API documentation + + + + + + + + + +
+
+
+

Module onionr.etc.powchoice

+
+
+

Onionr - Private P2P Communication

+

This file does determinations for what proof of work module should be used

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file does determinations for what proof of work module should be used
+'''
+'''
+    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/>.
+'''
+import platform
+def use_subprocess(config_inst):
+    use = True
+    if not config_inst.get('general.use_subprocess_pow_if_possible', True):
+        use = False
+    if 'Windows' == platform.system():
+        use = False
+    return use
+
+
+
+
+
+
+
+

Functions

+
+
+def use_subprocess(config_inst) +
+
+
+
+Source code +
def use_subprocess(config_inst):
+    use = True
+    if not config_inst.get('general.use_subprocess_pow_if_possible', True):
+        use = False
+    if 'Windows' == platform.system():
+        use = False
+    return use
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/etc/waitforsetvar.html b/docs/html/onionr/etc/waitforsetvar.html new file mode 100644 index 00000000..b45ab0e6 --- /dev/null +++ b/docs/html/onionr/etc/waitforsetvar.html @@ -0,0 +1,86 @@ + + + + + + +onionr.etc.waitforsetvar API documentation + + + + + + + + + +
+
+
+

Module onionr.etc.waitforsetvar

+
+
+
+Source code +
from __future__ import annotations
+from typing import Union, Generic
+from gevent import sleep
+def wait_for_set_var(obj, attribute, sleep_seconds: Union[int, float]=0):
+    """Wait for an object to get an attribute with an optional sleep time"""
+    while not hasattr(obj, attribute):
+        if hasattr(obj, attribute): break
+        if sleep_seconds > 0: sleep(sleep_seconds)
+
+
+
+
+
+
+
+

Functions

+
+
+def wait_for_set_var(obj, attribute, sleep_seconds=0) +
+
+

Wait for an object to get an attribute with an optional sleep time

+
+Source code +
def wait_for_set_var(obj, attribute, sleep_seconds: Union[int, float]=0):
+    """Wait for an object to get an attribute with an optional sleep time"""
+    while not hasattr(obj, attribute):
+        if hasattr(obj, attribute): break
+        if sleep_seconds > 0: sleep(sleep_seconds)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/filepaths/index.html b/docs/html/onionr/filepaths/index.html new file mode 100644 index 00000000..157af2c1 --- /dev/null +++ b/docs/html/onionr/filepaths/index.html @@ -0,0 +1,84 @@ + + + + + + +onionr.filepaths API documentation + + + + + + + + + +
+
+
+

Module onionr.filepaths

+
+
+
+Source code +
from utils import identifyhome
+import os
+home = identifyhome.identify_home()
+if not home.endswith('/'): home += '/'
+
+app_root = os.path.dirname(os.path.realpath(__file__)) + '/../../'
+usage_file = home + 'disk-usage.txt'
+block_data_location = home + 'blocks/'
+contacts_location = home + 'contacts/'
+public_API_host_file = home + 'public-host.txt'
+private_API_host_file = home + 'private-host.txt'
+bootstrap_file_location = 'static-data/bootstrap-nodes.txt'
+data_nonce_file = home + 'block-nonces.dat'
+forward_keys_file = home + 'forward-keys.db'
+cached_storage = home + 'cachedstorage.dat'
+announce_cache = home + 'announcecache.dat'
+export_location = home + 'block-export/'
+upload_list = home + 'upload-list.json'
+config_file = home + 'config.json'
+daemon_mark_file = app_root + '/daemon-true.txt'
+
+tor_hs_address_file = home + 'hs/hostname'
+
+run_check_file = home + '.runcheck'
+
+data_nonce_file = home + 'block-nonces.dat'
+
+keys_file = home + 'keys.txt'
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/apiutils/getblockdata.html b/docs/html/onionr/httpapi/apiutils/getblockdata.html new file mode 100644 index 00000000..99a66a34 --- /dev/null +++ b/docs/html/onionr/httpapi/apiutils/getblockdata.html @@ -0,0 +1,184 @@ + + + + + + +onionr.httpapi.apiutils.getblockdata API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.apiutils.getblockdata

+
+
+
+Source code +
import 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
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class GetBlockData +(client_api_inst=None) +
+
+
+
+Source code +
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
+
+

Methods

+
+
+def get_block_data(self, bHash, decrypt=False, raw=False, headerOnly=False) +
+
+
+
+Source code +
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
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/apiutils/index.html b/docs/html/onionr/httpapi/apiutils/index.html new file mode 100644 index 00000000..e1a8c674 --- /dev/null +++ b/docs/html/onionr/httpapi/apiutils/index.html @@ -0,0 +1,81 @@ + + + + + + +onionr.httpapi.apiutils API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.apiutils

+
+
+
+Source code +
from . import shutdown, setbindip, getblockdata
+
+GetBlockData = getblockdata.GetBlockData
+
+
+
+

Sub-modules

+
+
onionr.httpapi.apiutils.getblockdata
+
+
+
+
onionr.httpapi.apiutils.setbindip
+
+
+
+
onionr.httpapi.apiutils.shutdown
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/apiutils/setbindip.html b/docs/html/onionr/httpapi/apiutils/setbindip.html new file mode 100644 index 00000000..58fd35b4 --- /dev/null +++ b/docs/html/onionr/httpapi/apiutils/setbindip.html @@ -0,0 +1,141 @@ + + + + + + +onionr.httpapi.apiutils.setbindip API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.apiutils.setbindip

+
+
+
+Source code +
import gevent
+from gevent import socket, sleep
+import secrets, random
+import config, logger
+import os
+
+# Hacky monkey patch so we can bind random localhosts without gevent trying to switch with an empty hub
+socket.getfqdn = lambda n: n
+
+def _get_acceptable_random_number()->int:
+    """Return a cryptographically random number in the inclusive range (1, 255)"""
+    number = 0
+    while number == 0:
+        number = secrets.randbelow(0xFF)
+    return number
+
+def set_bind_IP(filePath=''):
+    '''Set a random localhost IP to a specified file (intended for private or public API localhost IPs)'''
+    if config.get('general.random_bind_ip', True):
+        hostOctets = []
+        # Build the random localhost address
+        for i in range(3):
+            hostOctets.append(str(_get_acceptable_random_number()))
+        hostOctets = ['127'] + hostOctets
+        # Convert the localhost address to a normal string address
+        data = '.'.join(hostOctets)
+        
+        # Try to bind IP. Some platforms like Mac block non normal 127.x.x.x
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        try:
+            s.bind((data, 0))
+        except OSError:
+            # if mac/non-bindable, show warning and default to 127.0.0.1
+            logger.warn('Your platform appears to not support random local host addresses 127.x.x.x. Falling back to 127.0.0.1.')
+            data = '127.0.0.1'
+        s.close()
+    else:
+        data = '127.0.0.1'
+    if filePath != '':
+        with open(filePath, 'w') as bindFile:
+            bindFile.write(data)
+    return data
+
+
+
+
+
+
+
+

Functions

+
+
+def set_bind_IP(filePath='') +
+
+

Set a random localhost IP to a specified file (intended for private or public API localhost IPs)

+
+Source code +
def set_bind_IP(filePath=''):
+    '''Set a random localhost IP to a specified file (intended for private or public API localhost IPs)'''
+    if config.get('general.random_bind_ip', True):
+        hostOctets = []
+        # Build the random localhost address
+        for i in range(3):
+            hostOctets.append(str(_get_acceptable_random_number()))
+        hostOctets = ['127'] + hostOctets
+        # Convert the localhost address to a normal string address
+        data = '.'.join(hostOctets)
+        
+        # Try to bind IP. Some platforms like Mac block non normal 127.x.x.x
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        try:
+            s.bind((data, 0))
+        except OSError:
+            # if mac/non-bindable, show warning and default to 127.0.0.1
+            logger.warn('Your platform appears to not support random local host addresses 127.x.x.x. Falling back to 127.0.0.1.')
+            data = '127.0.0.1'
+        s.close()
+    else:
+        data = '127.0.0.1'
+    if filePath != '':
+        with open(filePath, 'w') as bindFile:
+            bindFile.write(data)
+    return data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/apiutils/shutdown.html b/docs/html/onionr/httpapi/apiutils/shutdown.html new file mode 100644 index 00000000..8dcb7c41 --- /dev/null +++ b/docs/html/onionr/httpapi/apiutils/shutdown.html @@ -0,0 +1,136 @@ + + + + + + +onionr.httpapi.apiutils.shutdown API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.apiutils.shutdown

+
+
+

Onionr - Private P2P Communication

+

Shutdown the node either hard or cleanly

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Shutdown the node either hard or cleanly
+'''
+'''
+    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 flask import Blueprint, Response
+from onionrblocks import onionrblockapi
+import onionrexceptions
+from onionrutils import stringvalidators
+from coredb import daemonqueue
+shutdown_bp = Blueprint('shutdown', __name__)
+
+def shutdown(client_api_inst):
+    try:
+        client_api_inst.publicAPI.httpServer.stop()
+        client_api_inst.httpServer.stop()
+    except AttributeError:
+        pass
+    return Response("bye")
+
+@shutdown_bp.route('/shutdownclean')
+def shutdown_clean():
+    # good for calling from other clients
+    daemonqueue.daemon_queue_add('shutdown')
+    return Response("bye")
+
+
+
+
+
+
+
+

Functions

+
+
+def shutdown(client_api_inst) +
+
+
+
+Source code +
def shutdown(client_api_inst):
+    try:
+        client_api_inst.publicAPI.httpServer.stop()
+        client_api_inst.httpServer.stop()
+    except AttributeError:
+        pass
+    return Response("bye")
+
+
+
+def shutdown_clean() +
+
+
+
+Source code +
@shutdown_bp.route('/shutdownclean')
+def shutdown_clean():
+    # good for calling from other clients
+    daemonqueue.daemon_queue_add('shutdown')
+    return Response("bye")
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/configapi/index.html b/docs/html/onionr/httpapi/configapi/index.html new file mode 100644 index 00000000..1f18bf38 --- /dev/null +++ b/docs/html/onionr/httpapi/configapi/index.html @@ -0,0 +1,200 @@ + + + + + + +onionr.httpapi.configapi API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.configapi

+
+
+

Onionr - Private P2P Communication

+

This file handles configuration setting and getting from the HTTP API

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file handles configuration setting and getting from the HTTP API
+'''
+'''
+    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/>.
+'''
+import json
+from flask import Blueprint, request, Response, abort
+import config, onionrutils
+config.reload()
+
+config_BP = Blueprint('config_BP', __name__)
+
+@config_BP.route('/config/get')
+def get_all_config():
+    '''Simply return all configuration as JSON string'''
+    return Response(json.dumps(config.get_config(), indent=4, sort_keys=True))
+
+@config_BP.route('/config/get/<key>')
+def get_by_key(key):
+    '''Return a config setting by key'''
+    return Response(json.dumps(config.get(key)))
+
+@config_BP.route('/config/setall', methods=['POST'])
+def set_all_config():
+    '''Overwrite existing JSON config with new JSON string'''
+    try:
+        new_config = request.get_json(force=True)
+    except json.JSONDecodeError:
+        abort(400)
+    else:
+        config.set_config(new_config)
+        config.save()
+        return Response('success')
+
+@config_BP.route('/config/set/<key>', methods=['POST'])
+def set_by_key(key):
+    '''Overwrite/set only 1 config key'''
+    '''
+    {
+        'data': data
+    }
+    '''
+    try:
+        data = json.loads(onionrutils.OnionrUtils.bytesToStr(request.data))['data']
+    except (json.JSONDecodeError, KeyError):
+        abort(400)
+    config.set(key, data, True)
+    return Response('success')
+
+
+
+
+
+
+
+

Functions

+
+
+def get_all_config() +
+
+

Simply return all configuration as JSON string

+
+Source code +
@config_BP.route('/config/get')
+def get_all_config():
+    '''Simply return all configuration as JSON string'''
+    return Response(json.dumps(config.get_config(), indent=4, sort_keys=True))
+
+
+
+def get_by_key(key) +
+
+

Return a config setting by key

+
+Source code +
@config_BP.route('/config/get/<key>')
+def get_by_key(key):
+    '''Return a config setting by key'''
+    return Response(json.dumps(config.get(key)))
+
+
+
+def set_all_config() +
+
+

Overwrite existing JSON config with new JSON string

+
+Source code +
@config_BP.route('/config/setall', methods=['POST'])
+def set_all_config():
+    '''Overwrite existing JSON config with new JSON string'''
+    try:
+        new_config = request.get_json(force=True)
+    except json.JSONDecodeError:
+        abort(400)
+    else:
+        config.set_config(new_config)
+        config.save()
+        return Response('success')
+
+
+
+def set_by_key(key) +
+
+

Overwrite/set only 1 config key

+
+Source code +
@config_BP.route('/config/set/<key>', methods=['POST'])
+def set_by_key(key):
+    '''Overwrite/set only 1 config key'''
+    '''
+    {
+        'data': data
+    }
+    '''
+    try:
+        data = json.loads(onionrutils.OnionrUtils.bytesToStr(request.data))['data']
+    except (json.JSONDecodeError, KeyError):
+        abort(400)
+    config.set(key, data, True)
+    return Response('success')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/directconnections/index.html b/docs/html/onionr/httpapi/directconnections/index.html new file mode 100644 index 00000000..485049b5 --- /dev/null +++ b/docs/html/onionr/httpapi/directconnections/index.html @@ -0,0 +1,179 @@ + + + + + + +onionr.httpapi.directconnections API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.directconnections

+
+
+

Onionr - Private P2P Communication

+

Misc client API endpoints too small to need their own file and that need access to the client api inst

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Misc client API endpoints too small to need their own file and that need access to the client api inst
+'''
+'''
+    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/>.
+'''
+import threading # For the client creation thread
+
+from flask import Response # For direct connection management HTTP endpoints
+from flask import Blueprint # To make the direct connection management blueprint in the webUI
+from flask import g # Mainly to access the shared toomanyobjs object
+import deadsimplekv
+
+import filepaths
+import onionrservices
+from onionrservices import pool
+
+def _get_communicator(g):
+    while True:
+        try:
+            return g.too_many.get_by_string("OnionrCommunicatorDaemon")
+        except KeyError:
+            pass
+
+class DirectConnectionManagement:
+    def __init__(self, client_api):
+        direct_conn_management_bp = Blueprint('direct_conn_management', __name__)
+        self.direct_conn_management_bp = direct_conn_management_bp
+
+        cache = deadsimplekv.DeadSimpleKV(filepaths.cached_storage)
+
+        @direct_conn_management_bp.route('/dc-client/isconnected/<pubkey>')
+        def is_connected(pubkey):
+            communicator = _get_communicator(g)
+            resp = ""
+            if pubkey in communicator.direct_connection_clients:
+                resp = communicator.direct_connection_clients[pubkey]
+            return Response(resp)
+        
+        @direct_conn_management_bp.route('/dc-client/connect/<pubkey>')
+        def make_new_connection(pubkey):
+            communicator = _get_communicator(g)
+            resp = "pending"
+            if pubkey in communicator.shared_state.get(pool.ServicePool).bootstrap_pending:
+                return Response(resp)
+                
+            if pubkey in communicator.direct_connection_clients:
+                resp = communicator.direct_connection_clients[pubkey]
+            else:
+                """Spawn a thread that will create the client and eventually add it to the
+                communicator.active_services 
+                """
+                threading.Thread(target=onionrservices.OnionrServices().create_client, 
+                                 args=[pubkey, communicator], daemon=True).start()
+
+            return Response(resp)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class DirectConnectionManagement +(client_api) +
+
+
+
+Source code +
class DirectConnectionManagement:
+    def __init__(self, client_api):
+        direct_conn_management_bp = Blueprint('direct_conn_management', __name__)
+        self.direct_conn_management_bp = direct_conn_management_bp
+
+        cache = deadsimplekv.DeadSimpleKV(filepaths.cached_storage)
+
+        @direct_conn_management_bp.route('/dc-client/isconnected/<pubkey>')
+        def is_connected(pubkey):
+            communicator = _get_communicator(g)
+            resp = ""
+            if pubkey in communicator.direct_connection_clients:
+                resp = communicator.direct_connection_clients[pubkey]
+            return Response(resp)
+        
+        @direct_conn_management_bp.route('/dc-client/connect/<pubkey>')
+        def make_new_connection(pubkey):
+            communicator = _get_communicator(g)
+            resp = "pending"
+            if pubkey in communicator.shared_state.get(pool.ServicePool).bootstrap_pending:
+                return Response(resp)
+                
+            if pubkey in communicator.direct_connection_clients:
+                resp = communicator.direct_connection_clients[pubkey]
+            else:
+                """Spawn a thread that will create the client and eventually add it to the
+                communicator.active_services 
+                """
+                threading.Thread(target=onionrservices.OnionrServices().create_client, 
+                                 args=[pubkey, communicator], daemon=True).start()
+
+            return Response(resp)
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/fdsafehandler.html b/docs/html/onionr/httpapi/fdsafehandler.html new file mode 100644 index 00000000..9e9a5e0a --- /dev/null +++ b/docs/html/onionr/httpapi/fdsafehandler.html @@ -0,0 +1,135 @@ + + + + + + +onionr.httpapi.fdsafehandler API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.fdsafehandler

+
+
+
+Source code +
from gevent.pywsgi import WSGIServer, WSGIHandler
+from gevent import Timeout
+class FDSafeHandler(WSGIHandler):
+    '''Our WSGI handler. Doesn't do much non-default except timeouts'''
+    def handle(self):
+        self.timeout = Timeout(120, Exception)
+        self.timeout.start()
+        try:
+            WSGIHandler.handle(self)
+        except Timeout as ex:
+            if ex is self.timeout:
+                pass
+            else: 
+                raise
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class FDSafeHandler +(sock, address, server, rfile=None) +
+
+

Our WSGI handler. Doesn't do much non-default except timeouts

+
+Source code +
class FDSafeHandler(WSGIHandler):
+    '''Our WSGI handler. Doesn't do much non-default except timeouts'''
+    def handle(self):
+        self.timeout = Timeout(120, Exception)
+        self.timeout.start()
+        try:
+            WSGIHandler.handle(self)
+        except Timeout as ex:
+            if ex is self.timeout:
+                pass
+            else: 
+                raise
+
+

Ancestors

+
    +
  • gevent.pywsgi.WSGIHandler
  • +
+

Methods

+
+
+def handle(self) +
+
+

The main request handling method, called by the server.

+

This method runs a request handling loop, calling +:meth:handle_one_request until all requests on the +connection have been handled (that is, it implements +keep-alive).

+
+Source code +
def handle(self):
+    self.timeout = Timeout(120, Exception)
+    self.timeout.start()
+    try:
+        WSGIHandler.handle(self)
+    except Timeout as ex:
+        if ex is self.timeout:
+            pass
+        else: 
+            raise
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/friendsapi/index.html b/docs/html/onionr/httpapi/friendsapi/index.html new file mode 100644 index 00000000..c6680f5b --- /dev/null +++ b/docs/html/onionr/httpapi/friendsapi/index.html @@ -0,0 +1,198 @@ + + + + + + +onionr.httpapi.friendsapi API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.friendsapi

+
+
+

Onionr - Private P2P Communication

+

This file creates http endpoints for friend management

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file creates http endpoints for friend management
+'''
+'''
+    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/>.
+'''
+import json
+from onionrusers import contactmanager
+from flask import Blueprint, Response, request, abort, redirect
+
+friends = Blueprint('friends', __name__)
+
+@friends.route('/friends/list')
+def list_friends():
+    pubkey_list = {}
+    friend_list = contactmanager.ContactManager.list_friends()
+    for friend in friend_list:
+        pubkey_list[friend.publicKey] = {'name': friend.get_info('name')}
+    return json.dumps(pubkey_list)
+
+@friends.route('/friends/add/<pubkey>', methods=['POST'])
+def add_friend(pubkey):
+    contactmanager.ContactManager(pubkey, saveUser=True).setTrust(1)
+    return redirect(request.referrer + '#' + request.form['token'])
+
+@friends.route('/friends/remove/<pubkey>', methods=['POST'])
+def remove_friend(pubkey):
+    contactmanager.ContactManager(pubkey).setTrust(0)
+    return redirect(request.referrer + '#' + request.form['token'])
+
+@friends.route('/friends/setinfo/<pubkey>/<key>', methods=['POST'])
+def set_info(pubkey, key):
+    data = request.form['data']
+    contactmanager.ContactManager(pubkey).set_info(key, data)
+    return redirect(request.referrer + '#' + request.form['token'])
+
+@friends.route('/friends/getinfo/<pubkey>/<key>')
+def get_info(pubkey, key):
+    retData = contactmanager.ContactManager(pubkey).get_info(key)
+    if retData is None:
+        abort(404)
+    else:
+        return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def add_friend(pubkey) +
+
+
+
+Source code +
@friends.route('/friends/add/<pubkey>', methods=['POST'])
+def add_friend(pubkey):
+    contactmanager.ContactManager(pubkey, saveUser=True).setTrust(1)
+    return redirect(request.referrer + '#' + request.form['token'])
+
+
+
+def get_info(pubkey, key) +
+
+
+
+Source code +
@friends.route('/friends/getinfo/<pubkey>/<key>')
+def get_info(pubkey, key):
+    retData = contactmanager.ContactManager(pubkey).get_info(key)
+    if retData is None:
+        abort(404)
+    else:
+        return retData
+
+
+
+def list_friends() +
+
+
+
+Source code +
@friends.route('/friends/list')
+def list_friends():
+    pubkey_list = {}
+    friend_list = contactmanager.ContactManager.list_friends()
+    for friend in friend_list:
+        pubkey_list[friend.publicKey] = {'name': friend.get_info('name')}
+    return json.dumps(pubkey_list)
+
+
+
+def remove_friend(pubkey) +
+
+
+
+Source code +
@friends.route('/friends/remove/<pubkey>', methods=['POST'])
+def remove_friend(pubkey):
+    contactmanager.ContactManager(pubkey).setTrust(0)
+    return redirect(request.referrer + '#' + request.form['token'])
+
+
+
+def set_info(pubkey, key) +
+
+
+
+Source code +
@friends.route('/friends/setinfo/<pubkey>/<key>', methods=['POST'])
+def set_info(pubkey, key):
+    data = request.form['data']
+    contactmanager.ContactManager(pubkey).set_info(key, data)
+    return redirect(request.referrer + '#' + request.form['token'])
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/index.html b/docs/html/onionr/httpapi/index.html new file mode 100644 index 00000000..9f65fdd4 --- /dev/null +++ b/docs/html/onionr/httpapi/index.html @@ -0,0 +1,174 @@ + + + + + + +onionr.httpapi API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi

+
+
+

Onionr - Private P2P Communication

+

This file registers plugin's flask blueprints for the client http server

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    This file registers plugin's flask blueprints for the client http server
+"""
+"""
+    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/>.
+"""
+import onionrplugins
+
+def load_plugin_blueprints(flaskapp, blueprint: str = 'flask_blueprint'):
+    """Iterate enabled plugins and load any http endpoints they have"""
+    for plugin in onionrplugins.get_enabled_plugins():
+        plugin = onionrplugins.get_plugin(plugin)
+        try:
+            flaskapp.register_blueprint(getattr(plugin, blueprint))
+        except AttributeError:
+            pass
+
+
+
+

Sub-modules

+
+
onionr.httpapi.apiutils
+
+
+
+
onionr.httpapi.configapi
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.directconnections
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.fdsafehandler
+
+
+
+
onionr.httpapi.friendsapi
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.insertblock
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.miscclientapi
+
+
+
+
onionr.httpapi.miscpublicapi
+
+
+
+
onionr.httpapi.onionrsitesapi
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.profilesapi
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.security
+
+
+
+
+
+
+
+
+

Functions

+
+
+def load_plugin_blueprints(flaskapp, blueprint='flask_blueprint') +
+
+

Iterate enabled plugins and load any http endpoints they have

+
+Source code +
def load_plugin_blueprints(flaskapp, blueprint: str = 'flask_blueprint'):
+    """Iterate enabled plugins and load any http endpoints they have"""
+    for plugin in onionrplugins.get_enabled_plugins():
+        plugin = onionrplugins.get_plugin(plugin)
+        try:
+            flaskapp.register_blueprint(getattr(plugin, blueprint))
+        except AttributeError:
+            pass
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/insertblock.html b/docs/html/onionr/httpapi/insertblock.html new file mode 100644 index 00000000..5451b1a8 --- /dev/null +++ b/docs/html/onionr/httpapi/insertblock.html @@ -0,0 +1,194 @@ + + + + + + +onionr.httpapi.insertblock API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.insertblock

+
+
+

Onionr - Private P2P Communication

+

Create blocks with the client api server

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Create blocks with the client api server
+'''
+'''
+    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/>.
+'''
+import json, threading
+from flask import Blueprint, Response, request, g
+import onionrblocks
+from onionrcrypto import hashers
+from onionrutils import bytesconverter
+from onionrutils import mnemonickeys
+ib = Blueprint('insertblock', __name__)
+
+@ib.route('/insertblock', methods=['POST'])
+def client_api_insert_block():
+    encrypt = False
+    bData = request.get_json(force=True)
+    message = bData['message']
+    message_hash = bytesconverter.bytes_to_str(hashers.sha3_hash(message))
+
+    # Detect if message (block body) is not specified
+    if type(message) is None:
+        return 'failure due to unspecified message', 400
+
+    # Detect if block with same message is already being inserted
+    if message_hash in g.too_many.get_by_string("OnionrCommunicatorDaemon").generating_blocks:
+        return 'failure due to duplicate insert', 400
+    else:
+        g.too_many.get_by_string("OnionrCommunicatorDaemon").generating_blocks.append(message_hash)
+
+    subject = 'temp'
+    encryptType = ''
+    sign = True
+    meta = {}
+    to = ''
+    try:
+        if bData['encrypt']:
+            to = bData['to'].strip()
+            if "-" in to:
+                to = mnemonickeys.get_base32(to)
+            encrypt = True
+            encryptType = 'asym'
+    except KeyError:
+        pass
+    try:
+        if not bData['sign']:
+            sign = False
+    except KeyError:
+        pass
+    try:
+        bType = bData['type']
+    except KeyError:
+        bType = 'bin'
+    try:
+        meta = json.loads(bData['meta'])
+    except KeyError:
+        pass
+    threading.Thread(target=onionrblocks.insert, args=(message,), kwargs={'header': bType, 'encryptType': encryptType, 'sign':sign, 'asymPeer': to, 'meta': meta}).start()
+    return Response('success')
+
+
+
+
+
+
+
+

Functions

+
+
+def client_api_insert_block() +
+
+
+
+Source code +
@ib.route('/insertblock', methods=['POST'])
+def client_api_insert_block():
+    encrypt = False
+    bData = request.get_json(force=True)
+    message = bData['message']
+    message_hash = bytesconverter.bytes_to_str(hashers.sha3_hash(message))
+
+    # Detect if message (block body) is not specified
+    if type(message) is None:
+        return 'failure due to unspecified message', 400
+
+    # Detect if block with same message is already being inserted
+    if message_hash in g.too_many.get_by_string("OnionrCommunicatorDaemon").generating_blocks:
+        return 'failure due to duplicate insert', 400
+    else:
+        g.too_many.get_by_string("OnionrCommunicatorDaemon").generating_blocks.append(message_hash)
+
+    subject = 'temp'
+    encryptType = ''
+    sign = True
+    meta = {}
+    to = ''
+    try:
+        if bData['encrypt']:
+            to = bData['to'].strip()
+            if "-" in to:
+                to = mnemonickeys.get_base32(to)
+            encrypt = True
+            encryptType = 'asym'
+    except KeyError:
+        pass
+    try:
+        if not bData['sign']:
+            sign = False
+    except KeyError:
+        pass
+    try:
+        bType = bData['type']
+    except KeyError:
+        bType = 'bin'
+    try:
+        meta = json.loads(bData['meta'])
+    except KeyError:
+        pass
+    threading.Thread(target=onionrblocks.insert, args=(message,), kwargs={'header': bType, 'encryptType': encryptType, 'sign':sign, 'asymPeer': to, 'meta': meta}).start()
+    return Response('success')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscclientapi/endpoints.html b/docs/html/onionr/httpapi/miscclientapi/endpoints.html new file mode 100644 index 00000000..a419195b --- /dev/null +++ b/docs/html/onionr/httpapi/miscclientapi/endpoints.html @@ -0,0 +1,309 @@ + + + + + + +onionr.httpapi.miscclientapi.endpoints API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscclientapi.endpoints

+
+
+

Onionr - Private P2P Communication

+

Misc client API endpoints too small to need their own file and that need access to the client api inst

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Misc client API endpoints too small to need their own file and that need access to the client api inst
+'''
+'''
+    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 flask import Response, Blueprint, request, send_from_directory, abort
+import unpaddedbase32
+
+from httpapi import apiutils
+import onionrcrypto, config
+from netcontroller import NetController
+from serializeddata import SerializedData
+from onionrutils import mnemonickeys
+from onionrutils import bytesconverter
+from utils import reconstructhash
+from onionrcommands import restartonionr
+
+pub_key = onionrcrypto.pub_key.replace('=', '')
+
+class PrivateEndpoints:
+    def __init__(self, client_api):
+        private_endpoints_bp = Blueprint('privateendpoints', __name__)
+        self.private_endpoints_bp = private_endpoints_bp
+
+        @private_endpoints_bp.route('/www/<path:path>', endpoint='www')
+        def wwwPublic(path):
+            if not config.get("www.private.run", True):
+                abort(403)
+            return send_from_directory(config.get('www.private.path', 'static-data/www/private/'), path)
+
+        @private_endpoints_bp.route('/hitcount')
+        def get_hit_count():
+            return Response(str(client_api.publicAPI.hitCount))
+
+        @private_endpoints_bp.route('/queueResponseAdd/<name>', methods=['post'])
+        def queueResponseAdd(name):
+            # Responses from the daemon. TODO: change to direct var access instead of http endpoint
+            client_api.queueResponse[name] = request.form['data']
+            return Response('success')
+        
+        @private_endpoints_bp.route('/queueResponse/<name>')
+        def queueResponse(name):
+            # Fetch a daemon queue response
+            resp = 'failure'
+            try:
+                resp = client_api.queueResponse[name]
+            except KeyError:
+                pass
+            else:
+                del client_api.queueResponse[name]
+            if resp == 'failure':
+                return resp, 404
+            else:
+                return resp
+            
+        @private_endpoints_bp.route('/ping')
+        def ping():
+            # Used to check if client api is working
+            return Response("pong!")
+
+        @private_endpoints_bp.route('/lastconnect')
+        def lastConnect():
+            return Response(str(client_api.publicAPI.lastRequest))
+
+        @private_endpoints_bp.route('/waitforshare/<name>', methods=['post'])
+        def waitforshare(name):
+            '''Used to prevent the **public** api from sharing blocks we just created'''
+            if not name.isalnum(): raise ValueError('block hash needs to be alpha numeric')
+            name = reconstructhash.reconstruct_hash(name)
+            if name in client_api.publicAPI.hideBlocks:
+                client_api.publicAPI.hideBlocks.remove(name)
+                return Response("removed")
+            else:
+                client_api.publicAPI.hideBlocks.append(name)
+                return Response("added")
+
+        @private_endpoints_bp.route('/shutdown')
+        def shutdown():
+            return apiutils.shutdown.shutdown(client_api)
+
+        @private_endpoints_bp.route('/restartclean')
+        def restart_clean():
+            restartonionr.restart()
+            return Response("bye")
+        
+        @private_endpoints_bp.route('/getstats')
+        def getStats():
+            # returns node stats
+            while True:
+                try:    
+                    return Response(client_api._too_many.get(SerializedData).get_stats())
+                except AttributeError as e:
+                    pass
+        
+        @private_endpoints_bp.route('/getuptime')
+        def showUptime():
+            return Response(str(client_api.getUptime()))
+        
+        @private_endpoints_bp.route('/getActivePubkey')
+        def getActivePubkey():
+            return Response(pub_key)
+
+        @private_endpoints_bp.route('/getHumanReadable')
+        def getHumanReadableDefault():
+            return Response(mnemonickeys.get_human_readable_ID())
+
+        @private_endpoints_bp.route('/getHumanReadable/<name>')
+        def getHumanReadable(name):
+            name = unpaddedbase32.repad(bytesconverter.str_to_bytes(name))
+            return Response(mnemonickeys.get_human_readable_ID(name))
+        
+        @private_endpoints_bp.route('/getBase32FromHumanReadable/<words>')
+        def get_base32_from_human_readable(words):
+            return Response(bytesconverter.bytes_to_str(mnemonickeys.get_base32(words)))
+
+        @private_endpoints_bp.route('/gettorsocks')
+        def get_tor_socks():
+            return Response(str(client_api._too_many.get(NetController).socksPort))
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class PrivateEndpoints +(client_api) +
+
+
+
+Source code +
class PrivateEndpoints:
+    def __init__(self, client_api):
+        private_endpoints_bp = Blueprint('privateendpoints', __name__)
+        self.private_endpoints_bp = private_endpoints_bp
+
+        @private_endpoints_bp.route('/www/<path:path>', endpoint='www')
+        def wwwPublic(path):
+            if not config.get("www.private.run", True):
+                abort(403)
+            return send_from_directory(config.get('www.private.path', 'static-data/www/private/'), path)
+
+        @private_endpoints_bp.route('/hitcount')
+        def get_hit_count():
+            return Response(str(client_api.publicAPI.hitCount))
+
+        @private_endpoints_bp.route('/queueResponseAdd/<name>', methods=['post'])
+        def queueResponseAdd(name):
+            # Responses from the daemon. TODO: change to direct var access instead of http endpoint
+            client_api.queueResponse[name] = request.form['data']
+            return Response('success')
+        
+        @private_endpoints_bp.route('/queueResponse/<name>')
+        def queueResponse(name):
+            # Fetch a daemon queue response
+            resp = 'failure'
+            try:
+                resp = client_api.queueResponse[name]
+            except KeyError:
+                pass
+            else:
+                del client_api.queueResponse[name]
+            if resp == 'failure':
+                return resp, 404
+            else:
+                return resp
+            
+        @private_endpoints_bp.route('/ping')
+        def ping():
+            # Used to check if client api is working
+            return Response("pong!")
+
+        @private_endpoints_bp.route('/lastconnect')
+        def lastConnect():
+            return Response(str(client_api.publicAPI.lastRequest))
+
+        @private_endpoints_bp.route('/waitforshare/<name>', methods=['post'])
+        def waitforshare(name):
+            '''Used to prevent the **public** api from sharing blocks we just created'''
+            if not name.isalnum(): raise ValueError('block hash needs to be alpha numeric')
+            name = reconstructhash.reconstruct_hash(name)
+            if name in client_api.publicAPI.hideBlocks:
+                client_api.publicAPI.hideBlocks.remove(name)
+                return Response("removed")
+            else:
+                client_api.publicAPI.hideBlocks.append(name)
+                return Response("added")
+
+        @private_endpoints_bp.route('/shutdown')
+        def shutdown():
+            return apiutils.shutdown.shutdown(client_api)
+
+        @private_endpoints_bp.route('/restartclean')
+        def restart_clean():
+            restartonionr.restart()
+            return Response("bye")
+        
+        @private_endpoints_bp.route('/getstats')
+        def getStats():
+            # returns node stats
+            while True:
+                try:    
+                    return Response(client_api._too_many.get(SerializedData).get_stats())
+                except AttributeError as e:
+                    pass
+        
+        @private_endpoints_bp.route('/getuptime')
+        def showUptime():
+            return Response(str(client_api.getUptime()))
+        
+        @private_endpoints_bp.route('/getActivePubkey')
+        def getActivePubkey():
+            return Response(pub_key)
+
+        @private_endpoints_bp.route('/getHumanReadable')
+        def getHumanReadableDefault():
+            return Response(mnemonickeys.get_human_readable_ID())
+
+        @private_endpoints_bp.route('/getHumanReadable/<name>')
+        def getHumanReadable(name):
+            name = unpaddedbase32.repad(bytesconverter.str_to_bytes(name))
+            return Response(mnemonickeys.get_human_readable_ID(name))
+        
+        @private_endpoints_bp.route('/getBase32FromHumanReadable/<words>')
+        def get_base32_from_human_readable(words):
+            return Response(bytesconverter.bytes_to_str(mnemonickeys.get_base32(words)))
+
+        @private_endpoints_bp.route('/gettorsocks')
+        def get_tor_socks():
+            return Response(str(client_api._too_many.get(NetController).socksPort))
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscclientapi/getblocks.html b/docs/html/onionr/httpapi/miscclientapi/getblocks.html new file mode 100644 index 00000000..df238228 --- /dev/null +++ b/docs/html/onionr/httpapi/miscclientapi/getblocks.html @@ -0,0 +1,203 @@ + + + + + + +onionr.httpapi.miscclientapi.getblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscclientapi.getblocks

+
+
+

Onionr - Private P2P Communication

+

Create blocks with the client api server

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Create blocks with the client api server
+'''
+'''
+    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 flask import Blueprint, Response, abort
+from onionrblocks import onionrblockapi
+from .. import apiutils
+from onionrutils import stringvalidators
+from coredb import blockmetadb
+
+client_get_block = apiutils.GetBlockData()
+
+client_get_blocks = Blueprint('miscclient', __name__)
+
+@client_get_blocks.route('/getblocksbytype/<name>')
+def get_blocks_by_type_endpoint(name):
+    blocks = blockmetadb.get_blocks_by_type(name)
+    return Response(','.join(blocks))
+
+@client_get_blocks.route('/getblockbody/<name>')
+def getBlockBodyData(name):
+    resp = ''
+    if stringvalidators.validate_hash(name):
+        try:
+            resp = onionrblockapi.Block(name, decrypt=True).bcontent
+        except TypeError:
+            pass
+    else:
+        abort(404)
+    return Response(resp)
+
+@client_get_blocks.route('/getblockdata/<name>')
+def getData(name):
+    resp = ""
+    if stringvalidators.validate_hash(name):
+        if name in blockmetadb.get_block_list():
+            try:
+                resp = client_get_block.get_block_data(name, decrypt=True)
+            except ValueError:
+                pass
+        else:
+            abort(404)
+    else:
+        abort(404)
+    return Response(resp)
+
+@client_get_blocks.route('/getblockheader/<name>')
+def getBlockHeader(name):
+    resp = client_get_block.get_block_data(name, decrypt=True, headerOnly=True)
+    return Response(resp)
+
+
+
+
+
+
+
+

Functions

+
+
+def getBlockBodyData(name) +
+
+
+
+Source code +
@client_get_blocks.route('/getblockbody/<name>')
+def getBlockBodyData(name):
+    resp = ''
+    if stringvalidators.validate_hash(name):
+        try:
+            resp = onionrblockapi.Block(name, decrypt=True).bcontent
+        except TypeError:
+            pass
+    else:
+        abort(404)
+    return Response(resp)
+
+
+
+def getBlockHeader(name) +
+
+
+
+Source code +
@client_get_blocks.route('/getblockheader/<name>')
+def getBlockHeader(name):
+    resp = client_get_block.get_block_data(name, decrypt=True, headerOnly=True)
+    return Response(resp)
+
+
+
+def getData(name) +
+
+
+
+Source code +
@client_get_blocks.route('/getblockdata/<name>')
+def getData(name):
+    resp = ""
+    if stringvalidators.validate_hash(name):
+        if name in blockmetadb.get_block_list():
+            try:
+                resp = client_get_block.get_block_data(name, decrypt=True)
+            except ValueError:
+                pass
+        else:
+            abort(404)
+    else:
+        abort(404)
+    return Response(resp)
+
+
+
+def get_blocks_by_type_endpoint(name) +
+
+
+
+Source code +
@client_get_blocks.route('/getblocksbytype/<name>')
+def get_blocks_by_type_endpoint(name):
+    blocks = blockmetadb.get_blocks_by_type(name)
+    return Response(','.join(blocks))
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscclientapi/index.html b/docs/html/onionr/httpapi/miscclientapi/index.html new file mode 100644 index 00000000..60f471f5 --- /dev/null +++ b/docs/html/onionr/httpapi/miscclientapi/index.html @@ -0,0 +1,79 @@ + + + + + + +onionr.httpapi.miscclientapi API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscclientapi

+
+
+
+Source code +
from . import getblocks, staticfiles, endpoints
+
+
+
+

Sub-modules

+
+
onionr.httpapi.miscclientapi.endpoints
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.miscclientapi.getblocks
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.miscclientapi.staticfiles
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscclientapi/staticfiles.html b/docs/html/onionr/httpapi/miscclientapi/staticfiles.html new file mode 100644 index 00000000..1aac0e75 --- /dev/null +++ b/docs/html/onionr/httpapi/miscclientapi/staticfiles.html @@ -0,0 +1,280 @@ + + + + + + +onionr.httpapi.miscclientapi.staticfiles API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscclientapi.staticfiles

+
+
+

Onionr - Private P2P Communication

+

Register static file routes

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Register static file routes
+'''
+'''
+    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/>.
+'''
+import os
+import mimetypes
+from flask import Blueprint, send_from_directory
+
+# Was having some mime type issues on windows, this appeared to fix it.
+# we have no-sniff set, so if the mime types are invalid sripts can't load.
+mimetypes.add_type('application/javascript', '.js')
+mimetypes.add_type('text/css', '.css')
+
+static_files_bp = Blueprint('staticfiles', __name__)
+
+root = os.path.dirname(os.path.realpath(__file__)) + '/../../../static-data/www/' # should be set to onionr install directory from onionr startup
+
+@static_files_bp.route('/chat/', endpoint='chatIndex')
+def chat_index():
+    return send_from_directory(root + 'chat/', "index.html")
+
+@static_files_bp.route('/chat/<path:path>', endpoint='chat')
+def load_chat(path):
+    return send_from_directory(root + 'chat/', path)
+
+@static_files_bp.route('/board/', endpoint='board')
+def loadBoard():
+    return send_from_directory(root + 'board/', "index.html")
+
+@static_files_bp.route('/mail/<path:path>', endpoint='mail')
+def loadMail(path):
+    return send_from_directory(root + 'mail/', path)
+
+@static_files_bp.route('/mail/', endpoint='mailindex')
+def loadMailIndex():
+    return send_from_directory(root + 'mail/', 'index.html')
+
+@static_files_bp.route('/friends/<path:path>', endpoint='friends')
+def loadContacts(path):
+    return send_from_directory(root + 'friends/', path)
+
+@static_files_bp.route('/friends/', endpoint='friendsindex')
+def loadContacts():
+    return send_from_directory(root + 'friends/', 'index.html')
+
+@static_files_bp.route('/profiles/<path:path>', endpoint='profiles')
+def loadContacts(path):
+    return send_from_directory(root + 'profiles/', path)
+
+@static_files_bp.route('/profiles/', endpoint='profilesindex')
+def loadContacts():
+    return send_from_directory(root + 'profiles/', 'index.html')
+
+@static_files_bp.route('/board/<path:path>', endpoint='boardContent')
+def boardContent(path):
+    return send_from_directory(root + 'board/', path)
+
+@static_files_bp.route('/shared/<path:path>', endpoint='sharedContent')
+def sharedContent(path):
+    return send_from_directory(root + 'shared/', path)
+
+@static_files_bp.route('/', endpoint='onionrhome')
+def hello():
+    # ui home
+    return send_from_directory(root + 'private/', 'index.html')
+
+@static_files_bp.route('/private/<path:path>', endpoint='homedata')
+def homedata(path):
+    return send_from_directory(root + 'private/', path)
+
+
+
+
+
+
+
+

Functions

+
+
+def boardContent(path) +
+
+
+
+Source code +
@static_files_bp.route('/board/<path:path>', endpoint='boardContent')
+def boardContent(path):
+    return send_from_directory(root + 'board/', path)
+
+
+
+def chat_index() +
+
+
+
+Source code +
@static_files_bp.route('/chat/', endpoint='chatIndex')
+def chat_index():
+    return send_from_directory(root + 'chat/', "index.html")
+
+
+
+def hello() +
+
+
+
+Source code +
@static_files_bp.route('/', endpoint='onionrhome')
+def hello():
+    # ui home
+    return send_from_directory(root + 'private/', 'index.html')
+
+
+
+def homedata(path) +
+
+
+
+Source code +
@static_files_bp.route('/private/<path:path>', endpoint='homedata')
+def homedata(path):
+    return send_from_directory(root + 'private/', path)
+
+
+
+def loadBoard() +
+
+
+
+Source code +
@static_files_bp.route('/board/', endpoint='board')
+def loadBoard():
+    return send_from_directory(root + 'board/', "index.html")
+
+
+
+def loadContacts() +
+
+
+
+Source code +
@static_files_bp.route('/profiles/', endpoint='profilesindex')
+def loadContacts():
+    return send_from_directory(root + 'profiles/', 'index.html')
+
+
+
+def loadMail(path) +
+
+
+
+Source code +
@static_files_bp.route('/mail/<path:path>', endpoint='mail')
+def loadMail(path):
+    return send_from_directory(root + 'mail/', path)
+
+
+
+def loadMailIndex() +
+
+
+
+Source code +
@static_files_bp.route('/mail/', endpoint='mailindex')
+def loadMailIndex():
+    return send_from_directory(root + 'mail/', 'index.html')
+
+
+
+def load_chat(path) +
+
+
+
+Source code +
@static_files_bp.route('/chat/<path:path>', endpoint='chat')
+def load_chat(path):
+    return send_from_directory(root + 'chat/', path)
+
+
+
+def sharedContent(path) +
+
+
+
+Source code +
@static_files_bp.route('/shared/<path:path>', endpoint='sharedContent')
+def sharedContent(path):
+    return send_from_directory(root + 'shared/', path)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscpublicapi/announce.html b/docs/html/onionr/httpapi/miscpublicapi/announce.html new file mode 100644 index 00000000..00d3760c --- /dev/null +++ b/docs/html/onionr/httpapi/miscpublicapi/announce.html @@ -0,0 +1,200 @@ + + + + + + +onionr.httpapi.miscpublicapi.announce API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscpublicapi.announce

+
+
+

Onionr - Private P2P Communication

+

Handle announcements to the public API server

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Handle announcements to the public API server
+'''
+'''
+    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/>.
+'''
+import base64
+from flask import Response, g
+import deadsimplekv
+import logger
+from etc import onionrvalues
+from onionrutils import stringvalidators, bytesconverter
+from utils import gettransports
+import onionrcrypto as crypto, filepaths
+from communicator import OnionrCommunicatorDaemon
+def handle_announce(request):
+    '''
+    accept announcement posts, validating POW
+    clientAPI should be an instance of the clientAPI server running, request is a instance of a flask request
+    '''
+    resp = 'failure'
+    powHash = ''
+    randomData = ''
+    newNode = ''
+
+    try:
+        newNode = request.form['node'].encode()
+    except KeyError:
+        logger.warn('No node specified for upload')
+        pass
+    else:
+        try:
+            randomData = request.form['random']
+            randomData = base64.b64decode(randomData)
+        except KeyError:
+            logger.warn('No random data specified for upload')
+        else:
+            nodes = newNode + bytesconverter.str_to_bytes(gettransports.get()[0])
+            nodes = crypto.hashers.blake2b_hash(nodes)
+            powHash = crypto.hashers.blake2b_hash(randomData + nodes)
+            try:
+                powHash = powHash.decode()
+            except AttributeError:
+                pass
+            if powHash.startswith('0' * onionrvalues.ANNOUNCE_POW):
+                newNode = bytesconverter.bytes_to_str(newNode)
+                announce_queue = deadsimplekv.DeadSimpleKV(filepaths.announce_cache)
+                announce_queue_list = announce_queue.get('new_peers')
+                if announce_queue_list is None:
+                    announce_queue_list = []
+
+                if stringvalidators.validate_transport(newNode) and not newNode in announce_queue_list:
+                    #clientAPI.onionrInst.communicatorInst.newPeers.append(newNode)
+                    g.shared_state.get(OnionrCommunicatorDaemon).newPeers.append(newNode)
+                    announce_queue.put('new_peers', announce_queue_list.append(newNode))
+                    announce_queue.flush()
+                    resp = 'Success'
+            else:
+                logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash)
+    resp = Response(resp)
+    if resp == 'failure':
+        return resp, 406
+    return resp
+
+
+
+
+
+
+
+

Functions

+
+
+def handle_announce(request) +
+
+

accept announcement posts, validating POW +clientAPI should be an instance of the clientAPI server running, request is a instance of a flask request

+
+Source code +
def handle_announce(request):
+    '''
+    accept announcement posts, validating POW
+    clientAPI should be an instance of the clientAPI server running, request is a instance of a flask request
+    '''
+    resp = 'failure'
+    powHash = ''
+    randomData = ''
+    newNode = ''
+
+    try:
+        newNode = request.form['node'].encode()
+    except KeyError:
+        logger.warn('No node specified for upload')
+        pass
+    else:
+        try:
+            randomData = request.form['random']
+            randomData = base64.b64decode(randomData)
+        except KeyError:
+            logger.warn('No random data specified for upload')
+        else:
+            nodes = newNode + bytesconverter.str_to_bytes(gettransports.get()[0])
+            nodes = crypto.hashers.blake2b_hash(nodes)
+            powHash = crypto.hashers.blake2b_hash(randomData + nodes)
+            try:
+                powHash = powHash.decode()
+            except AttributeError:
+                pass
+            if powHash.startswith('0' * onionrvalues.ANNOUNCE_POW):
+                newNode = bytesconverter.bytes_to_str(newNode)
+                announce_queue = deadsimplekv.DeadSimpleKV(filepaths.announce_cache)
+                announce_queue_list = announce_queue.get('new_peers')
+                if announce_queue_list is None:
+                    announce_queue_list = []
+
+                if stringvalidators.validate_transport(newNode) and not newNode in announce_queue_list:
+                    #clientAPI.onionrInst.communicatorInst.newPeers.append(newNode)
+                    g.shared_state.get(OnionrCommunicatorDaemon).newPeers.append(newNode)
+                    announce_queue.put('new_peers', announce_queue_list.append(newNode))
+                    announce_queue.flush()
+                    resp = 'Success'
+            else:
+                logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash)
+    resp = Response(resp)
+    if resp == 'failure':
+        return resp, 406
+    return resp
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscpublicapi/endpoints.html b/docs/html/onionr/httpapi/miscpublicapi/endpoints.html new file mode 100644 index 00000000..ca5b9fe9 --- /dev/null +++ b/docs/html/onionr/httpapi/miscpublicapi/endpoints.html @@ -0,0 +1,217 @@ + + + + + + +onionr.httpapi.miscpublicapi.endpoints API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscpublicapi.endpoints

+
+
+

Onionr - Private P2P Communication

+

Misc public API endpoints too small to need their own file and that need access to the public api inst

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Misc public API endpoints too small to need their own file and that need access to the public api inst
+'''
+'''
+    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 flask import Response, Blueprint, request, send_from_directory, abort, g
+from . import getblocks, upload, announce
+from coredb import keydb
+import config
+class PublicEndpoints:
+    def __init__(self, public_api):
+
+        public_endpoints_bp = Blueprint('publicendpoints', __name__)
+        self.public_endpoints_bp = public_endpoints_bp
+
+        @public_endpoints_bp.route('/')
+        def banner():
+            # Display a bit of information to people who visit a node address in their browser
+            try:
+                with open('../static-data/index.html', 'r') as html:
+                    resp = Response(html.read(), mimetype='text/html')
+            except FileNotFoundError:
+                resp = Response("")
+            return resp
+
+        @public_endpoints_bp.route('/getblocklist')
+        def get_block_list():
+            '''Get a list of blocks, optionally filtered by epoch time stamp, excluding those hidden'''
+            return getblocks.get_public_block_list(public_api, request)
+
+        @public_endpoints_bp.route('/getdata/<name>')
+        def get_block_data(name):
+            # Share data for a block if we have it and it isn't hidden
+            return getblocks.get_block_data(public_api, name)
+
+        @public_endpoints_bp.route('/www/<path:path>')
+        def www_public(path):
+            # A way to share files directly over your .onion
+            if not config.get("www.public.run", True):
+                abort(403)
+            return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path)
+
+        @public_endpoints_bp.route('/ping')
+        def ping():
+            # Endpoint to test if nodes are up
+            return Response("pong!")
+
+        @public_endpoints_bp.route('/pex')
+        def peer_exchange():
+            response = ','.join(keydb.listkeys.list_adders(recent=3600))
+            if len(response) == 0:
+                response = ''
+            return Response(response)
+
+        @public_endpoints_bp.route('/announce', methods=['post'])
+        def accept_announce():
+            '''Accept announcements with pow token to prevent spam'''
+            g.shared_state = public_api._too_many
+            resp = announce.handle_announce(request)
+            return resp
+
+        @public_endpoints_bp.route('/upload', methods=['post'])
+        def upload_endpoint():
+            '''Accept file uploads. In the future this will be done more often than on creation 
+            to speed up block sync
+            '''
+            return upload.accept_upload(request)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class PublicEndpoints +(public_api) +
+
+
+
+Source code +
class PublicEndpoints:
+    def __init__(self, public_api):
+
+        public_endpoints_bp = Blueprint('publicendpoints', __name__)
+        self.public_endpoints_bp = public_endpoints_bp
+
+        @public_endpoints_bp.route('/')
+        def banner():
+            # Display a bit of information to people who visit a node address in their browser
+            try:
+                with open('../static-data/index.html', 'r') as html:
+                    resp = Response(html.read(), mimetype='text/html')
+            except FileNotFoundError:
+                resp = Response("")
+            return resp
+
+        @public_endpoints_bp.route('/getblocklist')
+        def get_block_list():
+            '''Get a list of blocks, optionally filtered by epoch time stamp, excluding those hidden'''
+            return getblocks.get_public_block_list(public_api, request)
+
+        @public_endpoints_bp.route('/getdata/<name>')
+        def get_block_data(name):
+            # Share data for a block if we have it and it isn't hidden
+            return getblocks.get_block_data(public_api, name)
+
+        @public_endpoints_bp.route('/www/<path:path>')
+        def www_public(path):
+            # A way to share files directly over your .onion
+            if not config.get("www.public.run", True):
+                abort(403)
+            return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path)
+
+        @public_endpoints_bp.route('/ping')
+        def ping():
+            # Endpoint to test if nodes are up
+            return Response("pong!")
+
+        @public_endpoints_bp.route('/pex')
+        def peer_exchange():
+            response = ','.join(keydb.listkeys.list_adders(recent=3600))
+            if len(response) == 0:
+                response = ''
+            return Response(response)
+
+        @public_endpoints_bp.route('/announce', methods=['post'])
+        def accept_announce():
+            '''Accept announcements with pow token to prevent spam'''
+            g.shared_state = public_api._too_many
+            resp = announce.handle_announce(request)
+            return resp
+
+        @public_endpoints_bp.route('/upload', methods=['post'])
+        def upload_endpoint():
+            '''Accept file uploads. In the future this will be done more often than on creation 
+            to speed up block sync
+            '''
+            return upload.accept_upload(request)
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscpublicapi/getblocks.html b/docs/html/onionr/httpapi/miscpublicapi/getblocks.html new file mode 100644 index 00000000..8553ba25 --- /dev/null +++ b/docs/html/onionr/httpapi/miscpublicapi/getblocks.html @@ -0,0 +1,175 @@ + + + + + + +onionr.httpapi.miscpublicapi.getblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscpublicapi.getblocks

+
+
+

Onionr - Private P2P Communication

+

Public endpoints to get block data and lists

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Public endpoints to get block data and lists
+'''
+'''
+    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 flask import Response, abort
+import config
+from onionrutils import bytesconverter, stringvalidators
+from coredb import blockmetadb
+from utils import reconstructhash
+from .. import apiutils
+def get_public_block_list(publicAPI, request):
+    # Provide a list of our blocks, with a date offset
+    dateAdjust = request.args.get('date')
+    bList = blockmetadb.get_block_list(dateRec=dateAdjust)
+    share_list = ''
+    if config.get('general.hide_created_blocks', True):
+        for b in publicAPI.hideBlocks:
+            if b in bList:
+                # Don't share blocks we created if they haven't been *uploaded* yet, makes it harder to find who created a block
+                bList.remove(b)
+    for b in bList:
+        share_list += '%s\n' % (reconstructhash.deconstruct_hash(b),)
+    return Response(share_list)
+
+def get_block_data(publicAPI, data):
+    '''data is the block hash in hex'''
+    resp = ''
+    if stringvalidators.validate_hash(data):
+        if not config.get('general.hide_created_blocks', True) or data not in publicAPI.hideBlocks:
+            if data in blockmetadb.get_block_list():
+                block = apiutils.GetBlockData().get_block_data(data, raw=True)
+                try:
+                    block = block.encode() # Encode in case data is binary
+                except AttributeError:
+                    if len(block) == 0:
+                        abort(404)
+                block = bytesconverter.str_to_bytes(block)
+                resp = block
+    if len(resp) == 0:
+        abort(404)
+        resp = ""
+    # Has to be octet stream, otherwise binary data fails hash check
+    return Response(resp, mimetype='application/octet-stream')
+
+
+
+
+
+
+
+

Functions

+
+
+def get_block_data(publicAPI, data) +
+
+

data is the block hash in hex

+
+Source code +
def get_block_data(publicAPI, data):
+    '''data is the block hash in hex'''
+    resp = ''
+    if stringvalidators.validate_hash(data):
+        if not config.get('general.hide_created_blocks', True) or data not in publicAPI.hideBlocks:
+            if data in blockmetadb.get_block_list():
+                block = apiutils.GetBlockData().get_block_data(data, raw=True)
+                try:
+                    block = block.encode() # Encode in case data is binary
+                except AttributeError:
+                    if len(block) == 0:
+                        abort(404)
+                block = bytesconverter.str_to_bytes(block)
+                resp = block
+    if len(resp) == 0:
+        abort(404)
+        resp = ""
+    # Has to be octet stream, otherwise binary data fails hash check
+    return Response(resp, mimetype='application/octet-stream')
+
+
+
+def get_public_block_list(publicAPI, request) +
+
+
+
+Source code +
def get_public_block_list(publicAPI, request):
+    # Provide a list of our blocks, with a date offset
+    dateAdjust = request.args.get('date')
+    bList = blockmetadb.get_block_list(dateRec=dateAdjust)
+    share_list = ''
+    if config.get('general.hide_created_blocks', True):
+        for b in publicAPI.hideBlocks:
+            if b in bList:
+                # Don't share blocks we created if they haven't been *uploaded* yet, makes it harder to find who created a block
+                bList.remove(b)
+    for b in bList:
+        share_list += '%s\n' % (reconstructhash.deconstruct_hash(b),)
+    return Response(share_list)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscpublicapi/index.html b/docs/html/onionr/httpapi/miscpublicapi/index.html new file mode 100644 index 00000000..ddd946ba --- /dev/null +++ b/docs/html/onionr/httpapi/miscpublicapi/index.html @@ -0,0 +1,89 @@ + + + + + + +onionr.httpapi.miscpublicapi API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscpublicapi

+
+
+
+Source code +
from . import announce, upload, getblocks, endpoints
+
+announce = announce.handle_announce # endpoint handler for accepting peer announcements
+upload = upload.accept_upload # endpoint handler for accepting public uploads
+public_block_list = getblocks.get_public_block_list # endpoint handler for getting block lists
+public_get_block_data = getblocks.get_block_data # endpoint handler for responding to peers requests for block data
+
+
+
+

Sub-modules

+
+
onionr.httpapi.miscpublicapi.announce
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.miscpublicapi.endpoints
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.miscpublicapi.getblocks
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.miscpublicapi.upload
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/miscpublicapi/upload.html b/docs/html/onionr/httpapi/miscpublicapi/upload.html new file mode 100644 index 00000000..ac2cf0ce --- /dev/null +++ b/docs/html/onionr/httpapi/miscpublicapi/upload.html @@ -0,0 +1,148 @@ + + + + + + +onionr.httpapi.miscpublicapi.upload API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.miscpublicapi.upload

+
+
+

Onionr - Private P2P Communication

+

Accept block uploads to the public API server

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Accept block uploads to the public API server
+'''
+'''
+    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/>.
+'''
+import sys
+from flask import Response, abort
+
+from onionrblocks import blockimporter
+import onionrexceptions, logger
+
+def accept_upload(request):
+    resp = 'failure'
+    data = request.get_data()
+    if sys.getsizeof(data) < 100000000:
+        try:
+            if blockimporter.importBlockFromData(data):
+                resp = 'success'
+            else:
+                resp = 'failure'
+                logger.warn('Error encountered importing uploaded block')
+        except onionrexceptions.BlacklistedBlock:
+            logger.debug('uploaded block is blacklisted')
+            resp = 'failure'
+        except onionrexceptions.InvalidProof:
+            resp = 'proof'
+        except onionrexceptions.DataExists:
+            resp = 'exists'
+    if resp == 'failure':
+        abort(400)
+    elif resp == 'proof':
+        resp = Response(resp, 400)
+    else:
+        resp = Response(resp)
+    return resp
+
+
+
+
+
+
+
+

Functions

+
+
+def accept_upload(request) +
+
+
+
+Source code +
def accept_upload(request):
+    resp = 'failure'
+    data = request.get_data()
+    if sys.getsizeof(data) < 100000000:
+        try:
+            if blockimporter.importBlockFromData(data):
+                resp = 'success'
+            else:
+                resp = 'failure'
+                logger.warn('Error encountered importing uploaded block')
+        except onionrexceptions.BlacklistedBlock:
+            logger.debug('uploaded block is blacklisted')
+            resp = 'failure'
+        except onionrexceptions.InvalidProof:
+            resp = 'proof'
+        except onionrexceptions.DataExists:
+            resp = 'exists'
+    if resp == 'failure':
+        abort(400)
+    elif resp == 'proof':
+        resp = Response(resp, 400)
+    else:
+        resp = Response(resp)
+    return resp
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/onionrsitesapi/index.html b/docs/html/onionr/httpapi/onionrsitesapi/index.html new file mode 100644 index 00000000..087ddae0 --- /dev/null +++ b/docs/html/onionr/httpapi/onionrsitesapi/index.html @@ -0,0 +1,139 @@ + + + + + + +onionr.httpapi.onionrsitesapi API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.onionrsitesapi

+
+
+

Onionr - Private P2P Communication

+

view and interact with onionr sites

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    view and interact with onionr sites
+'''
+'''
+    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/>.
+'''
+import base64
+import binascii
+from flask import Blueprint, Response, request, abort
+from onionrblocks import onionrblockapi
+import onionrexceptions
+from onionrutils import stringvalidators
+
+site_api = Blueprint('siteapi', __name__)
+
+@site_api.route('/site/<name>', endpoint='site')
+def site(name):
+    bHash = name
+    resp = 'Not Found'
+    if stringvalidators.validate_hash(bHash):
+        try:
+            resp = onionrblockapi.Block(bHash).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)
+
+
+
+
+
+
+
+

Functions

+
+
+def site(name) +
+
+
+
+Source code +
@site_api.route('/site/<name>', endpoint='site')
+def site(name):
+    bHash = name
+    resp = 'Not Found'
+    if stringvalidators.validate_hash(bHash):
+        try:
+            resp = onionrblockapi.Block(bHash).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)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/profilesapi/index.html b/docs/html/onionr/httpapi/profilesapi/index.html new file mode 100644 index 00000000..8b5bb4a1 --- /dev/null +++ b/docs/html/onionr/httpapi/profilesapi/index.html @@ -0,0 +1,117 @@ + + + + + + +onionr.httpapi.profilesapi API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.profilesapi

+
+
+

Onionr - Private P2P Communication

+

This file creates http endpoints for user profile pages

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file creates http endpoints for user profile pages
+'''
+'''
+    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 flask import Blueprint, Response, request, abort
+from . import profiles
+
+profile_BP = Blueprint('profile_BP', __name__)
+
+@profile_BP.route('/profile/get/<pubkey>', endpoint='profiles')
+def get_profile_page(pubkey):
+    return Response(pubkey)
+
+
+
+

Sub-modules

+
+
onionr.httpapi.profilesapi.profiles
+
+
+
+
+
+
+
+
+

Functions

+
+
+def get_profile_page(pubkey) +
+
+
+
+Source code +
@profile_BP.route('/profile/get/<pubkey>', endpoint='profiles')
+def get_profile_page(pubkey):
+    return Response(pubkey)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/profilesapi/profiles.html b/docs/html/onionr/httpapi/profilesapi/profiles.html new file mode 100644 index 00000000..02e05882 --- /dev/null +++ b/docs/html/onionr/httpapi/profilesapi/profiles.html @@ -0,0 +1,77 @@ + + + + + + +onionr.httpapi.profilesapi.profiles API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.profilesapi.profiles

+
+
+
+Source code +
def get_latest_user_profile(pubkey):
+    return ''
+
+
+
+
+
+
+
+

Functions

+
+
+def get_latest_user_profile(pubkey) +
+
+
+
+Source code +
def get_latest_user_profile(pubkey):
+    return ''
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/security/client.html b/docs/html/onionr/httpapi/security/client.html new file mode 100644 index 00000000..eb72078b --- /dev/null +++ b/docs/html/onionr/httpapi/security/client.html @@ -0,0 +1,184 @@ + + + + + + +onionr.httpapi.security.client API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.security.client

+
+
+

Onionr - Private P2P Communication

+

Process incoming requests to the client api server to validate they are legitimate

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Process incoming requests to the client api server to validate they are legitimate
+'''
+'''
+    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/>.
+'''
+import hmac
+from flask import Blueprint, request, abort, g
+from onionrservices import httpheaders
+from . import pluginwhitelist
+
+# Be extremely mindful of this. These are endpoints available without a password
+whitelist_endpoints = ['www', 'staticfiles.homedata', 'staticfiles.sharedContent', 
+'staticfiles.friends', 'staticfiles.friendsindex', 'siteapi.site', 'staticfiles.onionrhome']
+
+class ClientAPISecurity:
+    def __init__(self, client_api):
+        client_api_security_bp = Blueprint('clientapisecurity', __name__)
+        self.client_api_security_bp = client_api_security_bp
+        self.client_api = client_api
+        pluginwhitelist.load_plugin_security_whitelist_endpoints(whitelist_endpoints)
+
+        @client_api_security_bp.before_app_request
+        def validate_request():
+            '''Validate request has set password and is the correct hostname'''
+            # For the purpose of preventing DNS rebinding attacks
+            if request.host != '%s:%s' % (client_api.host, client_api.bindPort):
+                abort(403)
+
+            # Add shared objects
+            try:
+                g.too_many = self.client_api._too_many
+            except KeyError:
+                g.too_many = None
+
+            if request.endpoint in whitelist_endpoints:
+                return
+            try:
+                if not hmac.compare_digest(request.headers['token'], client_api.clientToken):
+                    if not hmac.compare_digest(request.form['token'], client_api.clientToken):
+                        abort(403)
+            except KeyError:
+                if not hmac.compare_digest(request.form['token'], client_api.clientToken):
+                    abort(403)
+
+        @client_api_security_bp.after_app_request
+        def after_req(resp):
+            # Security headers
+            resp = httpheaders.set_default_onionr_http_headers(resp)
+            if request.endpoint == 'siteapi.site':
+                resp.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:"
+            else:
+                resp.headers['Content-Security-Policy'] = "default-src 'none'; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'none'; frame-src 'none'; font-src 'self'; connect-src 'self'"
+            return resp
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class ClientAPISecurity +(client_api) +
+
+
+
+Source code +
class ClientAPISecurity:
+    def __init__(self, client_api):
+        client_api_security_bp = Blueprint('clientapisecurity', __name__)
+        self.client_api_security_bp = client_api_security_bp
+        self.client_api = client_api
+        pluginwhitelist.load_plugin_security_whitelist_endpoints(whitelist_endpoints)
+
+        @client_api_security_bp.before_app_request
+        def validate_request():
+            '''Validate request has set password and is the correct hostname'''
+            # For the purpose of preventing DNS rebinding attacks
+            if request.host != '%s:%s' % (client_api.host, client_api.bindPort):
+                abort(403)
+
+            # Add shared objects
+            try:
+                g.too_many = self.client_api._too_many
+            except KeyError:
+                g.too_many = None
+
+            if request.endpoint in whitelist_endpoints:
+                return
+            try:
+                if not hmac.compare_digest(request.headers['token'], client_api.clientToken):
+                    if not hmac.compare_digest(request.form['token'], client_api.clientToken):
+                        abort(403)
+            except KeyError:
+                if not hmac.compare_digest(request.form['token'], client_api.clientToken):
+                    abort(403)
+
+        @client_api_security_bp.after_app_request
+        def after_req(resp):
+            # Security headers
+            resp = httpheaders.set_default_onionr_http_headers(resp)
+            if request.endpoint == 'siteapi.site':
+                resp.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:"
+            else:
+                resp.headers['Content-Security-Policy'] = "default-src 'none'; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'none'; frame-src 'none'; font-src 'self'; connect-src 'self'"
+            return resp
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/security/index.html b/docs/html/onionr/httpapi/security/index.html new file mode 100644 index 00000000..b9ab048f --- /dev/null +++ b/docs/html/onionr/httpapi/security/index.html @@ -0,0 +1,79 @@ + + + + + + +onionr.httpapi.security API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.security

+
+
+
+Source code +
from . import client, public
+
+
+
+

Sub-modules

+
+
onionr.httpapi.security.client
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.security.pluginwhitelist
+
+

Onionr - Private P2P Communication …

+
+
onionr.httpapi.security.public
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/security/pluginwhitelist.html b/docs/html/onionr/httpapi/security/pluginwhitelist.html new file mode 100644 index 00000000..4afd34fd --- /dev/null +++ b/docs/html/onionr/httpapi/security/pluginwhitelist.html @@ -0,0 +1,120 @@ + + + + + + +onionr.httpapi.security.pluginwhitelist API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.security.pluginwhitelist

+
+
+

Onionr - Private P2P Communication

+

Load web UI client endpoints into the whitelist from plugins

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    Load web UI client endpoints into the whitelist from plugins
+"""
+"""
+    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/>.
+"""
+import onionrplugins
+def load_plugin_security_whitelist_endpoints(whitelist: list):
+    """Accept a list reference of whitelist endpoints from security/client.py and
+    append plugin's specified endpoints to them by attribute"""
+    for plugin in onionrplugins.get_enabled_plugins():
+        try:
+            plugin = onionrplugins.get_plugin(plugin)
+        except FileNotFoundError:
+            continue
+        try:
+            whitelist.extend(getattr(plugin, "security_whitelist"))
+        except AttributeError:
+            pass
+
+
+
+
+
+
+
+

Functions

+
+
+def load_plugin_security_whitelist_endpoints(whitelist) +
+
+

Accept a list reference of whitelist endpoints from security/client.py and +append plugin's specified endpoints to them by attribute

+
+Source code +
def load_plugin_security_whitelist_endpoints(whitelist: list):
+    """Accept a list reference of whitelist endpoints from security/client.py and
+    append plugin's specified endpoints to them by attribute"""
+    for plugin in onionrplugins.get_enabled_plugins():
+        try:
+            plugin = onionrplugins.get_plugin(plugin)
+        except FileNotFoundError:
+            continue
+        try:
+            whitelist.extend(getattr(plugin, "security_whitelist"))
+        except AttributeError:
+            pass
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/httpapi/security/public.html b/docs/html/onionr/httpapi/security/public.html new file mode 100644 index 00000000..d3d13860 --- /dev/null +++ b/docs/html/onionr/httpapi/security/public.html @@ -0,0 +1,175 @@ + + + + + + +onionr.httpapi.security.public API documentation + + + + + + + + + +
+
+
+

Module onionr.httpapi.security.public

+
+
+

Onionr - Private P2P Communication

+

Process incoming requests to the public api server for certain attacks

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Process incoming requests to the public api server for certain attacks
+'''
+'''
+    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 flask import Blueprint, request, abort, g
+from onionrservices import httpheaders
+from onionrutils import epoch
+from utils import gettransports
+class PublicAPISecurity:
+    def __init__(self, public_api):
+        public_api_security_bp = Blueprint('publicapisecurity', __name__)
+        self.public_api_security_bp = public_api_security_bp
+
+        @public_api_security_bp.before_app_request
+        def validate_request():
+            '''Validate request has the correct hostname'''
+            # If high security level, deny requests to public (HS should be disabled anyway for Tor, but might not be for I2P)
+            transports = gettransports.get()
+            if public_api.config.get('general.security_level', default=1) > 0:
+                abort(403)
+            if request.host not in transports:
+                # Disallow connection if wrong HTTP hostname, in order to prevent DNS rebinding attacks
+                abort(403)
+            public_api.hitCount += 1 # raise hit count for valid requests
+            try:
+                if 'onionr' in request.headers['User-Agent'].lower():
+                    g.is_onionr_client = True
+                else:
+                    g.is_onionr_client = False
+            except KeyError:
+                g.is_onionr_client = False
+
+        @public_api_security_bp.after_app_request
+        def send_headers(resp):
+            '''Send api, access control headers'''
+            resp = httpheaders.set_default_onionr_http_headers(resp)
+            # Network API version
+            resp.headers['X-API'] = public_api.API_VERSION
+            # Delete some HTTP headers for Onionr user agents
+            NON_NETWORK_HEADERS = ('Content-Security-Policy', 'X-Frame-Options',
+            'X-Content-Type-Options', 'Feature-Policy', 'Clear-Site-Data', 'Referrer-Policy')
+            if g.is_onionr_client:
+                for header in NON_NETWORK_HEADERS: del resp.headers[header]
+            public_api.lastRequest = epoch.get_rounded_epoch(roundS=5)
+            return resp
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class PublicAPISecurity +(public_api) +
+
+
+
+Source code +
class PublicAPISecurity:
+    def __init__(self, public_api):
+        public_api_security_bp = Blueprint('publicapisecurity', __name__)
+        self.public_api_security_bp = public_api_security_bp
+
+        @public_api_security_bp.before_app_request
+        def validate_request():
+            '''Validate request has the correct hostname'''
+            # If high security level, deny requests to public (HS should be disabled anyway for Tor, but might not be for I2P)
+            transports = gettransports.get()
+            if public_api.config.get('general.security_level', default=1) > 0:
+                abort(403)
+            if request.host not in transports:
+                # Disallow connection if wrong HTTP hostname, in order to prevent DNS rebinding attacks
+                abort(403)
+            public_api.hitCount += 1 # raise hit count for valid requests
+            try:
+                if 'onionr' in request.headers['User-Agent'].lower():
+                    g.is_onionr_client = True
+                else:
+                    g.is_onionr_client = False
+            except KeyError:
+                g.is_onionr_client = False
+
+        @public_api_security_bp.after_app_request
+        def send_headers(resp):
+            '''Send api, access control headers'''
+            resp = httpheaders.set_default_onionr_http_headers(resp)
+            # Network API version
+            resp.headers['X-API'] = public_api.API_VERSION
+            # Delete some HTTP headers for Onionr user agents
+            NON_NETWORK_HEADERS = ('Content-Security-Policy', 'X-Frame-Options',
+            'X-Content-Type-Options', 'Feature-Policy', 'Clear-Site-Data', 'Referrer-Policy')
+            if g.is_onionr_client:
+                for header in NON_NETWORK_HEADERS: del resp.headers[header]
+            public_api.lastRequest = epoch.get_rounded_epoch(roundS=5)
+            return resp
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/index.html b/docs/html/onionr/index.html new file mode 100644 index 00000000..439ddbcd --- /dev/null +++ b/docs/html/onionr/index.html @@ -0,0 +1,273 @@ + + + + + + +onionr API documentation + + + + + + + + + +
+
+
+

Module onionr

+
+
+

Onionr - Private P2P Communication

+

This file initializes Onionr when ran to be a daemon or with commands

+

Run with 'help' for usage.

+
+Source code +
#!/usr/bin/env python3
+'''
+    Onionr - Private P2P Communication
+
+    This file initializes Onionr when ran to be a daemon or with commands
+
+    Run with 'help' for usage.
+'''
+'''
+    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/>.
+'''
+
+# Set the user's locale for encoding reasons
+import locale 
+locale.setlocale(locale.LC_ALL, '')
+
+# Import standard libraries
+import sys
+
+# 3rd party lib imports
+# Ensure that PySocks is installed
+try:
+    from urllib3.contrib.socks import SOCKSProxyManager
+except ModuleNotFoundError:
+    raise ModuleNotFoundError("You need the PySocks module (for use with socks5 proxy to use Tor)")
+
+# Onionr imports
+from etc import onionrvalues # For different Onionr related constants such as versions
+import onionrsetup as setup
+
+# Ensure we have at least the minimum python version
+if sys.version_info[0] == 2 or sys.version_info[1] < onionrvalues.MIN_PY_VERSION:
+    sys.stderr.write('Error, Onionr requires Python 3.%s+\n' % (onionrvalues.MIN_PY_VERSION,))
+    sys.exit(1)
+
+# Create Onionr data directories, must be done before most imports
+from utils import createdirs
+createdirs.create_dirs()
+
+from onionrcommands import parser
+from onionrplugins import onionrevents as events
+
+setup.setup_config()
+setup.setup_default_plugins()
+
+def onionr_main():
+    parser.register()
+    return
+
+if __name__ == "__main__":
+    onionr_main()
+
+
+
+

Sub-modules

+
+
onionr.apiservers
+
+
+
+
onionr.communicator
+
+

Onionr - Private P2P Communication …

+
+
onionr.communicatorutils
+
+
+
+
onionr.config
+
+

Onionr - Private P2P Communication …

+
+
onionr.coredb
+
+
+
+
onionr.etc
+
+
+
+
onionr.filepaths
+
+
+
+
onionr.httpapi
+
+

Onionr - Private P2P Communication …

+
+
onionr.keymanager
+
+

Onionr - Private P2P Communication …

+
+
onionr.logger
+
+

Onionr - Private P2P Communication …

+
+
onionr.netcontroller
+
+
+
+
onionr.notifier
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrblocks
+
+
+
+
onionr.onionrcommands
+
+
+
+
onionr.onionrcrypto
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrexceptions
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrpeers
+
+
+
+
onionr.onionrplugins
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrproofs
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrservices
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrsetup
+
+
+
+
onionr.onionrstorage
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrusers
+
+
+
+
onionr.onionrutils
+
+
+
+
onionr.serializeddata
+
+

Onionr - Private P2P Communication …

+
+
onionr.utils
+
+
+
+
+
+
+
+
+

Functions

+
+
+def onionr_main() +
+
+
+
+Source code +
def onionr_main():
+    parser.register()
+    return
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/keymanager.html b/docs/html/onionr/keymanager.html new file mode 100644 index 00000000..38368779 --- /dev/null +++ b/docs/html/onionr/keymanager.html @@ -0,0 +1,301 @@ + + + + + + +onionr.keymanager API documentation + + + + + + + + + +
+
+
+

Module onionr.keymanager

+
+
+

Onionr - Private P2P Communication

+

Load, save, and delete the user's public key pairs (does not handle peer keys)

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Load, save, and delete the user's public key pairs (does not handle peer keys)
+'''
+'''
+    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 onionrutils import bytesconverter
+from onionrcrypto import generate
+import os
+import filepaths
+
+class KeyManager:
+    def __init__(self):
+        self.keyFile = filepaths.keys_file
+
+    def addKey(self, pubKey=None, privKey=None):
+        '''Add a new key pair, either specified or none to generate a new pair automatically'''
+        if type(pubKey) is type(None) and type(privKey) is type(None):
+            pubKey, privKey = generate.generate_pub_key()
+        pubKey = bytesconverter.bytes_to_str(pubKey)
+        privKey = bytesconverter.bytes_to_str(privKey)
+        try:
+            if pubKey in self.getPubkeyList():
+                raise ValueError('Pubkey already in list: %s' % (pubKey,))
+        except FileNotFoundError:
+            pass
+
+        with open(self.keyFile, "a") as keyFile:
+            keyFile.write(pubKey + ',' + privKey + '\n')
+        return (pubKey, privKey)
+
+    def removeKey(self, pubKey):
+        '''Remove a key pair by pubkey'''
+        keyList = self.getPubkeyList()
+        keyData = ''
+        try:
+            keyList.remove(pubKey)
+        except ValueError:
+            return False
+        else:
+            keyData = ','.join(keyList)
+            with open(self.keyFile, "w") as keyFile:
+                keyFile.write(keyData)
+
+    def getPubkeyList(self):
+        '''Return a list of the user's keys'''
+        keyList = []
+        try:
+            with open(self.keyFile, "r") as keyFile:
+                keyData = keyFile.read()
+        except FileNotFoundError:
+            keyData = ''
+        keyData = keyData.split('\n')
+        for pair in keyData:
+            if len(pair) > 0: keyList.append(pair.split(',')[0])
+        return keyList
+    
+    def getPrivkey(self, pubKey):
+        privKey = None
+        with open(self.keyFile, "r") as keyFile:
+            keyData = keyFile.read()
+        for pair in keyData.split('\n'):
+            if pubKey in pair or pubKey.replace('=', '') in pair:
+                privKey = pair.split(',')[1]
+        return privKey
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class KeyManager +
+
+
+
+Source code +
class KeyManager:
+    def __init__(self):
+        self.keyFile = filepaths.keys_file
+
+    def addKey(self, pubKey=None, privKey=None):
+        '''Add a new key pair, either specified or none to generate a new pair automatically'''
+        if type(pubKey) is type(None) and type(privKey) is type(None):
+            pubKey, privKey = generate.generate_pub_key()
+        pubKey = bytesconverter.bytes_to_str(pubKey)
+        privKey = bytesconverter.bytes_to_str(privKey)
+        try:
+            if pubKey in self.getPubkeyList():
+                raise ValueError('Pubkey already in list: %s' % (pubKey,))
+        except FileNotFoundError:
+            pass
+
+        with open(self.keyFile, "a") as keyFile:
+            keyFile.write(pubKey + ',' + privKey + '\n')
+        return (pubKey, privKey)
+
+    def removeKey(self, pubKey):
+        '''Remove a key pair by pubkey'''
+        keyList = self.getPubkeyList()
+        keyData = ''
+        try:
+            keyList.remove(pubKey)
+        except ValueError:
+            return False
+        else:
+            keyData = ','.join(keyList)
+            with open(self.keyFile, "w") as keyFile:
+                keyFile.write(keyData)
+
+    def getPubkeyList(self):
+        '''Return a list of the user's keys'''
+        keyList = []
+        try:
+            with open(self.keyFile, "r") as keyFile:
+                keyData = keyFile.read()
+        except FileNotFoundError:
+            keyData = ''
+        keyData = keyData.split('\n')
+        for pair in keyData:
+            if len(pair) > 0: keyList.append(pair.split(',')[0])
+        return keyList
+    
+    def getPrivkey(self, pubKey):
+        privKey = None
+        with open(self.keyFile, "r") as keyFile:
+            keyData = keyFile.read()
+        for pair in keyData.split('\n'):
+            if pubKey in pair or pubKey.replace('=', '') in pair:
+                privKey = pair.split(',')[1]
+        return privKey
+
+

Methods

+
+
+def addKey(self, pubKey=None, privKey=None) +
+
+

Add a new key pair, either specified or none to generate a new pair automatically

+
+Source code +
def addKey(self, pubKey=None, privKey=None):
+    '''Add a new key pair, either specified or none to generate a new pair automatically'''
+    if type(pubKey) is type(None) and type(privKey) is type(None):
+        pubKey, privKey = generate.generate_pub_key()
+    pubKey = bytesconverter.bytes_to_str(pubKey)
+    privKey = bytesconverter.bytes_to_str(privKey)
+    try:
+        if pubKey in self.getPubkeyList():
+            raise ValueError('Pubkey already in list: %s' % (pubKey,))
+    except FileNotFoundError:
+        pass
+
+    with open(self.keyFile, "a") as keyFile:
+        keyFile.write(pubKey + ',' + privKey + '\n')
+    return (pubKey, privKey)
+
+
+
+def getPrivkey(self, pubKey) +
+
+
+
+Source code +
def getPrivkey(self, pubKey):
+    privKey = None
+    with open(self.keyFile, "r") as keyFile:
+        keyData = keyFile.read()
+    for pair in keyData.split('\n'):
+        if pubKey in pair or pubKey.replace('=', '') in pair:
+            privKey = pair.split(',')[1]
+    return privKey
+
+
+
+def getPubkeyList(self) +
+
+

Return a list of the user's keys

+
+Source code +
def getPubkeyList(self):
+    '''Return a list of the user's keys'''
+    keyList = []
+    try:
+        with open(self.keyFile, "r") as keyFile:
+            keyData = keyFile.read()
+    except FileNotFoundError:
+        keyData = ''
+    keyData = keyData.split('\n')
+    for pair in keyData:
+        if len(pair) > 0: keyList.append(pair.split(',')[0])
+    return keyList
+
+
+
+def removeKey(self, pubKey) +
+
+

Remove a key pair by pubkey

+
+Source code +
def removeKey(self, pubKey):
+    '''Remove a key pair by pubkey'''
+    keyList = self.getPubkeyList()
+    keyData = ''
+    try:
+        keyList.remove(pubKey)
+    except ValueError:
+        return False
+    else:
+        keyData = ','.join(keyList)
+        with open(self.keyFile, "w") as keyFile:
+            keyFile.write(keyData)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/logger/colors.html b/docs/html/onionr/logger/colors.html new file mode 100644 index 00000000..04cdf064 --- /dev/null +++ b/docs/html/onionr/logger/colors.html @@ -0,0 +1,249 @@ + + + + + + +onionr.logger.colors API documentation + + + + + + + + + +
+
+
+

Module onionr.logger.colors

+
+
+

Onionr - Private P2P Communication

+

class to access ANSI control codes

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    class to access ANSI control codes
+'''
+'''
+    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/>.
+'''
+import re
+class Colors:
+    '''
+        This class allows you to set the color if ANSI codes are supported
+    '''
+    reset='\033[0m'
+    bold='\033[01m'
+    disable='\033[02m'
+    underline='\033[04m'
+    reverse='\033[07m'
+    strikethrough='\033[09m'
+    invisible='\033[08m'
+    italics='\033[3m'
+    class fg:
+        black='\033[30m'
+        red='\033[31m'
+        green='\033[32m'
+        orange='\033[33m'
+        blue='\033[34m'
+        purple='\033[35m'
+        cyan='\033[36m'
+        lightgrey='\033[37m'
+        darkgrey='\033[90m'
+        lightred='\033[91m'
+        lightgreen='\033[92m'
+        yellow='\033[93m'
+        lightblue='\033[94m'
+        pink='\033[95m'
+        lightcyan='\033[96m'
+    class bg:
+        black='\033[40m'
+        red='\033[41m'
+        green='\033[42m'
+        orange='\033[43m'
+        blue='\033[44m'
+        purple='\033[45m'
+        cyan='\033[46m'
+        lightgrey='\033[47m'
+    @staticmethod
+    def filter(data):
+        return re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]').sub('', str(data))
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Colors +(*args, **kwargs) +
+
+

This class allows you to set the color if ANSI codes are supported

+
+Source code +
class Colors:
+    '''
+        This class allows you to set the color if ANSI codes are supported
+    '''
+    reset='\033[0m'
+    bold='\033[01m'
+    disable='\033[02m'
+    underline='\033[04m'
+    reverse='\033[07m'
+    strikethrough='\033[09m'
+    invisible='\033[08m'
+    italics='\033[3m'
+    class fg:
+        black='\033[30m'
+        red='\033[31m'
+        green='\033[32m'
+        orange='\033[33m'
+        blue='\033[34m'
+        purple='\033[35m'
+        cyan='\033[36m'
+        lightgrey='\033[37m'
+        darkgrey='\033[90m'
+        lightred='\033[91m'
+        lightgreen='\033[92m'
+        yellow='\033[93m'
+        lightblue='\033[94m'
+        pink='\033[95m'
+        lightcyan='\033[96m'
+    class bg:
+        black='\033[40m'
+        red='\033[41m'
+        green='\033[42m'
+        orange='\033[43m'
+        blue='\033[44m'
+        purple='\033[45m'
+        cyan='\033[46m'
+        lightgrey='\033[47m'
+    @staticmethod
+    def filter(data):
+        return re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]').sub('', str(data))
+
+

Class variables

+
+
var bg
+
+
+
+
var bold
+
+
+
+
var disable
+
+
+
+
var fg
+
+
+
+
var invisible
+
+
+
+
var italics
+
+
+
+
var reset
+
+
+
+
var reverse
+
+
+
+
var strikethrough
+
+
+
+
var underline
+
+
+
+
+

Static methods

+
+
+def filter(data) +
+
+
+
+Source code +
@staticmethod
+def filter(data):
+    return re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]').sub('', str(data))
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/logger/confirm.html b/docs/html/onionr/logger/confirm.html new file mode 100644 index 00000000..97122144 --- /dev/null +++ b/docs/html/onionr/logger/confirm.html @@ -0,0 +1,163 @@ + + + + + + +onionr.logger.confirm API documentation + + + + + + + + + +
+
+
+

Module onionr.logger.confirm

+
+
+

Onionr - Private P2P Communication

+

confirm y/n cli prompt

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    confirm y/n cli prompt
+'''
+'''
+    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/>.
+'''
+import sys
+from . import colors, settings
+colors = colors.Colors
+def confirm(default = 'y', message = 'Are you sure %s? '):
+    '''
+        Displays an "Are you sure" message, returns True for Y and False for N
+        message: The confirmation message, use %s for (y/n)
+        default: which to prefer-- y or n
+    '''
+
+    color = colors.fg.green + colors.bold
+
+    default = default.lower()
+    confirm = colors.bold
+    if default.startswith('y'):
+        confirm += '(Y/n)'
+    else:
+        confirm += '(y/N)'
+    confirm += colors.reset + color
+
+    output = colors.reset + str(color) + '... ' + colors.reset + str(message) + colors.reset
+
+    if not get_settings() & settings.USE_ANSI:
+        output = colors.filter(output)
+
+    sys.stdout.write(output.replace('%s', confirm))
+
+    inp = input().lower()
+
+    if 'y' in inp:
+        return True
+    if 'n' in inp:
+        return False
+    else:
+        return default == 'y'
+
+
+
+
+
+
+
+

Functions

+
+
+def confirm(default='y', message='Are you sure %s? ') +
+
+

Displays an "Are you sure" message, returns True for Y and False for N +message: The confirmation message, use %s for (y/n) +default: which to prefer– y or n

+
+Source code +
def confirm(default = 'y', message = 'Are you sure %s? '):
+    '''
+        Displays an "Are you sure" message, returns True for Y and False for N
+        message: The confirmation message, use %s for (y/n)
+        default: which to prefer-- y or n
+    '''
+
+    color = colors.fg.green + colors.bold
+
+    default = default.lower()
+    confirm = colors.bold
+    if default.startswith('y'):
+        confirm += '(Y/n)'
+    else:
+        confirm += '(y/N)'
+    confirm += colors.reset + color
+
+    output = colors.reset + str(color) + '... ' + colors.reset + str(message) + colors.reset
+
+    if not get_settings() & settings.USE_ANSI:
+        output = colors.filter(output)
+
+    sys.stdout.write(output.replace('%s', confirm))
+
+    inp = input().lower()
+
+    if 'y' in inp:
+        return True
+    if 'n' in inp:
+        return False
+    else:
+        return default == 'y'
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/logger/index.html b/docs/html/onionr/logger/index.html new file mode 100644 index 00000000..740cb01e --- /dev/null +++ b/docs/html/onionr/logger/index.html @@ -0,0 +1,264 @@ + + + + + + +onionr.logger API documentation + + + + + + + + + +
+
+
+

Module onionr.logger

+
+
+

Onionr - Private P2P Communication

+

This file handles all operations involving logging

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file handles all operations involving logging
+'''
+'''
+    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/>.
+'''
+
+import sys, traceback
+
+from . import colors, readline, log, raw, confirm, colors, settings
+colors = colors.Colors
+readline = readline.readline
+log = log.log
+raw = raw.raw
+confirm = confirm.confirm
+
+# debug: when there is info that could be useful for debugging purposes only
+def debug(data: str, error = None, timestamp = True, prompt = True, terminal = False, level = settings.LEVEL_DEBUG):
+    if settings.get_level() <= level:
+        log('/', data, timestamp = timestamp, prompt = prompt, terminal = terminal)
+    if not error is None:
+        debug('Error: ' + str(error) + parse_error())
+
+# info: when there is something to notify the user of, such as the success of a process
+def info(data: str, timestamp = False, prompt = True, terminal = False, level = settings.LEVEL_INFO):
+    if settings.get_level() <= level:
+        log('+', data, colors.fg.green, timestamp = timestamp, prompt = prompt, terminal = terminal)
+
+# warn: when there is a potential for something bad to happen
+def warn(data: str, error = None, timestamp = True, prompt = True, terminal = False, level = settings.LEVEL_WARN):
+    if not error is None:
+        debug('Error: ' + str(error) + parse_error())
+    if settings.get_level() <= level:
+        log('!', data, colors.fg.orange, timestamp = timestamp, prompt = prompt, terminal = terminal)
+
+# error: when only one function, module, or process of the program encountered a problem and must stop
+def error(data: str, error = None, timestamp = True, prompt = True, terminal = False, level = settings.LEVEL_ERROR):
+    if settings.get_level() <= level:
+        log('-', data, colors.fg.red, timestamp = timestamp, fd = sys.stderr, prompt = prompt, terminal = terminal)
+    if not error is None:
+        debug('Error: ' + str(error) + parse_error())
+
+# fatal: when the something so bad has happened that the program must stop
+def fatal(data: str, error = None, timestamp=True, prompt = True, terminal = False, level = settings.LEVEL_FATAL):
+    if not error is None:
+        debug('Error: ' + str(error) + parse_error(), terminal = terminal)
+    if settings.get_level() <= level:
+        log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp = timestamp, fd = sys.stderr, prompt = prompt, terminal = terminal)
+
+# returns a formatted error message
+def parse_error():
+    details = traceback.extract_tb(sys.exc_info()[2])
+    output = ''
+
+    for line in details:
+        output += '\n    ... module %s in  %s:%i' % (line[2], line[0], line[1])
+
+    return output
+
+
+
+

Sub-modules

+
+
onionr.logger.colors
+
+

Onionr - Private P2P Communication …

+
+
onionr.logger.confirm
+
+

Onionr - Private P2P Communication …

+
+
onionr.logger.log
+
+

Onionr - Private P2P Communication …

+
+
onionr.logger.raw
+
+

Onionr - Private P2P Communication …

+
+
onionr.logger.readline
+
+

Onionr - Private P2P Communication …

+
+
onionr.logger.settings
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+

Functions

+
+
+def debug(data, error=None, timestamp=True, prompt=True, terminal=False, level=1) +
+
+
+
+Source code +
def debug(data: str, error = None, timestamp = True, prompt = True, terminal = False, level = settings.LEVEL_DEBUG):
+    if settings.get_level() <= level:
+        log('/', data, timestamp = timestamp, prompt = prompt, terminal = terminal)
+    if not error is None:
+        debug('Error: ' + str(error) + parse_error())
+
+
+
+def error(data, error=None, timestamp=True, prompt=True, terminal=False, level=4) +
+
+
+
+Source code +
def error(data: str, error = None, timestamp = True, prompt = True, terminal = False, level = settings.LEVEL_ERROR):
+    if settings.get_level() <= level:
+        log('-', data, colors.fg.red, timestamp = timestamp, fd = sys.stderr, prompt = prompt, terminal = terminal)
+    if not error is None:
+        debug('Error: ' + str(error) + parse_error())
+
+
+
+def fatal(data, error=None, timestamp=True, prompt=True, terminal=False, level=5) +
+
+
+
+Source code +
def fatal(data: str, error = None, timestamp=True, prompt = True, terminal = False, level = settings.LEVEL_FATAL):
+    if not error is None:
+        debug('Error: ' + str(error) + parse_error(), terminal = terminal)
+    if settings.get_level() <= level:
+        log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp = timestamp, fd = sys.stderr, prompt = prompt, terminal = terminal)
+
+
+
+def info(data, timestamp=False, prompt=True, terminal=False, level=2) +
+
+
+
+Source code +
def info(data: str, timestamp = False, prompt = True, terminal = False, level = settings.LEVEL_INFO):
+    if settings.get_level() <= level:
+        log('+', data, colors.fg.green, timestamp = timestamp, prompt = prompt, terminal = terminal)
+
+
+
+def parse_error() +
+
+
+
+Source code +
def parse_error():
+    details = traceback.extract_tb(sys.exc_info()[2])
+    output = ''
+
+    for line in details:
+        output += '\n    ... module %s in  %s:%i' % (line[2], line[0], line[1])
+
+    return output
+
+
+
+def warn(data, error=None, timestamp=True, prompt=True, terminal=False, level=3) +
+
+
+
+Source code +
def warn(data: str, error = None, timestamp = True, prompt = True, terminal = False, level = settings.LEVEL_WARN):
+    if not error is None:
+        debug('Error: ' + str(error) + parse_error())
+    if settings.get_level() <= level:
+        log('!', data, colors.fg.orange, timestamp = timestamp, prompt = prompt, terminal = terminal)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/logger/log.html b/docs/html/onionr/logger/log.html new file mode 100644 index 00000000..47741757 --- /dev/null +++ b/docs/html/onionr/logger/log.html @@ -0,0 +1,134 @@ + + + + + + +onionr.logger.log API documentation + + + + + + + + + +
+
+
+

Module onionr.logger.log

+
+
+

Onionr - Private P2P Communication

+

god log function

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    god log function
+'''
+'''
+    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/>.
+'''
+import sys, time
+from . import colors, raw, settings
+colors = colors.Colors
+def log(prefix, data, color = '', timestamp=True, fd = sys.stdout, prompt = True, terminal = False):
+    '''
+        Logs the data
+        prefix : The prefix to the output
+        data   : The actual data to output
+        color  : The color to output before the data
+    '''
+    curTime = ''
+    if timestamp:
+        curTime = time.strftime("%m-%d %H:%M:%S") + ' '
+
+    output = colors.reset + str(color) + ('[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' if prompt is True else '') + curTime + str(data) + colors.reset
+    if not settings.get_settings() & settings.USE_ANSI:
+        output = colors.filter(output)
+
+    raw.raw(output, fd = fd, terminal = terminal)
+
+
+
+
+
+
+
+

Functions

+
+
+def log(prefix, data, color='', timestamp=True, fd=<_io.TextIOWrapper name='' mode='w' encoding='UTF-8'>, prompt=True, terminal=False) +
+
+

Logs the data +prefix : The prefix to the output +data +: The actual data to output +color +: The color to output before the data

+
+Source code +
def log(prefix, data, color = '', timestamp=True, fd = sys.stdout, prompt = True, terminal = False):
+    '''
+        Logs the data
+        prefix : The prefix to the output
+        data   : The actual data to output
+        color  : The color to output before the data
+    '''
+    curTime = ''
+    if timestamp:
+        curTime = time.strftime("%m-%d %H:%M:%S") + ' '
+
+    output = colors.reset + str(color) + ('[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' if prompt is True else '') + curTime + str(data) + colors.reset
+    if not settings.get_settings() & settings.USE_ANSI:
+        output = colors.filter(output)
+
+    raw.raw(output, fd = fd, terminal = terminal)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/logger/raw.html b/docs/html/onionr/logger/raw.html new file mode 100644 index 00000000..afab8177 --- /dev/null +++ b/docs/html/onionr/logger/raw.html @@ -0,0 +1,145 @@ + + + + + + +onionr.logger.raw API documentation + + + + + + + + + +
+
+
+

Module onionr.logger.raw

+
+
+

Onionr - Private P2P Communication

+

Output raw data to file or terminal

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Output raw data to file or terminal
+'''
+'''
+    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/>.
+'''
+import sys, os
+from . import settings, colors
+colors = colors.Colors
+def raw(data, fd = sys.stdout, terminal = False):
+    '''
+        Outputs raw data to console without formatting
+    '''
+
+    if terminal and (settings.get_settings() & settings.OUTPUT_TO_CONSOLE):
+        try:
+            ts = fd.write('%s\n' % data)
+        except OSError:
+            pass
+    if settings.get_settings() & settings.OUTPUT_TO_FILE:
+        fdata = ''
+        try:
+            with open(settings._outputfile, 'r') as file:
+                fdata = file.read()
+        except FileNotFoundError:
+            pass
+        fdata = fdata + '\n' + data
+        fdata = fdata.split('\n')
+        if len(fdata) >= settings.MAX_LOG_FILE_LINES:
+            fdata.pop(0)
+        fdata = '\n'.join(fdata)
+        with open(settings._outputfile, 'w') as file:
+            file.write(fdata)
+
+
+
+
+
+
+
+

Functions

+
+
+def raw(data, fd=<_io.TextIOWrapper name='' mode='w' encoding='UTF-8'>, terminal=False) +
+
+

Outputs raw data to console without formatting

+
+Source code +
def raw(data, fd = sys.stdout, terminal = False):
+    '''
+        Outputs raw data to console without formatting
+    '''
+
+    if terminal and (settings.get_settings() & settings.OUTPUT_TO_CONSOLE):
+        try:
+            ts = fd.write('%s\n' % data)
+        except OSError:
+            pass
+    if settings.get_settings() & settings.OUTPUT_TO_FILE:
+        fdata = ''
+        try:
+            with open(settings._outputfile, 'r') as file:
+                fdata = file.read()
+        except FileNotFoundError:
+            pass
+        fdata = fdata + '\n' + data
+        fdata = fdata.split('\n')
+        if len(fdata) >= settings.MAX_LOG_FILE_LINES:
+            fdata.pop(0)
+        fdata = '\n'.join(fdata)
+        with open(settings._outputfile, 'w') as file:
+            file.write(fdata)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/logger/readline.html b/docs/html/onionr/logger/readline.html new file mode 100644 index 00000000..282fa247 --- /dev/null +++ b/docs/html/onionr/logger/readline.html @@ -0,0 +1,128 @@ + + + + + + +onionr.logger.readline API documentation + + + + + + + + + +
+
+
+

Module onionr.logger.readline

+
+
+

Onionr - Private P2P Communication

+

get a line of input from stdin

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    get a line of input from stdin
+'''
+'''
+    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/>.
+'''
+import sys
+from . import colors, settings
+colors = colors.Colors
+def readline(message = ''):
+    '''
+        Takes in input from the console, not stored in logs
+        message: The message to display before taking input
+    '''
+
+    color = colors.fg.green + colors.bold
+    output = colors.reset + str(color) + '... ' + colors.reset + str(message) + colors.reset
+
+    if not settings.get_settings() & settings.USE_ANSI:
+        output = colors.filter(output)
+
+    sys.stdout.write(output)
+
+    return input()
+
+
+
+
+
+
+
+

Functions

+
+
+def readline(message='') +
+
+

Takes in input from the console, not stored in logs +message: The message to display before taking input

+
+Source code +
def readline(message = ''):
+    '''
+        Takes in input from the console, not stored in logs
+        message: The message to display before taking input
+    '''
+
+    color = colors.fg.green + colors.bold
+    output = colors.reset + str(color) + '... ' + colors.reset + str(message) + colors.reset
+
+    if not settings.get_settings() & settings.USE_ANSI:
+        output = colors.filter(output)
+
+    sys.stdout.write(output)
+
+    return input()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/logger/settings.html b/docs/html/onionr/logger/settings.html new file mode 100644 index 00000000..7c70f340 --- /dev/null +++ b/docs/html/onionr/logger/settings.html @@ -0,0 +1,251 @@ + + + + + + +onionr.logger.settings API documentation + + + + + + + + + +
+
+
+

Module onionr.logger.settings

+
+
+

Onionr - Private P2P Communication

+

logger settings

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    logger 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/>.
+'''
+import os
+from utils import identifyhome
+
+data_home = os.environ.get('ONIONR_LOG_DIR', identifyhome.identify_home())
+# Use the bitwise operators to merge these settings
+USE_ANSI = 0b100
+if os.name == 'nt':
+    USE_ANSI = 0b000
+OUTPUT_TO_CONSOLE = 0b010
+OUTPUT_TO_FILE = 0b001
+
+LEVEL_DEBUG = 1
+LEVEL_INFO = 2
+LEVEL_WARN = 3
+LEVEL_ERROR = 4
+LEVEL_FATAL = 5
+LEVEL_IMPORTANT = 6
+
+MAX_LOG_FILE_LINES = 10000
+
+_type = OUTPUT_TO_CONSOLE | USE_ANSI # the default settings for logging
+_level = LEVEL_DEBUG # the lowest level to log
+_outputfile = '%s/onionr.log' % (data_home,) # the file to log to
+
+def set_settings(type):
+    '''
+        Set the settings for the logger using bitwise operators
+    '''
+
+    global _type
+    _type = type
+
+def get_settings():
+    '''
+        Get settings from the logger
+    '''
+
+    return _type
+
+def set_level(level):
+    '''
+        Set the lowest log level to output
+    '''
+
+    global _level
+    _level = level
+
+def get_level()->int:
+    '''
+        Get the lowest log level currently being outputted
+    '''
+
+    return _level
+
+def set_file(outputfile):
+    '''
+        Set the file to output to, if enabled
+    '''
+
+    global _outputfile
+    _outputfile = outputfile
+
+def get_file():
+    '''
+        Get the file to output to
+    '''
+
+    return _outputfile
+
+
+
+
+
+
+
+

Functions

+
+
+def get_file() +
+
+

Get the file to output to

+
+Source code +
def get_file():
+    '''
+        Get the file to output to
+    '''
+
+    return _outputfile
+
+
+
+def get_level() +
+
+

Get the lowest log level currently being outputted

+
+Source code +
def get_level()->int:
+    '''
+        Get the lowest log level currently being outputted
+    '''
+
+    return _level
+
+
+
+def get_settings() +
+
+

Get settings from the logger

+
+Source code +
def get_settings():
+    '''
+        Get settings from the logger
+    '''
+
+    return _type
+
+
+
+def set_file(outputfile) +
+
+

Set the file to output to, if enabled

+
+Source code +
def set_file(outputfile):
+    '''
+        Set the file to output to, if enabled
+    '''
+
+    global _outputfile
+    _outputfile = outputfile
+
+
+
+def set_level(level) +
+
+

Set the lowest log level to output

+
+Source code +
def set_level(level):
+    '''
+        Set the lowest log level to output
+    '''
+
+    global _level
+    _level = level
+
+
+
+def set_settings(type) +
+
+

Set the settings for the logger using bitwise operators

+
+Source code +
def set_settings(type):
+    '''
+        Set the settings for the logger using bitwise operators
+    '''
+
+    global _type
+    _type = type
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/netcontroller/getopenport.html b/docs/html/onionr/netcontroller/getopenport.html new file mode 100644 index 00000000..112bd039 --- /dev/null +++ b/docs/html/onionr/netcontroller/getopenport.html @@ -0,0 +1,113 @@ + + + + + + +onionr.netcontroller.getopenport API documentation + + + + + + + + + +
+
+
+

Module onionr.netcontroller.getopenport

+
+
+

Onionr - Private P2P Communication

+

get an open port

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    get an open port
+'''
+'''
+    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/>.
+'''
+import socket
+def get_open_port():
+    # taken from (but modified) https://stackoverflow.com/a/2838309 by https://stackoverflow.com/users/133374/albert ccy-by-sa-3 https://creativecommons.org/licenses/by-sa/3.0/
+    # changes from source: import moved to top of file, bind specifically to localhost
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.bind(("127.0.0.1",0))
+    s.listen(1)
+    port = s.getsockname()[1]
+    s.close()
+    return port
+
+
+
+
+
+
+
+

Functions

+
+
+def get_open_port() +
+
+
+
+Source code +
def get_open_port():
+    # taken from (but modified) https://stackoverflow.com/a/2838309 by https://stackoverflow.com/users/133374/albert ccy-by-sa-3 https://creativecommons.org/licenses/by-sa/3.0/
+    # changes from source: import moved to top of file, bind specifically to localhost
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.bind(("127.0.0.1",0))
+    s.listen(1)
+    port = s.getsockname()[1]
+    s.close()
+    return port
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/netcontroller/index.html b/docs/html/onionr/netcontroller/index.html new file mode 100644 index 00000000..38477d89 --- /dev/null +++ b/docs/html/onionr/netcontroller/index.html @@ -0,0 +1,92 @@ + + + + + + +onionr.netcontroller API documentation + + + + + + + + + +
+
+
+

Module onionr.netcontroller

+
+
+
+Source code +
from . import torbinary, getopenport, netcontrol
+tor_binary = torbinary.tor_binary
+get_open_port = getopenport.get_open_port
+NetController = netcontrol.NetController
+
+
+
+

Sub-modules

+
+
onionr.netcontroller.getopenport
+
+

Onionr - Private P2P Communication …

+
+
onionr.netcontroller.netcontrol
+
+

Onionr - Private P2P Communication …

+
+
onionr.netcontroller.rebuildtor
+
+
+
+
onionr.netcontroller.torbinary
+
+

Onionr - Private P2P Communication …

+
+
onionr.netcontroller.torcontroller
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/netcontroller/netcontrol.html b/docs/html/onionr/netcontroller/netcontrol.html new file mode 100644 index 00000000..6b1e9523 --- /dev/null +++ b/docs/html/onionr/netcontroller/netcontrol.html @@ -0,0 +1,682 @@ + + + + + + +onionr.netcontroller.netcontrol API documentation + + + + + + + + + +
+
+
+

Module onionr.netcontroller.netcontrol

+
+
+

Onionr - Private P2P Communication

+

Netcontroller library, used to control/work with Tor/I2P and send requests through them

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Netcontroller library, used to control/work with Tor/I2P and send requests through them
+'''
+'''
+    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/>.
+'''
+import os, sys, base64, subprocess, signal, time
+import platform # For windows sigkill workaround
+import config, logger
+from . import getopenport
+from utils import identifyhome
+config.reload()
+TOR_KILL_WAIT = 3
+
+def add_bridges(torrc: str)->str:
+    """Configure tor to use a bridge using Onionr config keys"""
+    if config.get('tor.use_bridge', False) == True:
+        bridge = config.get('tor.bridge_ip', None)
+        if not bridge is None:
+            fingerprint = config.get('tor.bridge_fingerprint', '') # allow blank fingerprint purposefully
+            torrc += '\nUseBridges 1\nBridge %s %s\n' % (bridge, fingerprint)
+        else:
+            logger.warn('bridge was enabled but not specified in config')
+
+    return torrc
+
+class NetController:
+    '''
+        This class handles hidden service setup on Tor and I2P
+    '''
+
+    def __init__(self, hsPort, apiServerIP='127.0.0.1'):
+        # set data dir
+        self.dataDir = identifyhome.identify_home()
+
+        self.torConfigLocation = self.dataDir + 'torrc'
+        self.readyState = False
+        self.socksPort = getopenport.get_open_port()
+        self.hsPort = hsPort
+        self._torInstnace = ''
+        self.myID = ''
+        self.apiServerIP = apiServerIP
+
+        if os.path.exists('./tor'):
+            self.torBinary = './tor'
+        elif os.path.exists('/usr/bin/tor'):
+            self.torBinary = '/usr/bin/tor'
+        else:
+            self.torBinary = 'tor'
+
+    def generateTorrc(self):
+        '''
+            Generate a torrc file for our tor instance
+        '''
+        hsVer = '# v2 onions'
+        if config.get('tor.v3onions'):
+            hsVer = 'HiddenServiceVersion 3'
+
+        if os.path.exists(self.torConfigLocation):
+            os.remove(self.torConfigLocation)
+
+        # Set the Tor control password. Meant to make it harder to manipulate our Tor instance
+        plaintext = base64.b64encode(os.urandom(50)).decode()
+        config.set('tor.controlpassword', plaintext, savefile=True)
+        config.set('tor.socksport', self.socksPort, savefile=True)
+
+        controlPort = getopenport.get_open_port()
+
+        config.set('tor.controlPort', controlPort, savefile=True)
+
+        hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        for line in iter(hashedPassword.stdout.readline, b''):
+            password = line.decode()
+            if 'warn' not in password:
+                break
+
+        torrcData = '''SocksPort ''' + str(self.socksPort) + ''' OnionTrafficOnly
+DataDirectory ''' + self.dataDir + '''tordata/
+CookieAuthentication 1
+KeepalivePeriod 40
+CircuitsAvailableTimeout 86400
+ControlPort ''' + str(controlPort) + '''
+HashedControlPassword ''' + str(password) + '''
+        '''
+        if config.get('general.security_level', 1) == 0:
+            torrcData += '''\nHiddenServiceDir ''' + self.dataDir + '''hs/
+\n''' + hsVer + '''\n
+HiddenServiceNumIntroductionPoints 6
+HiddenServiceMaxStreams 100
+HiddenServiceMaxStreamsCloseCircuit 1
+HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
+
+        torrcData = add_bridges(torrcData)
+
+        torrc = open(self.torConfigLocation, 'w')
+        torrc.write(torrcData)
+        torrc.close()
+        return
+
+    def startTor(self, gen_torrc=True):
+        '''
+            Start Tor with onion service on port 80 & socks proxy on random port
+        '''
+        if gen_torrc:
+            self.generateTorrc()
+
+        if os.path.exists('./tor'):
+            self.torBinary = './tor'
+        elif os.path.exists('/usr/bin/tor'):
+            self.torBinary = '/usr/bin/tor'
+        else:
+            self.torBinary = 'tor'
+
+        try:
+            tor = subprocess.Popen([self.torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        except FileNotFoundError:
+            logger.fatal("Tor was not found in your path or the Onionr directory. Please install Tor and try again.", terminal=True)
+            sys.exit(1)
+        else:
+            # Test Tor Version
+            torVersion = subprocess.Popen([self.torBinary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            for line in iter(torVersion.stdout.readline, b''):
+                if 'Tor 0.2.' in line.decode():
+                    logger.fatal('Tor 0.3+ required', terminal=True)
+                    sys.exit(1)
+                    break
+            torVersion.kill()
+
+        # wait for tor to get to 100% bootstrap
+        try:
+            for line in iter(tor.stdout.readline, b''):
+                if 'bootstrapped 100' in line.decode().lower():
+                    logger.info(line.decode())
+                    break
+                elif 'opening socks listener' in line.decode().lower():
+                    logger.debug(line.decode().replace('\n', ''))
+            else:
+                logger.fatal('Failed to start Tor. Maybe a stray instance of Tor used by Onionr is still running? This can also be a result of file permissions being too open', terminal=True)
+                return False
+        except KeyboardInterrupt:
+            logger.fatal('Got keyboard interrupt. Onionr will exit soon.', timestamp = False, terminal=True)
+            return False
+
+        logger.info('Finished starting Tor.', terminal=True)
+
+        self.readyState = True
+
+        try:
+            myID = open(self.dataDir + 'hs/hostname', 'r')
+            self.myID = myID.read().replace('\n', '')
+            myID.close()
+        except FileNotFoundError:
+            self.myID = ""
+
+        torPidFile = open(self.dataDir + 'torPid.txt', 'w')
+        torPidFile.write(str(tor.pid))
+        torPidFile.close()
+
+        return True
+
+    def killTor(self):
+        '''
+            Properly kill tor based on pid saved to file
+        '''
+
+        try:
+            pid = open(self.dataDir + 'torPid.txt', 'r')
+            pidN = pid.read()
+            pid.close()
+        except FileNotFoundError:
+            return
+
+        try:
+            int(pidN)
+        except:
+            return
+
+        try:
+            try:
+                os.kill(int(pidN), signal.SIGTERM)
+            except PermissionError:
+                # seems to happen on win 10
+                pass
+            os.remove(self.dataDir + 'torPid.txt')
+        except ProcessLookupError:
+            pass
+        except FileNotFoundError:
+            pass
+        
+        try:
+            time.sleep(TOR_KILL_WAIT)
+        except KeyboardInterrupt:
+            pass
+
+        if 'windows' == platform.system().lower():
+            os.system('taskkill /PID %s /F' % (pidN,))
+            time.sleep(0.5)
+            return
+        try:
+            os.kill(int(pidN), signal.SIGKILL)
+        except (ProcessLookupError, PermissionError) as e:
+            pass
+
+
+
+
+
+
+
+

Functions

+
+
+def add_bridges(torrc) +
+
+

Configure tor to use a bridge using Onionr config keys

+
+Source code +
def add_bridges(torrc: str)->str:
+    """Configure tor to use a bridge using Onionr config keys"""
+    if config.get('tor.use_bridge', False) == True:
+        bridge = config.get('tor.bridge_ip', None)
+        if not bridge is None:
+            fingerprint = config.get('tor.bridge_fingerprint', '') # allow blank fingerprint purposefully
+            torrc += '\nUseBridges 1\nBridge %s %s\n' % (bridge, fingerprint)
+        else:
+            logger.warn('bridge was enabled but not specified in config')
+
+    return torrc
+
+
+
+
+
+

Classes

+
+
+class NetController +(hsPort, apiServerIP='127.0.0.1') +
+
+

This class handles hidden service setup on Tor and I2P

+
+Source code +
class NetController:
+    '''
+        This class handles hidden service setup on Tor and I2P
+    '''
+
+    def __init__(self, hsPort, apiServerIP='127.0.0.1'):
+        # set data dir
+        self.dataDir = identifyhome.identify_home()
+
+        self.torConfigLocation = self.dataDir + 'torrc'
+        self.readyState = False
+        self.socksPort = getopenport.get_open_port()
+        self.hsPort = hsPort
+        self._torInstnace = ''
+        self.myID = ''
+        self.apiServerIP = apiServerIP
+
+        if os.path.exists('./tor'):
+            self.torBinary = './tor'
+        elif os.path.exists('/usr/bin/tor'):
+            self.torBinary = '/usr/bin/tor'
+        else:
+            self.torBinary = 'tor'
+
+    def generateTorrc(self):
+        '''
+            Generate a torrc file for our tor instance
+        '''
+        hsVer = '# v2 onions'
+        if config.get('tor.v3onions'):
+            hsVer = 'HiddenServiceVersion 3'
+
+        if os.path.exists(self.torConfigLocation):
+            os.remove(self.torConfigLocation)
+
+        # Set the Tor control password. Meant to make it harder to manipulate our Tor instance
+        plaintext = base64.b64encode(os.urandom(50)).decode()
+        config.set('tor.controlpassword', plaintext, savefile=True)
+        config.set('tor.socksport', self.socksPort, savefile=True)
+
+        controlPort = getopenport.get_open_port()
+
+        config.set('tor.controlPort', controlPort, savefile=True)
+
+        hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        for line in iter(hashedPassword.stdout.readline, b''):
+            password = line.decode()
+            if 'warn' not in password:
+                break
+
+        torrcData = '''SocksPort ''' + str(self.socksPort) + ''' OnionTrafficOnly
+DataDirectory ''' + self.dataDir + '''tordata/
+CookieAuthentication 1
+KeepalivePeriod 40
+CircuitsAvailableTimeout 86400
+ControlPort ''' + str(controlPort) + '''
+HashedControlPassword ''' + str(password) + '''
+        '''
+        if config.get('general.security_level', 1) == 0:
+            torrcData += '''\nHiddenServiceDir ''' + self.dataDir + '''hs/
+\n''' + hsVer + '''\n
+HiddenServiceNumIntroductionPoints 6
+HiddenServiceMaxStreams 100
+HiddenServiceMaxStreamsCloseCircuit 1
+HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
+
+        torrcData = add_bridges(torrcData)
+
+        torrc = open(self.torConfigLocation, 'w')
+        torrc.write(torrcData)
+        torrc.close()
+        return
+
+    def startTor(self, gen_torrc=True):
+        '''
+            Start Tor with onion service on port 80 & socks proxy on random port
+        '''
+        if gen_torrc:
+            self.generateTorrc()
+
+        if os.path.exists('./tor'):
+            self.torBinary = './tor'
+        elif os.path.exists('/usr/bin/tor'):
+            self.torBinary = '/usr/bin/tor'
+        else:
+            self.torBinary = 'tor'
+
+        try:
+            tor = subprocess.Popen([self.torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        except FileNotFoundError:
+            logger.fatal("Tor was not found in your path or the Onionr directory. Please install Tor and try again.", terminal=True)
+            sys.exit(1)
+        else:
+            # Test Tor Version
+            torVersion = subprocess.Popen([self.torBinary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            for line in iter(torVersion.stdout.readline, b''):
+                if 'Tor 0.2.' in line.decode():
+                    logger.fatal('Tor 0.3+ required', terminal=True)
+                    sys.exit(1)
+                    break
+            torVersion.kill()
+
+        # wait for tor to get to 100% bootstrap
+        try:
+            for line in iter(tor.stdout.readline, b''):
+                if 'bootstrapped 100' in line.decode().lower():
+                    logger.info(line.decode())
+                    break
+                elif 'opening socks listener' in line.decode().lower():
+                    logger.debug(line.decode().replace('\n', ''))
+            else:
+                logger.fatal('Failed to start Tor. Maybe a stray instance of Tor used by Onionr is still running? This can also be a result of file permissions being too open', terminal=True)
+                return False
+        except KeyboardInterrupt:
+            logger.fatal('Got keyboard interrupt. Onionr will exit soon.', timestamp = False, terminal=True)
+            return False
+
+        logger.info('Finished starting Tor.', terminal=True)
+
+        self.readyState = True
+
+        try:
+            myID = open(self.dataDir + 'hs/hostname', 'r')
+            self.myID = myID.read().replace('\n', '')
+            myID.close()
+        except FileNotFoundError:
+            self.myID = ""
+
+        torPidFile = open(self.dataDir + 'torPid.txt', 'w')
+        torPidFile.write(str(tor.pid))
+        torPidFile.close()
+
+        return True
+
+    def killTor(self):
+        '''
+            Properly kill tor based on pid saved to file
+        '''
+
+        try:
+            pid = open(self.dataDir + 'torPid.txt', 'r')
+            pidN = pid.read()
+            pid.close()
+        except FileNotFoundError:
+            return
+
+        try:
+            int(pidN)
+        except:
+            return
+
+        try:
+            try:
+                os.kill(int(pidN), signal.SIGTERM)
+            except PermissionError:
+                # seems to happen on win 10
+                pass
+            os.remove(self.dataDir + 'torPid.txt')
+        except ProcessLookupError:
+            pass
+        except FileNotFoundError:
+            pass
+        
+        try:
+            time.sleep(TOR_KILL_WAIT)
+        except KeyboardInterrupt:
+            pass
+
+        if 'windows' == platform.system().lower():
+            os.system('taskkill /PID %s /F' % (pidN,))
+            time.sleep(0.5)
+            return
+        try:
+            os.kill(int(pidN), signal.SIGKILL)
+        except (ProcessLookupError, PermissionError) as e:
+            pass
+
+

Methods

+
+
+def generateTorrc(self) +
+
+

Generate a torrc file for our tor instance

+
+Source code +
    def generateTorrc(self):
+        '''
+            Generate a torrc file for our tor instance
+        '''
+        hsVer = '# v2 onions'
+        if config.get('tor.v3onions'):
+            hsVer = 'HiddenServiceVersion 3'
+
+        if os.path.exists(self.torConfigLocation):
+            os.remove(self.torConfigLocation)
+
+        # Set the Tor control password. Meant to make it harder to manipulate our Tor instance
+        plaintext = base64.b64encode(os.urandom(50)).decode()
+        config.set('tor.controlpassword', plaintext, savefile=True)
+        config.set('tor.socksport', self.socksPort, savefile=True)
+
+        controlPort = getopenport.get_open_port()
+
+        config.set('tor.controlPort', controlPort, savefile=True)
+
+        hashedPassword = subprocess.Popen([self.torBinary, '--hash-password', plaintext], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        for line in iter(hashedPassword.stdout.readline, b''):
+            password = line.decode()
+            if 'warn' not in password:
+                break
+
+        torrcData = '''SocksPort ''' + str(self.socksPort) + ''' OnionTrafficOnly
+DataDirectory ''' + self.dataDir + '''tordata/
+CookieAuthentication 1
+KeepalivePeriod 40
+CircuitsAvailableTimeout 86400
+ControlPort ''' + str(controlPort) + '''
+HashedControlPassword ''' + str(password) + '''
+        '''
+        if config.get('general.security_level', 1) == 0:
+            torrcData += '''\nHiddenServiceDir ''' + self.dataDir + '''hs/
+\n''' + hsVer + '''\n
+HiddenServiceNumIntroductionPoints 6
+HiddenServiceMaxStreams 100
+HiddenServiceMaxStreamsCloseCircuit 1
+HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort)
+
+        torrcData = add_bridges(torrcData)
+
+        torrc = open(self.torConfigLocation, 'w')
+        torrc.write(torrcData)
+        torrc.close()
+        return
+
+
+
+def killTor(self) +
+
+

Properly kill tor based on pid saved to file

+
+Source code +
def killTor(self):
+    '''
+        Properly kill tor based on pid saved to file
+    '''
+
+    try:
+        pid = open(self.dataDir + 'torPid.txt', 'r')
+        pidN = pid.read()
+        pid.close()
+    except FileNotFoundError:
+        return
+
+    try:
+        int(pidN)
+    except:
+        return
+
+    try:
+        try:
+            os.kill(int(pidN), signal.SIGTERM)
+        except PermissionError:
+            # seems to happen on win 10
+            pass
+        os.remove(self.dataDir + 'torPid.txt')
+    except ProcessLookupError:
+        pass
+    except FileNotFoundError:
+        pass
+    
+    try:
+        time.sleep(TOR_KILL_WAIT)
+    except KeyboardInterrupt:
+        pass
+
+    if 'windows' == platform.system().lower():
+        os.system('taskkill /PID %s /F' % (pidN,))
+        time.sleep(0.5)
+        return
+    try:
+        os.kill(int(pidN), signal.SIGKILL)
+    except (ProcessLookupError, PermissionError) as e:
+        pass
+
+
+
+def startTor(self, gen_torrc=True) +
+
+

Start Tor with onion service on port 80 & socks proxy on random port

+
+Source code +
def startTor(self, gen_torrc=True):
+    '''
+        Start Tor with onion service on port 80 & socks proxy on random port
+    '''
+    if gen_torrc:
+        self.generateTorrc()
+
+    if os.path.exists('./tor'):
+        self.torBinary = './tor'
+    elif os.path.exists('/usr/bin/tor'):
+        self.torBinary = '/usr/bin/tor'
+    else:
+        self.torBinary = 'tor'
+
+    try:
+        tor = subprocess.Popen([self.torBinary, '-f', self.torConfigLocation], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    except FileNotFoundError:
+        logger.fatal("Tor was not found in your path or the Onionr directory. Please install Tor and try again.", terminal=True)
+        sys.exit(1)
+    else:
+        # Test Tor Version
+        torVersion = subprocess.Popen([self.torBinary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        for line in iter(torVersion.stdout.readline, b''):
+            if 'Tor 0.2.' in line.decode():
+                logger.fatal('Tor 0.3+ required', terminal=True)
+                sys.exit(1)
+                break
+        torVersion.kill()
+
+    # wait for tor to get to 100% bootstrap
+    try:
+        for line in iter(tor.stdout.readline, b''):
+            if 'bootstrapped 100' in line.decode().lower():
+                logger.info(line.decode())
+                break
+            elif 'opening socks listener' in line.decode().lower():
+                logger.debug(line.decode().replace('\n', ''))
+        else:
+            logger.fatal('Failed to start Tor. Maybe a stray instance of Tor used by Onionr is still running? This can also be a result of file permissions being too open', terminal=True)
+            return False
+    except KeyboardInterrupt:
+        logger.fatal('Got keyboard interrupt. Onionr will exit soon.', timestamp = False, terminal=True)
+        return False
+
+    logger.info('Finished starting Tor.', terminal=True)
+
+    self.readyState = True
+
+    try:
+        myID = open(self.dataDir + 'hs/hostname', 'r')
+        self.myID = myID.read().replace('\n', '')
+        myID.close()
+    except FileNotFoundError:
+        self.myID = ""
+
+    torPidFile = open(self.dataDir + 'torPid.txt', 'w')
+    torPidFile.write(str(tor.pid))
+    torPidFile.close()
+
+    return True
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/netcontroller/rebuildtor.html b/docs/html/onionr/netcontroller/rebuildtor.html new file mode 100644 index 00000000..f3299927 --- /dev/null +++ b/docs/html/onionr/netcontroller/rebuildtor.html @@ -0,0 +1,80 @@ + + + + + + +onionr.netcontroller.rebuildtor API documentation + + + + + + + + + +
+
+
+

Module onionr.netcontroller.rebuildtor

+
+
+
+Source code +
import time
+from coredb import daemonqueue
+
+def rebuild():
+    daemonqueue.daemon_queue_add('restartTor')
+
+
+
+
+
+
+
+

Functions

+
+
+def rebuild() +
+
+
+
+Source code +
def rebuild():
+    daemonqueue.daemon_queue_add('restartTor')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/netcontroller/torbinary.html b/docs/html/onionr/netcontroller/torbinary.html new file mode 100644 index 00000000..3edd6368 --- /dev/null +++ b/docs/html/onionr/netcontroller/torbinary.html @@ -0,0 +1,109 @@ + + + + + + +onionr.netcontroller.torbinary API documentation + + + + + + + + + +
+
+
+

Module onionr.netcontroller.torbinary

+
+
+

Onionr - Private P2P Communication

+

get the tor binary path

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    get the tor binary path
+'''
+'''
+    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/>.
+'''
+import os
+from shutil import which
+
+def tor_binary():
+    '''Return tor binary path or none if not exists'''
+    tor_path = './tor'
+    if not os.path.exists(tor_path):
+        tor_path = which('tor')
+    return tor_path
+
+
+
+
+
+
+
+

Functions

+
+
+def tor_binary() +
+
+

Return tor binary path or none if not exists

+
+Source code +
def tor_binary():
+    '''Return tor binary path or none if not exists'''
+    tor_path = './tor'
+    if not os.path.exists(tor_path):
+        tor_path = which('tor')
+    return tor_path
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/netcontroller/torcontroller.html b/docs/html/onionr/netcontroller/torcontroller.html new file mode 100644 index 00000000..e5f548f3 --- /dev/null +++ b/docs/html/onionr/netcontroller/torcontroller.html @@ -0,0 +1,85 @@ + + + + + + +onionr.netcontroller.torcontroller API documentation + + + + + + + + + +
+
+
+

Module onionr.netcontroller.torcontroller

+
+
+
+Source code +
from stem.control import Controller
+
+import config
+
+def get_controller():
+    c = Controller.from_port(port=config.get('tor.controlPort'))
+    c.authenticate(config.get('tor.controlpassword'))
+    return c
+
+
+
+
+
+
+
+

Functions

+
+
+def get_controller() +
+
+
+
+Source code +
def get_controller():
+    c = Controller.from_port(port=config.get('tor.controlPort'))
+    c.authenticate(config.get('tor.controlpassword'))
+    return c
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/notifier/index.html b/docs/html/onionr/notifier/index.html new file mode 100644 index 00000000..12c6b1fe --- /dev/null +++ b/docs/html/onionr/notifier/index.html @@ -0,0 +1,109 @@ + + + + + + +onionr.notifier API documentation + + + + + + + + + +
+
+
+

Module onionr.notifier

+
+
+

Onionr - Private P2P Communication

+

Desktop notification wrapper

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Desktop notification wrapper
+'''
+'''
+    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/>.
+'''
+try:
+    import simplenotifications as simplenotify
+except ImportError:
+    notifications_enabled = False
+else:
+    notifications_enabled = True
+
+def notify(title: str = "Onionr", message: str = ""):
+    """cross platform method to show a notification"""
+    if not notifications_enabled: return
+    simplenotify.notify(title, message)
+
+
+
+
+
+
+
+

Functions

+
+
+def notify(title='Onionr', message='') +
+
+

cross platform method to show a notification

+
+Source code +
def notify(title: str = "Onionr", message: str = ""):
+    """cross platform method to show a notification"""
+    if not notifications_enabled: return
+    simplenotify.notify(title, message)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrblocks/blockimporter.html b/docs/html/onionr/onionrblocks/blockimporter.html new file mode 100644 index 00000000..de72f9bd --- /dev/null +++ b/docs/html/onionr/onionrblocks/blockimporter.html @@ -0,0 +1,160 @@ + + + + + + +onionr.onionrblocks.blockimporter API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrblocks.blockimporter

+
+
+

Onionr - Private P2P Communication

+

Import block data and save it

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Import block data and save it
+'''
+'''
+    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/>.
+'''
+import onionrexceptions, logger
+from onionrutils import validatemetadata, blockmetadata
+from coredb import blockmetadb
+from . import onionrblacklist
+import onionrstorage
+import onionrcrypto as crypto
+def importBlockFromData(content):
+    blacklist = onionrblacklist.OnionrBlackList()
+    retData = False
+
+    try:
+        content = content.encode()
+    except AttributeError:
+        pass
+
+    dataHash = crypto.hashers.sha3_hash(content)
+
+    if blacklist.inBlacklist(dataHash):
+        raise onionrexceptions.BlacklistedBlock('%s is a blacklisted block' % (dataHash,))
+
+    metas = blockmetadata.get_block_metadata_from_data(content) # returns tuple(metadata, meta), meta is also in metadata
+    metadata = metas[0]
+    if validatemetadata.validate_metadata(metadata, metas[2]): # check if metadata is valid
+        if crypto.cryptoutils.verify_POW(content): # check if POW is enough/correct
+            logger.info('Block passed proof, saving.', terminal=True)
+            try:
+                blockHash = onionrstorage.set_data(content)
+            except onionrexceptions.DiskAllocationReached:
+                logger.warn('Failed to save block due to full disk allocation')
+            else:
+                blockmetadb.add_to_block_DB(blockHash, dataSaved=True)
+                blockmetadata.process_block_metadata(blockHash) # caches block metadata values to block database
+                retData = True
+        else:
+            raise onionrexceptions.InvalidProof
+    return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def importBlockFromData(content) +
+
+
+
+Source code +
def importBlockFromData(content):
+    blacklist = onionrblacklist.OnionrBlackList()
+    retData = False
+
+    try:
+        content = content.encode()
+    except AttributeError:
+        pass
+
+    dataHash = crypto.hashers.sha3_hash(content)
+
+    if blacklist.inBlacklist(dataHash):
+        raise onionrexceptions.BlacklistedBlock('%s is a blacklisted block' % (dataHash,))
+
+    metas = blockmetadata.get_block_metadata_from_data(content) # returns tuple(metadata, meta), meta is also in metadata
+    metadata = metas[0]
+    if validatemetadata.validate_metadata(metadata, metas[2]): # check if metadata is valid
+        if crypto.cryptoutils.verify_POW(content): # check if POW is enough/correct
+            logger.info('Block passed proof, saving.', terminal=True)
+            try:
+                blockHash = onionrstorage.set_data(content)
+            except onionrexceptions.DiskAllocationReached:
+                logger.warn('Failed to save block due to full disk allocation')
+            else:
+                blockmetadb.add_to_block_DB(blockHash, dataSaved=True)
+                blockmetadata.process_block_metadata(blockHash) # caches block metadata values to block database
+                retData = True
+        else:
+            raise onionrexceptions.InvalidProof
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrblocks/index.html b/docs/html/onionr/onionrblocks/index.html new file mode 100644 index 00000000..418786e8 --- /dev/null +++ b/docs/html/onionr/onionrblocks/index.html @@ -0,0 +1,91 @@ + + + + + + +onionr.onionrblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrblocks

+
+
+
+Source code +
from . import insert
+
+insert = insert.insert_block
+
+
+
+

Sub-modules

+
+
onionr.onionrblocks.blockimporter
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrblocks.insert
+
+
+
+
onionr.onionrblocks.onionrblacklist
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrblocks.onionrblockapi
+
+

Onionr - P2P Anonymous Storage Network …

+
+
onionr.onionrblocks.storagecounter
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrblocks/insert.html b/docs/html/onionr/onionrblocks/insert.html new file mode 100644 index 00000000..f6a7588e --- /dev/null +++ b/docs/html/onionr/onionrblocks/insert.html @@ -0,0 +1,386 @@ + + + + + + +onionr.onionrblocks.insert API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrblocks.insert

+
+
+
+Source code +
from typing import Union
+import json
+from onionrutils import bytesconverter, epoch
+import filepaths, onionrstorage
+from . import storagecounter
+from onionrplugins import onionrevents as events
+from etc import powchoice, onionrvalues
+import config, onionrcrypto as crypto, onionrexceptions
+from onionrusers import onionrusers
+from onionrutils import localcommand, blockmetadata, stringvalidators
+import coredb
+import onionrproofs
+from onionrproofs import subprocesspow
+import logger
+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)->Union[str,bool]:
+    """
+        Inserts a block into the network
+        encryptType must be specified to encrypt a block
+    """
+    use_subprocess = powchoice.use_subprocess(config)
+    storage_counter = storagecounter.StorageCounter()
+    allocationReachedMessage = 'Cannot insert block, disk allocation reached.'
+    if storage_counter.is_full():
+        logger.error(allocationReachedMessage)
+        raise onionrexceptions.DiskAllocationReached
+    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 nonceFile:
+        nonceFile.write(dataNonce + '\n')
+
+    if type(data) is bytes:
+        data = data.decode()
+    data = str(data)
+    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 encryptType in ('', None):
+            raise onionrexceptions.InvalidMetadata('encryptType must be asym or sym, or blank')
+
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+
+    if encryptType == 'asym':
+        meta['rply'] = createTime # Duplicate the time in encrypted messages to prevent replays
+        if not disableForward and sign and asymPeer != crypto.pub_key:
+            try:
+                forwardEncrypted = onionrusers.OnionrUser(asymPeer).forwardEncrypt(data)
+                data = forwardEncrypted[0]
+                meta['forwardEnc'] = True
+                expire = forwardEncrypted[2] # Expire time of key. no sense keeping block after that
+            except onionrexceptions.InvalidPubkey:
+                pass
+                #onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
+            fsKey = onionrusers.OnionrUser(asymPeer).generateForwardKey()
+            #fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys().reverse()
+            meta['newFSKey'] = fsKey
+    jsonMeta = json.dumps(meta)
+    plaintextMeta = jsonMeta
+    if sign:
+        signature = crypto.signing.ed_sign(jsonMeta.encode() + data, key=crypto.priv_key, encodeResult=True)
+        signer = crypto.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)#.decode()
+            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:
+            raise onionrexceptions.InvalidPubkey(asymPeer + ' 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):
+        if not len(str(int(expire))) < 20: raise ValueError('expire must be valid int less than 20 digits in length')
+        metadata['expire'] = expire
+
+    # send block data (and metadata) to POW module to get tokenized block data
+    if use_subprocess:
+        payload = subprocesspow.SubprocessPOW(data, metadata).start()
+    else:
+        payload = onionrproofs.POW(metadata, data).waitForResult()
+    if payload != False:
+        try:
+            retData = onionrstorage.set_data(payload)
+        except onionrexceptions.DiskAllocationReached:
+            logger.error(allocationReachedMessage)
+            retData = False
+        else:
+            # Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult
+            if localcommand.local_command('/ping', maxWait=10) == 'pong!':
+                if config.get('general.security_level', 1) == 0:
+                    localcommand.local_command('/waitforshare/' + retData, post=True, maxWait=5)
+                coredb.daemonqueue.daemon_queue_add('uploadBlock', retData)
+            else:
+                pass
+            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:
+        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)
+    coredb.daemonqueue.daemon_queue_add('remove_from_insert_list', data=dataNonce)
+    return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def insert_block(data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta={}, expire=None, disableForward=False) +
+
+

Inserts a block into the network +encryptType must be specified to encrypt a block

+
+Source code +
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)->Union[str,bool]:
+    """
+        Inserts a block into the network
+        encryptType must be specified to encrypt a block
+    """
+    use_subprocess = powchoice.use_subprocess(config)
+    storage_counter = storagecounter.StorageCounter()
+    allocationReachedMessage = 'Cannot insert block, disk allocation reached.'
+    if storage_counter.is_full():
+        logger.error(allocationReachedMessage)
+        raise onionrexceptions.DiskAllocationReached
+    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 nonceFile:
+        nonceFile.write(dataNonce + '\n')
+
+    if type(data) is bytes:
+        data = data.decode()
+    data = str(data)
+    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 encryptType in ('', None):
+            raise onionrexceptions.InvalidMetadata('encryptType must be asym or sym, or blank')
+
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+
+    if encryptType == 'asym':
+        meta['rply'] = createTime # Duplicate the time in encrypted messages to prevent replays
+        if not disableForward and sign and asymPeer != crypto.pub_key:
+            try:
+                forwardEncrypted = onionrusers.OnionrUser(asymPeer).forwardEncrypt(data)
+                data = forwardEncrypted[0]
+                meta['forwardEnc'] = True
+                expire = forwardEncrypted[2] # Expire time of key. no sense keeping block after that
+            except onionrexceptions.InvalidPubkey:
+                pass
+                #onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
+            fsKey = onionrusers.OnionrUser(asymPeer).generateForwardKey()
+            #fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys().reverse()
+            meta['newFSKey'] = fsKey
+    jsonMeta = json.dumps(meta)
+    plaintextMeta = jsonMeta
+    if sign:
+        signature = crypto.signing.ed_sign(jsonMeta.encode() + data, key=crypto.priv_key, encodeResult=True)
+        signer = crypto.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)#.decode()
+            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:
+            raise onionrexceptions.InvalidPubkey(asymPeer + ' 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):
+        if not len(str(int(expire))) < 20: raise ValueError('expire must be valid int less than 20 digits in length')
+        metadata['expire'] = expire
+
+    # send block data (and metadata) to POW module to get tokenized block data
+    if use_subprocess:
+        payload = subprocesspow.SubprocessPOW(data, metadata).start()
+    else:
+        payload = onionrproofs.POW(metadata, data).waitForResult()
+    if payload != False:
+        try:
+            retData = onionrstorage.set_data(payload)
+        except onionrexceptions.DiskAllocationReached:
+            logger.error(allocationReachedMessage)
+            retData = False
+        else:
+            # Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult
+            if localcommand.local_command('/ping', maxWait=10) == 'pong!':
+                if config.get('general.security_level', 1) == 0:
+                    localcommand.local_command('/waitforshare/' + retData, post=True, maxWait=5)
+                coredb.daemonqueue.daemon_queue_add('uploadBlock', retData)
+            else:
+                pass
+            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:
+        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)
+    coredb.daemonqueue.daemon_queue_add('remove_from_insert_list', data=dataNonce)
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrblocks/onionrblacklist.html b/docs/html/onionr/onionrblocks/onionrblacklist.html new file mode 100644 index 00000000..f4a5331b --- /dev/null +++ b/docs/html/onionr/onionrblocks/onionrblacklist.html @@ -0,0 +1,443 @@ + + + + + + +onionr.onionrblocks.onionrblacklist API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrblocks.onionrblacklist

+
+
+

Onionr - Private P2P Communication

+

This file handles maintenence of a blacklist database, for blocks and peers

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file handles maintenence of a blacklist database, for blocks and peers
+'''
+'''
+    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/>.
+'''
+import sqlite3, os
+import logger, onionrcrypto
+from onionrutils import epoch, bytesconverter
+from coredb import dbfiles
+
+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,)):
+            retData = True # this only executes if an entry is present by that hash
+            break
+
+        return retData
+
+    def _dbExecute(self, toExec, params = ()):
+        conn = sqlite3.connect(self.blacklistDB)
+        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))
+        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
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class OnionrBlackList +
+
+
+
+Source code +
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,)):
+            retData = True # this only executes if an entry is present by that hash
+            break
+
+        return retData
+
+    def _dbExecute(self, toExec, params = ()):
+        conn = sqlite3.connect(self.blacklistDB)
+        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))
+        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
+
+

Methods

+
+
+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

+
+Source code +
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))
+    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
+
+
+
+def clearDB(self) +
+
+
+
+Source code +
def clearDB(self):
+    self._dbExecute('''DELETE FROM blacklist;''')
+
+
+
+def deleteBeforeDate(self, date) +
+
+
+
+Source code +
def deleteBeforeDate(self, date):
+    # TODO, delete blacklist entries before date
+    return
+
+
+
+def deleteExpired(self, dataType=0) +
+
+

Delete expired entries

+
+Source code +
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) +
+
+
+
+Source code +
def generateDB(self):
+    return
+
+
+
+def getList(self) +
+
+
+
+Source code +
def getList(self):
+    data = self._dbExecute('SELECT * FROM blacklist')
+    myList = []
+    for i in data:
+        myList.append(i[0])
+    return myList
+
+
+
+def inBlacklist(self, data) +
+
+
+
+Source code +
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,)):
+        retData = True # this only executes if an entry is present by that hash
+        break
+
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrblocks/onionrblockapi.html b/docs/html/onionr/onionrblocks/onionrblockapi.html new file mode 100644 index 00000000..f34439b5 --- /dev/null +++ b/docs/html/onionr/onionrblocks/onionrblockapi.html @@ -0,0 +1,1910 @@ + + + + + + +onionr.onionrblocks.onionrblockapi API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrblocks.onionrblockapi

+
+
+

Onionr - P2P Anonymous Storage Network

+

This file contains the OnionrBlocks class which is a class for working with Onionr blocks

+
+Source code +
'''
+    Onionr - P2P Anonymous Storage Network
+
+    This file contains the OnionrBlocks class which is a class for working with Onionr 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/>.
+'''
+import binascii
+import logger, config, onionrexceptions, nacl.exceptions
+import json, os, sys, datetime, base64, onionrstorage
+from onionrusers import onionrusers
+from onionrutils import stringvalidators, epoch
+from coredb import blockmetadb
+from onionrstorage import removeblock
+import onionrblocks
+from onionrcrypto import encryption, cryptoutils as cryptoutils, signing
+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.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:
+                try:
+                    self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=encodedData)
+                except (binascii.Error, ValueError) as e:
+                    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:
+                pass
+                #logger.debug('Could not decrypt block. Either invalid key or corrupted data')
+            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 or 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') + 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)
+
+            # if block can't be parsed, it's a waste of precious space. Throw it away.
+            if not self.delete():
+                logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
+            else:
+                logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)
+
+        self.valid = False
+        return False
+
+    def delete(self):
+        '''
+            Deletes the block's file and records, if they exist
+
+            Outputs:
+            - (bool): whether or not the operation was successful
+        '''
+
+        if self.exists():
+            try:
+                os.remove(self.getBlockFile())
+            except TypeError:
+                pass
+            b_hash = self.getHash()
+            onionrstorage.deleteBlock(b_hash)
+            removeblock.remove_block(b_hash)
+            return True
+        return False
+
+    def save(self, sign = False, recreate = True):
+        '''
+            Saves a block to file and imports it into Onionr
+
+            Inputs:
+            - sign (bool): whether or not to sign the block before saving
+            - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash
+
+            Outputs:
+            - (bool): whether or not the operation was successful
+        '''
+
+        try:
+            if self.isValid() is True:
+
+                self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
+                if self.hash != False:
+                    self.update()
+
+                return self.getHash()
+            else:
+                logger.warn('Not writing block; it is invalid.')
+        except Exception as e:
+            logger.error('Failed to save block.', error = e, timestamp = 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 str(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
+        '''
+
+        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 getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
+        '''
+            Returns a list of Block objects based on supplied filters
+
+            Inputs:
+            - type (str): filters by block type
+            - signer (str/list): filters by signer (one in the list has to be a signer)
+            - signed (bool): filters out by whether or not the block is signed
+            - reverse (bool): reverses the list if True
+
+            Outputs:
+            - (list): a list of Block objects that match the input
+        '''
+
+        try:
+
+            relevant_blocks = list()
+            blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))
+
+            for block in blocks:
+                if Block.exists(block):
+                    block = Block(block)
+
+                    relevant = True
+
+                    if (not signed is None) and (block.isSigned() != bool(signed)):
+                        relevant = False
+                    if not signer is None:
+                        if isinstance(signer, (str,)):
+                            signer = [signer]
+                        if isinstance(signer, (bytes,)):
+                            signer = [signer.decode()]
+
+                        isSigner = False
+                        for key in signer:
+                            if block.isSigner(key):
+                                isSigner = True
+                                break
+
+                        if not isSigner:
+                            relevant = False
+
+                    if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
+                        relevant_blocks.append(block)
+
+            if bool(reverse):
+                relevant_blocks.reverse()
+
+            return relevant_blocks
+        except Exception as e:
+            logger.debug('Failed to get blocks.', error = e)
+
+        return list()
+
+    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
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Block +(hash=None, type=None, content=None, expire=None, decrypt=False, bypassReplayCheck=False) +
+
+
+
+Source code +
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.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:
+                try:
+                    self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=encodedData)
+                except (binascii.Error, ValueError) as e:
+                    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:
+                pass
+                #logger.debug('Could not decrypt block. Either invalid key or corrupted data')
+            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 or 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') + 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)
+
+            # if block can't be parsed, it's a waste of precious space. Throw it away.
+            if not self.delete():
+                logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
+            else:
+                logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)
+
+        self.valid = False
+        return False
+
+    def delete(self):
+        '''
+            Deletes the block's file and records, if they exist
+
+            Outputs:
+            - (bool): whether or not the operation was successful
+        '''
+
+        if self.exists():
+            try:
+                os.remove(self.getBlockFile())
+            except TypeError:
+                pass
+            b_hash = self.getHash()
+            onionrstorage.deleteBlock(b_hash)
+            removeblock.remove_block(b_hash)
+            return True
+        return False
+
+    def save(self, sign = False, recreate = True):
+        '''
+            Saves a block to file and imports it into Onionr
+
+            Inputs:
+            - sign (bool): whether or not to sign the block before saving
+            - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash
+
+            Outputs:
+            - (bool): whether or not the operation was successful
+        '''
+
+        try:
+            if self.isValid() is True:
+
+                self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
+                if self.hash != False:
+                    self.update()
+
+                return self.getHash()
+            else:
+                logger.warn('Not writing block; it is invalid.')
+        except Exception as e:
+            logger.error('Failed to save block.', error = e, timestamp = 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 str(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
+        '''
+
+        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 getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
+        '''
+            Returns a list of Block objects based on supplied filters
+
+            Inputs:
+            - type (str): filters by block type
+            - signer (str/list): filters by signer (one in the list has to be a signer)
+            - signed (bool): filters out by whether or not the block is signed
+            - reverse (bool): reverses the list if True
+
+            Outputs:
+            - (list): a list of Block objects that match the input
+        '''
+
+        try:
+
+            relevant_blocks = list()
+            blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))
+
+            for block in blocks:
+                if Block.exists(block):
+                    block = Block(block)
+
+                    relevant = True
+
+                    if (not signed is None) and (block.isSigned() != bool(signed)):
+                        relevant = False
+                    if not signer is None:
+                        if isinstance(signer, (str,)):
+                            signer = [signer]
+                        if isinstance(signer, (bytes,)):
+                            signer = [signer.decode()]
+
+                        isSigner = False
+                        for key in signer:
+                            if block.isSigner(key):
+                                isSigner = True
+                                break
+
+                        if not isSigner:
+                            relevant = False
+
+                    if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
+                        relevant_blocks.append(block)
+
+            if bool(reverse):
+                relevant_blocks.reverse()
+
+            return relevant_blocks
+        except Exception as e:
+            logger.debug('Failed to get blocks.', error = e)
+
+        return list()
+
+    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
+
+

Class variables

+
+
var blockCache
+
+
+
+
var blockCacheOrder
+
+
+
+
+

Methods

+
+
+def decrypt(self, encodedData=True) +
+
+

Decrypt a block, loading decrypted data into their vars

+
+Source code +
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:
+            try:
+                self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=encodedData)
+            except (binascii.Error, ValueError) as e:
+                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:
+            pass
+            #logger.debug('Could not decrypt block. Either invalid key or corrupted data')
+        except onionrexceptions.ReplayAttack:
+            logger.warn('%s is possibly a replay attack' % (self.hash,))
+        else:
+            retData = True
+            self.decrypted = True
+    return retData
+
+
+
+def delete(self) +
+
+

Deletes the block's file and records, if they exist

+

Outputs: +- (bool): whether or not the operation was successful

+
+Source code +
def delete(self):
+    '''
+        Deletes the block's file and records, if they exist
+
+        Outputs:
+        - (bool): whether or not the operation was successful
+    '''
+
+    if self.exists():
+        try:
+            os.remove(self.getBlockFile())
+        except TypeError:
+            pass
+        b_hash = self.getHash()
+        onionrstorage.deleteBlock(b_hash)
+        removeblock.remove_block(b_hash)
+        return True
+    return False
+
+
+
+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

+
+Source code +
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
+
+
+
+def getBlockFile(self) +
+
+

Returns the location of the block file if it is saved

+

Outputs: +- (str): the location of the block file, or None

+
+Source code +
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 getBlocks(type=None, signer=None, signed=None, reverse=False, limit=None) +
+
+

Returns a list of Block objects based on supplied filters

+

Inputs: +- type (str): filters by block type +- signer (str/list): filters by signer (one in the list has to be a signer) +- signed (bool): filters out by whether or not the block is signed +- reverse (bool): reverses the list if True

+

Outputs: +- (list): a list of Block objects that match the input

+
+Source code +
def getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
+    '''
+        Returns a list of Block objects based on supplied filters
+
+        Inputs:
+        - type (str): filters by block type
+        - signer (str/list): filters by signer (one in the list has to be a signer)
+        - signed (bool): filters out by whether or not the block is signed
+        - reverse (bool): reverses the list if True
+
+        Outputs:
+        - (list): a list of Block objects that match the input
+    '''
+
+    try:
+
+        relevant_blocks = list()
+        blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))
+
+        for block in blocks:
+            if Block.exists(block):
+                block = Block(block)
+
+                relevant = True
+
+                if (not signed is None) and (block.isSigned() != bool(signed)):
+                    relevant = False
+                if not signer is None:
+                    if isinstance(signer, (str,)):
+                        signer = [signer]
+                    if isinstance(signer, (bytes,)):
+                        signer = [signer.decode()]
+
+                    isSigner = False
+                    for key in signer:
+                        if block.isSigner(key):
+                            isSigner = True
+                            break
+
+                    if not isSigner:
+                        relevant = False
+
+                if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
+                    relevant_blocks.append(block)
+
+        if bool(reverse):
+            relevant_blocks.reverse()
+
+        return relevant_blocks
+    except Exception as e:
+        logger.debug('Failed to get blocks.', error = e)
+
+    return list()
+
+
+
+def getContent(self) +
+
+

Returns the contents of the block

+

Outputs: +- (str): the contents of the block

+
+Source code +
def getContent(self):
+    '''
+        Returns the contents of the block
+
+        Outputs:
+        - (str): the contents of the block
+    '''
+
+    return str(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

+
+Source code +
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 getExpire(self) +
+
+

Returns the expire time for a block

+

Outputs: +- (int): the expire time for a block, or None

+
+Source code +
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

+
+Source code +
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 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

+
+Source code +
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

+
+Source code +
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 getRaw(self) +
+
+

Returns the raw contents of the block, if saved to file

+

Outputs: +- (bytes): the raw contents of the block, or None

+
+Source code +
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 getSignature(self) +
+
+

Returns the base64-encoded signature

+

Outputs: +- (str): the signature, or None

+
+Source code +
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

+
+Source code +
def getSignedData(self):
+    '''
+        Returns the data that was signed
+
+        Outputs:
+        - (str): the data that was signed, or None
+    '''
+
+    return self.signedData
+
+
+
+def getType(self) +
+
+

Returns the type of the block

+

Outputs: +- (str): the type of the block

+
+Source code +
def getType(self):
+    '''
+        Returns the type of the block
+
+        Outputs:
+        - (str): the type of the block
+    '''
+    return self.btype
+
+
+
+def isSigned(self) +
+
+

Checks if the block was signed

+

Outputs: +- (bool): whether or not the block is signed

+
+Source code +
def isSigned(self):
+    '''
+        Checks if the block was signed
+
+        Outputs:
+        - (bool): whether or not the block is signed
+    '''
+
+    return self.signed
+
+
+
+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

+
+Source code +
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
+    '''
+
+    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
+
+
+
+def isValid(self) +
+
+

Checks if the block is valid

+

Outputs: +- (bool): whether or not the block is valid

+
+Source code +
def isValid(self):
+    '''
+        Checks if the block is valid
+
+        Outputs:
+        - (bool): whether or not the block is valid
+    '''
+
+    return self.valid
+
+
+
+def save(self, sign=False, recreate=True) +
+
+

Saves a block to file and imports it into Onionr

+

Inputs: +- sign (bool): whether or not to sign the block before saving +- recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

+

Outputs: +- (bool): whether or not the operation was successful

+
+Source code +
def save(self, sign = False, recreate = True):
+    '''
+        Saves a block to file and imports it into Onionr
+
+        Inputs:
+        - sign (bool): whether or not to sign the block before saving
+        - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash
+
+        Outputs:
+        - (bool): whether or not the operation was successful
+    '''
+
+    try:
+        if self.isValid() is True:
+
+            self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
+            if self.hash != False:
+                self.update()
+
+            return self.getHash()
+        else:
+            logger.warn('Not writing block; it is invalid.')
+    except Exception as e:
+        logger.error('Failed to save block.', error = e, timestamp = False)
+
+    return False
+
+
+
+def setContent(self, bcontent) +
+
+

Sets the contents of the block

+

Inputs: +- bcontent (str): the contents to be set to

+

Outputs: +- (Block): the Block instance

+
+Source code +
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
+
+
+
+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

+
+Source code +
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 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

+
+Source code +
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 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

+
+Source code +
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') + 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)
+
+        # if block can't be parsed, it's a waste of precious space. Throw it away.
+        if not self.delete():
+            logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
+        else:
+            logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)
+
+    self.valid = False
+    return False
+
+
+
+def verifySig(self) +
+
+

Verify if a block's signature is signed by its claimed signer

+
+Source code +
def verifySig(self):
+    '''
+        Verify if a block's signature is signed by its claimed signer
+    '''
+    if self.signer is None or signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
+        self.validSig = True
+    else:
+        self.validSig = False
+    return self.validSig
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrblocks/storagecounter.html b/docs/html/onionr/onionrblocks/storagecounter.html new file mode 100644 index 00000000..a54d9e3c --- /dev/null +++ b/docs/html/onionr/onionrblocks/storagecounter.html @@ -0,0 +1,284 @@ + + + + + + +onionr.onionrblocks.storagecounter API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrblocks.storagecounter

+
+
+

Onionr - Private P2P Communication

+

Keeps track of how much disk space we're using

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    Keeps track of how much disk space we're using
+"""
+"""
+    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/>.
+"""
+import config, filepaths
+config.reload()
+class StorageCounter:
+    def __init__(self):
+        self.data_file = filepaths.usage_file
+        return
+
+    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', 2000000000) <= (self.get_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_amount(self)->int:
+        """Return how much disk space we're using (according to record)"""
+        ret_data = 0
+        try:
+            with open(self.data_file, 'r') as data_file:
+                ret_data = int(data_file.read())
+        except FileNotFoundError:
+            pass
+        except ValueError:
+            pass # Possibly happens when the file is empty
+        return ret_data
+    
+    def get_percent(self)->int:
+        """Return percent (decimal/float) of disk space we're using"""
+        amount = self.get_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.get_amount()
+        ret_data = new_amount
+        if new_amount > config.get('allocations.disk', 2000000000):
+            ret_data = False
+        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.get_amount() - amount
+        self._update(new_amount)
+        return new_amount
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class StorageCounter +
+
+
+
+Source code +
class StorageCounter:
+    def __init__(self):
+        self.data_file = filepaths.usage_file
+        return
+
+    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', 2000000000) <= (self.get_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_amount(self)->int:
+        """Return how much disk space we're using (according to record)"""
+        ret_data = 0
+        try:
+            with open(self.data_file, 'r') as data_file:
+                ret_data = int(data_file.read())
+        except FileNotFoundError:
+            pass
+        except ValueError:
+            pass # Possibly happens when the file is empty
+        return ret_data
+    
+    def get_percent(self)->int:
+        """Return percent (decimal/float) of disk space we're using"""
+        amount = self.get_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.get_amount()
+        ret_data = new_amount
+        if new_amount > config.get('allocations.disk', 2000000000):
+            ret_data = False
+        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.get_amount() - amount
+        self._update(new_amount)
+        return new_amount
+
+

Methods

+
+
+def add_bytes(self, amount) +
+
+

Record that we are now using more disk space, unless doing so would exceed configured max

+
+Source code +
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.get_amount()
+    ret_data = new_amount
+    if new_amount > config.get('allocations.disk', 2000000000):
+        ret_data = False
+    else:
+        self._update(new_amount)
+    return ret_data
+
+
+
+def get_amount(self) +
+
+

Return how much disk space we're using (according to record)

+
+Source code +
def get_amount(self)->int:
+    """Return how much disk space we're using (according to record)"""
+    ret_data = 0
+    try:
+        with open(self.data_file, 'r') as data_file:
+            ret_data = int(data_file.read())
+    except FileNotFoundError:
+        pass
+    except ValueError:
+        pass # Possibly happens when the file is empty
+    return ret_data
+
+
+
+def get_percent(self) +
+
+

Return percent (decimal/float) of disk space we're using

+
+Source code +
def get_percent(self)->int:
+    """Return percent (decimal/float) of disk space we're using"""
+    amount = self.get_amount()
+    return round(amount / config.get('allocations.disk', 2000000000), 2)
+
+
+
+def is_full(self) +
+
+

Returns if the allocated disk space is full (this is Onionr config, not true FS capacity)

+
+Source code +
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', 2000000000) <= (self.get_amount() + 1000):
+        ret_data = True
+    return ret_data
+
+
+
+def remove_bytes(self, amount) +
+
+

Record that we are now using less disk space

+
+Source code +
def remove_bytes(self, amount)->int:
+    """Record that we are now using less disk space"""
+    new_amount = self.get_amount() - amount
+    self._update(new_amount)
+    return new_amount
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/banblocks.html b/docs/html/onionr/onionrcommands/banblocks.html new file mode 100644 index 00000000..00407312 --- /dev/null +++ b/docs/html/onionr/onionrcommands/banblocks.html @@ -0,0 +1,140 @@ + + + + + + +onionr.onionrcommands.banblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.banblocks

+
+
+

Onionr - Private P2P Communication

+

This file contains the command for banning blocks from the node

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file contains the command for banning blocks from the node
+'''
+'''
+    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/>.
+'''
+import sys
+import logger
+from onionrutils import stringvalidators
+from onionrstorage import removeblock
+from onionrblocks import onionrblacklist
+
+def ban_block():
+    blacklist = onionrblacklist.OnionrBlackList()
+    try:
+        ban = sys.argv[2]
+    except IndexError:
+        ban = logger.readline('Enter a block hash:')
+    if stringvalidators.validate_hash(ban):
+        if not blacklist.inBlacklist(ban):
+            try:
+                blacklist.addToDB(ban)
+                removeblock.remove_block(ban)
+            except Exception as error:
+                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>: deletes and blacklists a block"
+
+
+
+
+
+
+
+

Functions

+
+
+def ban_block() +
+
+
+
+Source code +
def ban_block():
+    blacklist = onionrblacklist.OnionrBlackList()
+    try:
+        ban = sys.argv[2]
+    except IndexError:
+        ban = logger.readline('Enter a block hash:')
+    if stringvalidators.validate_hash(ban):
+        if not blacklist.inBlacklist(ban):
+            try:
+                blacklist.addToDB(ban)
+                removeblock.remove_block(ban)
+            except Exception as error:
+                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)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/daemonlaunch.html b/docs/html/onionr/onionrcommands/daemonlaunch.html new file mode 100644 index 00000000..4c12fb64 --- /dev/null +++ b/docs/html/onionr/onionrcommands/daemonlaunch.html @@ -0,0 +1,352 @@ + + + + + + +onionr.onionrcommands.daemonlaunch API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.daemonlaunch

+
+
+

Onionr - Private P2P Communication

+

launch the api servers and communicator

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    launch the api servers and communicator
+'''
+'''
+    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/>.
+'''
+
+import os, time, sys, platform, sqlite3, signal
+from threading import Thread
+
+import toomanyobjs
+
+import config, apiservers, logger, communicator
+from onionrplugins import onionrevents as events
+from netcontroller import NetController
+from onionrutils import localcommand
+import filepaths
+from coredb import daemonqueue
+from etc import onionrvalues, cleanup
+from onionrcrypto import getourkeypair
+from utils import hastor, logoheader
+from . import version
+import serializeddata
+
+def _proper_shutdown():
+    localcommand.local_command('shutdown')
+    sys.exit(1)
+
+def daemon():
+    '''
+        Starts the Onionr communication daemon
+    '''
+    if not hastor.has_tor():
+        logger.error("Tor is not present in system path or Onionr directory", terminal=True)
+        sys.exit(1)
+
+    # remove runcheck if it exists
+    if os.path.isfile(filepaths.run_check_file):
+        logger.debug('Runcheck file found on daemon start, deleting in advance.')
+        os.remove(filepaths.run_check_file)
+    
+    # Create shared object
+
+    shared_state = toomanyobjs.TooMany()
+
+    Thread(target=shared_state.get(apiservers.ClientAPI).start, daemon=True, name='client HTTP API').start()
+    Thread(target=shared_state.get(apiservers.PublicAPI).start, daemon=True, name='public HTTP API').start()
+    shared_state.get(serializeddata.SerializedData)
+    shared_state.share_object() # share the parent object to the threads
+
+    apiHost = ''
+    while apiHost == '':
+        try:
+            with open(filepaths.public_API_host_file, 'r') as hostFile:
+                apiHost = hostFile.read()
+        except FileNotFoundError:
+            pass
+        time.sleep(0.5)
+
+    logger.raw('', terminal=True)
+    # print nice header thing :)
+    if config.get('general.display_header', True):
+        logoheader.header()
+    version.version(verbosity = 5, function = logger.info)
+    logger.debug('Python version %s' % platform.python_version())
+
+    if onionrvalues.DEVELOPMENT_MODE:
+        logger.warn('Development mode enabled', timestamp = False, terminal=True)
+
+    net = NetController(config.get('client.public.port', 59497), apiServerIP=apiHost)
+    shared_state.add(net)
+
+    logger.info('Tor is starting...', terminal=True)
+    if not net.startTor():
+        localcommand.local_command('shutdown')
+        sys.exit(1)
+    if len(net.myID) > 0 and config.get('general.security_level', 1) == 0:
+        logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID))
+    else:
+        logger.debug('.onion service disabled')
+    logger.info('Using public key: %s' % (logger.colors.underline + getourkeypair.get_keypair()[0][:52]))
+
+    try:
+        time.sleep(1)
+    except KeyboardInterrupt:
+        _proper_shutdown()
+    events.event('init', threaded = False)
+    events.event('daemon_start')
+    communicator.startCommunicator(shared_state)
+
+    localcommand.local_command('shutdown')
+
+    net.killTor()
+    try:
+        time.sleep(5) # Time to allow threads to finish, if not any "daemon" threads will be slaughtered http://docs.python.org/library/threading.html#threading.Thread.daemon
+    except KeyboardInterrupt:
+        pass
+    cleanup.delete_run_files()
+
+def _ignore_sigint(sig, frame):
+    '''This space intentionally left blank'''
+    return
+
+def kill_daemon():
+    '''
+        Shutdown the Onionr daemon (communicator)
+    '''
+
+    logger.warn('Stopping the running daemon...', timestamp = False, terminal=True)
+    try:
+        events.event('daemon_stop')
+        net = NetController(config.get('client.port', 59496))
+        try:
+            daemonqueue.daemon_queue_add('shutdown')
+        except sqlite3.OperationalError:
+            pass
+
+        net.killTor()
+    except Exception as e:
+        logger.error('Failed to shutdown daemon: ' + str(e), error = e, timestamp = False, terminal=True)
+    return
+
+kill_daemon.onionr_help = "Gracefully stops the Onionr API servers"
+
+def start(input: bool = False, override: bool = False):
+    """If no lock file, make one and start onionr, error if there is and its not overridden"""
+    if os.path.exists('.onionr-lock') and not override:
+        logger.fatal('Cannot start. Daemon is already running, or it did not exit cleanly.\n(if you are sure that there is not a daemon running, delete .onionr-lock & try again).', terminal=True)
+    else:
+        if not onionrvalues.DEVELOPMENT_MODE:
+            lockFile = open('.onionr-lock', 'w')
+            lockFile.write('')
+            lockFile.close()
+        daemon()
+        if not onionrvalues.DEVELOPMENT_MODE:
+            try:
+                os.remove('.onionr-lock')
+            except FileNotFoundError:
+                pass
+
+start.onionr_help = "Start Onionr node (public and clients API servers)"
+
+
+
+
+
+
+
+

Functions

+
+
+def daemon() +
+
+

Starts the Onionr communication daemon

+
+Source code +
def daemon():
+    '''
+        Starts the Onionr communication daemon
+    '''
+    if not hastor.has_tor():
+        logger.error("Tor is not present in system path or Onionr directory", terminal=True)
+        sys.exit(1)
+
+    # remove runcheck if it exists
+    if os.path.isfile(filepaths.run_check_file):
+        logger.debug('Runcheck file found on daemon start, deleting in advance.')
+        os.remove(filepaths.run_check_file)
+    
+    # Create shared object
+
+    shared_state = toomanyobjs.TooMany()
+
+    Thread(target=shared_state.get(apiservers.ClientAPI).start, daemon=True, name='client HTTP API').start()
+    Thread(target=shared_state.get(apiservers.PublicAPI).start, daemon=True, name='public HTTP API').start()
+    shared_state.get(serializeddata.SerializedData)
+    shared_state.share_object() # share the parent object to the threads
+
+    apiHost = ''
+    while apiHost == '':
+        try:
+            with open(filepaths.public_API_host_file, 'r') as hostFile:
+                apiHost = hostFile.read()
+        except FileNotFoundError:
+            pass
+        time.sleep(0.5)
+
+    logger.raw('', terminal=True)
+    # print nice header thing :)
+    if config.get('general.display_header', True):
+        logoheader.header()
+    version.version(verbosity = 5, function = logger.info)
+    logger.debug('Python version %s' % platform.python_version())
+
+    if onionrvalues.DEVELOPMENT_MODE:
+        logger.warn('Development mode enabled', timestamp = False, terminal=True)
+
+    net = NetController(config.get('client.public.port', 59497), apiServerIP=apiHost)
+    shared_state.add(net)
+
+    logger.info('Tor is starting...', terminal=True)
+    if not net.startTor():
+        localcommand.local_command('shutdown')
+        sys.exit(1)
+    if len(net.myID) > 0 and config.get('general.security_level', 1) == 0:
+        logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID))
+    else:
+        logger.debug('.onion service disabled')
+    logger.info('Using public key: %s' % (logger.colors.underline + getourkeypair.get_keypair()[0][:52]))
+
+    try:
+        time.sleep(1)
+    except KeyboardInterrupt:
+        _proper_shutdown()
+    events.event('init', threaded = False)
+    events.event('daemon_start')
+    communicator.startCommunicator(shared_state)
+
+    localcommand.local_command('shutdown')
+
+    net.killTor()
+    try:
+        time.sleep(5) # Time to allow threads to finish, if not any "daemon" threads will be slaughtered http://docs.python.org/library/threading.html#threading.Thread.daemon
+    except KeyboardInterrupt:
+        pass
+    cleanup.delete_run_files()
+
+
+
+def kill_daemon() +
+
+

Shutdown the Onionr daemon (communicator)

+
+Source code +
def kill_daemon():
+    '''
+        Shutdown the Onionr daemon (communicator)
+    '''
+
+    logger.warn('Stopping the running daemon...', timestamp = False, terminal=True)
+    try:
+        events.event('daemon_stop')
+        net = NetController(config.get('client.port', 59496))
+        try:
+            daemonqueue.daemon_queue_add('shutdown')
+        except sqlite3.OperationalError:
+            pass
+
+        net.killTor()
+    except Exception as e:
+        logger.error('Failed to shutdown daemon: ' + str(e), error = e, timestamp = False, terminal=True)
+    return
+
+
+
+def start(input=False, override=False) +
+
+

If no lock file, make one and start onionr, error if there is and its not overridden

+
+Source code +
def start(input: bool = False, override: bool = False):
+    """If no lock file, make one and start onionr, error if there is and its not overridden"""
+    if os.path.exists('.onionr-lock') and not override:
+        logger.fatal('Cannot start. Daemon is already running, or it did not exit cleanly.\n(if you are sure that there is not a daemon running, delete .onionr-lock & try again).', terminal=True)
+    else:
+        if not onionrvalues.DEVELOPMENT_MODE:
+            lockFile = open('.onionr-lock', 'w')
+            lockFile.write('')
+            lockFile.close()
+        daemon()
+        if not onionrvalues.DEVELOPMENT_MODE:
+            try:
+                os.remove('.onionr-lock')
+            except FileNotFoundError:
+                pass
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/exportblocks.html b/docs/html/onionr/onionrcommands/exportblocks.html new file mode 100644 index 00000000..942c9c70 --- /dev/null +++ b/docs/html/onionr/onionrcommands/exportblocks.html @@ -0,0 +1,144 @@ + + + + + + +onionr.onionrcommands.exportblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.exportblocks

+
+
+

Onionr - Private P2P Communication

+

This file handles the command for exporting blocks to disk

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file handles the command for exporting blocks to disk
+'''
+'''
+    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/>.
+'''
+import sys, os
+import logger, onionrstorage
+from utils import createdirs
+from onionrutils import stringvalidators
+import filepaths
+def doExport(bHash):
+    createdirs.create_dirs()
+    data = onionrstorage.getData(bHash)
+    with open('%s/%s.dat' % (filepaths.export_location, bHash), 'wb') as exportFile:
+        exportFile.write(data)
+        logger.info('Block exported as file', terminal=True)
+
+def export_block():
+    exportDir = filepaths.export_location
+    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:
+        bHash = sys.argv[2]
+        doExport(bHash)
+
+export_block.onionr_help = "<block hash>: Export an Onionr block to a file. Export directory is in the Onionr data directory under block-export/"
+
+
+
+
+
+
+
+

Functions

+
+
+def doExport(bHash) +
+
+
+
+Source code +
def doExport(bHash):
+    createdirs.create_dirs()
+    data = onionrstorage.getData(bHash)
+    with open('%s/%s.dat' % (filepaths.export_location, bHash), 'wb') as exportFile:
+        exportFile.write(data)
+        logger.info('Block exported as file', terminal=True)
+
+
+
+def export_block() +
+
+
+
+Source code +
def export_block():
+    exportDir = filepaths.export_location
+    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:
+        bHash = sys.argv[2]
+        doExport(bHash)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/filecommands.html b/docs/html/onionr/onionrcommands/filecommands.html new file mode 100644 index 00000000..7c9c0a80 --- /dev/null +++ b/docs/html/onionr/onionrcommands/filecommands.html @@ -0,0 +1,232 @@ + + + + + + +onionr.onionrcommands.filecommands API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.filecommands

+
+
+

Onionr - Private P2P Communication

+

This file handles the commands for adding and getting files from the Onionr network

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    This file handles the commands for adding and getting files from the Onionr network
+"""
+"""
+    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/>.
+"""
+
+import base64, sys, os
+import logger
+from onionrblocks.onionrblockapi import Block
+import onionrexceptions
+from onionrutils import stringvalidators
+from etc import onionrvalues
+from onionrblocks import insert
+_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
+    else: return path
+
+def add_html(singleBlock=True, blockType='html'):
+    add_file(singleBlock, blockType)
+
+add_html.onionr_help = "Adds an HTML file into Onionr. Does not currently include dependant resources"
+
+def add_file(singleBlock=False, blockType='bin'):
+    """
+        Adds a file to the onionr network
+    """
+
+    if len(sys.argv) >= 3:
+        filename = sys.argv[2]
+        contents = None
+        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 singleFile:
+                blockhash = insert(base64.b64encode(singleFile.read()), header=blockType)
+            if len(blockhash) > 0:
+                logger.info('File %s saved in block %s' % (filename, blockhash), terminal=True)
+        except Exception as e:
+            logger.error('Failed to save file in block ' + str(e), 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 the Onionr network"
+
+def get_file():
+    """
+        Get a file from onionr blocks
+    """
+    try:
+        fileName = _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(fileName, terminal=True)
+
+        contents = None
+        if os.path.exists(fileName):
+            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(fileName, 'wb') as myFile:
+                myFile.write(base64.b64decode(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 a file from the onionr network."
+
+
+
+
+
+
+
+

Functions

+
+
+def add_file(singleBlock=False, blockType='bin') +
+
+

Adds a file to the onionr network

+
+Source code +
def add_file(singleBlock=False, blockType='bin'):
+    """
+        Adds a file to the onionr network
+    """
+
+    if len(sys.argv) >= 3:
+        filename = sys.argv[2]
+        contents = None
+        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 singleFile:
+                blockhash = insert(base64.b64encode(singleFile.read()), header=blockType)
+            if len(blockhash) > 0:
+                logger.info('File %s saved in block %s' % (filename, blockhash), terminal=True)
+        except Exception as e:
+            logger.error('Failed to save file in block ' + str(e), timestamp = False, terminal=True)
+    else:
+        logger.error('%s add-file <filename>' % sys.argv[0], timestamp = False, terminal=True)
+
+
+
+def add_html(singleBlock=True, blockType='html') +
+
+
+
+Source code +
def add_html(singleBlock=True, blockType='html'):
+    add_file(singleBlock, blockType)
+
+
+
+def get_file() +
+
+

Get a file from onionr blocks

+
+Source code +
def get_file():
+    """
+        Get a file from onionr blocks
+    """
+    try:
+        fileName = _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(fileName, terminal=True)
+
+        contents = None
+        if os.path.exists(fileName):
+            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(fileName, 'wb') as myFile:
+                myFile.write(base64.b64decode(Block(bHash).bcontent))
+        except onionrexceptions.NoDataAvailable:
+            logger.error('That block is not available. Trying again later may work.', terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/index.html b/docs/html/onionr/onionrcommands/index.html new file mode 100644 index 00000000..6fac25e2 --- /dev/null +++ b/docs/html/onionr/onionrcommands/index.html @@ -0,0 +1,130 @@ + + + + + + +onionr.onionrcommands API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands

+
+
+
+
+

Sub-modules

+
+
onionr.onionrcommands.banblocks
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.daemonlaunch
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.exportblocks
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.filecommands
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.keyadders
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.onionrstatistics
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.openwebinterface
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.parser
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.pubkeymanager
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.resetplugins
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.resettor
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.restartonionr
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.softreset
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.version
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/keyadders.html b/docs/html/onionr/onionrcommands/keyadders.html new file mode 100644 index 00000000..dbb26ee1 --- /dev/null +++ b/docs/html/onionr/onionrcommands/keyadders.html @@ -0,0 +1,124 @@ + + + + + + +onionr.onionrcommands.keyadders API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.keyadders

+
+
+

Onionr - Private P2P Communication

+

add keys (transport and pubkey)

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    add keys (transport and pubkey)
+'''
+'''
+    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/>.
+'''
+import sys
+import logger
+from coredb import keydb
+
+def add_address():
+    try:
+        newAddress = sys.argv[2]
+        newAddress = newAddress.replace('http:', '').replace('/', '')
+    except IndexError:
+        pass
+    else:
+        logger.info("Adding address: " + logger.colors.underline + newAddress, terminal=True)
+        if keydb.addkeys.add_address(newAddress):
+            logger.info("Successfully added address.", terminal=True)
+        else:
+            logger.warn("Unable to add address.", terminal=True)
+
+add_address.onionr_help = "Adds a node transport address to the local node list"
+
+
+
+
+
+
+
+

Functions

+
+
+def add_address() +
+
+
+
+Source code +
def add_address():
+    try:
+        newAddress = sys.argv[2]
+        newAddress = newAddress.replace('http:', '').replace('/', '')
+    except IndexError:
+        pass
+    else:
+        logger.info("Adding address: " + logger.colors.underline + newAddress, terminal=True)
+        if keydb.addkeys.add_address(newAddress):
+            logger.info("Successfully added address.", terminal=True)
+        else:
+            logger.warn("Unable to add address.", terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/onionrstatistics.html b/docs/html/onionr/onionrcommands/onionrstatistics.html new file mode 100644 index 00000000..d0419977 --- /dev/null +++ b/docs/html/onionr/onionrcommands/onionrstatistics.html @@ -0,0 +1,254 @@ + + + + + + +onionr.onionrcommands.onionrstatistics API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.onionrstatistics

+
+
+

Onionr - Private P2P Communication

+

This module defines commands to show stats/details about the local node

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This module defines commands to show stats/details about the local node
+'''
+'''
+    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/>.
+'''
+import os, uuid, time
+import logger
+from onionrblocks import onionrblockapi
+from onionrutils import checkcommunicator, mnemonickeys
+from utils import sizeutils, gethostname, getconsolewidth, identifyhome
+from coredb import blockmetadb, daemonqueue, keydb
+import onionrcrypto, config
+from etc import onionrvalues
+def show_stats():
+    try:
+        # define stats messages here
+        totalBlocks = len(blockmetadb.get_block_list())
+        home = identifyhome.identify_home()
+        signedBlocks = len(onionrblockapi.Block.getBlocks(signed = True))
+        messages = {
+            # info about local client
+            'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if checkcommunicator.is_communicator_running(timeout = 9) else logger.colors.fg.red + 'Offline'),
+
+            # file and folder size stats
+            'div1' : True, # this creates a solid line across the screen, a div
+            'Total Block Size' : sizeutils.human_size(sizeutils.size(home + 'blocks/')),
+            'Total Plugin Size' : sizeutils.human_size(sizeutils.size(home + 'plugins/')),
+            'Log File Size' : sizeutils.human_size(sizeutils.size(home + 'output.log')),
+
+            # count stats
+            'div2' : True,
+            'Known Peers (nodes)' : str(max(len(keydb.listkeys.list_adders()) - 1, 0)),
+            'Enabled Plugins' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(home + 'plugins/'))),
+            'Stored Blocks' : str(totalBlocks),
+            'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%'
+        }
+
+        # color configuration
+        colors = {
+            'title' : logger.colors.bold,
+            'key' : logger.colors.fg.lightgreen,
+            'val' : logger.colors.fg.green,
+            'border' : logger.colors.fg.lightblue,
+
+            'reset' : logger.colors.reset
+        }
+
+        # pre-processing
+        maxlength = 0
+        width = getconsolewidth.get_console_width()
+        for key, val in messages.items():
+            if not (type(val) is bool and val is True):
+                maxlength = max(len(key), maxlength)
+        prewidth = maxlength + len(' | ')
+        groupsize = width - prewidth - len('[+] ')
+
+        # generate stats table
+        logger.info(colors['title'] + 'Onionr v%s Statistics' % onionrvalues.ONIONR_VERSION + colors['reset'], terminal=True)
+        logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
+        for key, val in messages.items():
+            if not (type(val) is bool and val is True):
+                val = [str(val)[i:i + groupsize] for i in range(0, len(str(val)), groupsize)]
+
+                logger.info(colors['key'] + str(key).rjust(maxlength) + colors['reset'] + colors['border'] + ' | ' + colors['reset'] + colors['val'] + str(val.pop(0)) + colors['reset'], terminal=True)
+
+                for value in val:
+                    logger.info(' ' * maxlength + colors['border'] + ' | ' + colors['reset'] + colors['val'] + str(value) + colors['reset'], terminal=True)
+            else:
+                logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
+        logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
+    except Exception as e:
+        logger.error('Failed to generate statistics table. ' + str(e), error = e, timestamp = False, terminal=True)
+
+def show_details():
+    details = {
+        'Node Address' : gethostname.get_hostname(),
+        'Web Password' : config.get('client.webpassword'),
+        'Public Key' : onionrcrypto.pub_key,
+        'Human-readable Public Key' : mnemonickeys.get_human_readable_ID()
+    }
+
+    for detail in details:
+        logger.info('%s%s: \n%s%s\n' % (logger.colors.fg.lightgreen, detail, logger.colors.fg.green, details[detail]), terminal = True)
+
+show_details.onionr_help = "Shows relevant information for your Onionr install: note address, web password and active public key."
+show_stats.onionr_help = "Shows statistics for your Onionr node. Slow if Onionr is not running"
+
+
+
+
+
+
+
+

Functions

+
+
+def show_details() +
+
+
+
+Source code +
def show_details():
+    details = {
+        'Node Address' : gethostname.get_hostname(),
+        'Web Password' : config.get('client.webpassword'),
+        'Public Key' : onionrcrypto.pub_key,
+        'Human-readable Public Key' : mnemonickeys.get_human_readable_ID()
+    }
+
+    for detail in details:
+        logger.info('%s%s: \n%s%s\n' % (logger.colors.fg.lightgreen, detail, logger.colors.fg.green, details[detail]), terminal = True)
+
+
+
+def show_stats() +
+
+
+
+Source code +
def show_stats():
+    try:
+        # define stats messages here
+        totalBlocks = len(blockmetadb.get_block_list())
+        home = identifyhome.identify_home()
+        signedBlocks = len(onionrblockapi.Block.getBlocks(signed = True))
+        messages = {
+            # info about local client
+            'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if checkcommunicator.is_communicator_running(timeout = 9) else logger.colors.fg.red + 'Offline'),
+
+            # file and folder size stats
+            'div1' : True, # this creates a solid line across the screen, a div
+            'Total Block Size' : sizeutils.human_size(sizeutils.size(home + 'blocks/')),
+            'Total Plugin Size' : sizeutils.human_size(sizeutils.size(home + 'plugins/')),
+            'Log File Size' : sizeutils.human_size(sizeutils.size(home + 'output.log')),
+
+            # count stats
+            'div2' : True,
+            'Known Peers (nodes)' : str(max(len(keydb.listkeys.list_adders()) - 1, 0)),
+            'Enabled Plugins' : str(len(config.get('plugins.enabled', list()))) + ' / ' + str(len(os.listdir(home + 'plugins/'))),
+            'Stored Blocks' : str(totalBlocks),
+            'Percent Blocks Signed' : str(round(100 * signedBlocks / max(totalBlocks, 1), 2)) + '%'
+        }
+
+        # color configuration
+        colors = {
+            'title' : logger.colors.bold,
+            'key' : logger.colors.fg.lightgreen,
+            'val' : logger.colors.fg.green,
+            'border' : logger.colors.fg.lightblue,
+
+            'reset' : logger.colors.reset
+        }
+
+        # pre-processing
+        maxlength = 0
+        width = getconsolewidth.get_console_width()
+        for key, val in messages.items():
+            if not (type(val) is bool and val is True):
+                maxlength = max(len(key), maxlength)
+        prewidth = maxlength + len(' | ')
+        groupsize = width - prewidth - len('[+] ')
+
+        # generate stats table
+        logger.info(colors['title'] + 'Onionr v%s Statistics' % onionrvalues.ONIONR_VERSION + colors['reset'], terminal=True)
+        logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
+        for key, val in messages.items():
+            if not (type(val) is bool and val is True):
+                val = [str(val)[i:i + groupsize] for i in range(0, len(str(val)), groupsize)]
+
+                logger.info(colors['key'] + str(key).rjust(maxlength) + colors['reset'] + colors['border'] + ' | ' + colors['reset'] + colors['val'] + str(val.pop(0)) + colors['reset'], terminal=True)
+
+                for value in val:
+                    logger.info(' ' * maxlength + colors['border'] + ' | ' + colors['reset'] + colors['val'] + str(value) + colors['reset'], terminal=True)
+            else:
+                logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
+        logger.info(colors['border'] + '-' * (maxlength + 1) + '+' + colors['reset'], terminal=True)
+    except Exception as e:
+        logger.error('Failed to generate statistics table. ' + str(e), error = e, timestamp = False, terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/openwebinterface.html b/docs/html/onionr/onionrcommands/openwebinterface.html new file mode 100644 index 00000000..8acb8712 --- /dev/null +++ b/docs/html/onionr/onionrcommands/openwebinterface.html @@ -0,0 +1,118 @@ + + + + + + +onionr.onionrcommands.openwebinterface API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.openwebinterface

+
+
+

Onionr - Private P2P Communication

+

Open the web interface properly into a web browser

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Open the web interface properly into a web browser
+'''
+'''
+    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/>.
+'''
+import webbrowser
+import logger
+from onionrutils import getclientapiserver
+import config
+def open_home():
+    try:
+        url = getclientapiserver.get_client_API_server()
+    except FileNotFoundError:
+        logger.error('Onionr seems to not be running (could not get api host)', terminal=True)
+    else:
+        url = 'http://%s/#%s' % (url, config.get('client.webpassword'))
+        logger.info('If Onionr does not open automatically, use this URL: ' + url, terminal=True)
+        webbrowser.open_new_tab(url)
+
+open_home.onionr_help = "Opens the Onionr web UI in the default browser. Node must be running."
+
+
+
+
+
+
+
+

Functions

+
+
+def open_home() +
+
+
+
+Source code +
def open_home():
+    try:
+        url = getclientapiserver.get_client_API_server()
+    except FileNotFoundError:
+        logger.error('Onionr seems to not be running (could not get api host)', terminal=True)
+    else:
+        url = 'http://%s/#%s' % (url, config.get('client.webpassword'))
+        logger.info('If Onionr does not open automatically, use this URL: ' + url, terminal=True)
+        webbrowser.open_new_tab(url)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/parser/arguments.html b/docs/html/onionr/onionrcommands/parser/arguments.html new file mode 100644 index 00000000..9c13282f --- /dev/null +++ b/docs/html/onionr/onionrcommands/parser/arguments.html @@ -0,0 +1,218 @@ + + + + + + +onionr.onionrcommands.parser.arguments API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.parser.arguments

+
+
+

Onionr - Private P2P Communication

+

Sets CLI arguments for Onionr

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Sets CLI arguments for Onionr
+'''
+'''
+    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 typing import Callable
+from .. import onionrstatistics, version, daemonlaunch, keyadders, 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 resettor # commands to reset the tor data directory or transport keypair
+from .. import resetplugins # command to reinstall default plugins
+from .. import softreset # command to delete onionr blocks
+from .. import restartonionr # command to restart Onionr
+import onionrexceptions
+from onionrutils import importnewblocks # func to import new blocks
+from onionrplugins import onionrevents as events
+
+def get_arguments()->dict:
+    """This is a function because we need to be able to dynamically modify them with plugins"""
+    args = {
+        ('blacklist', 'blacklist-block', 'remove-block', 'removeblock', 'banblock', 'ban-block'): banblocks.ban_block,
+        ('details', 'info'): onionrstatistics.show_details,
+        ('stats', 'statistics'): onionrstatistics.show_stats,
+        ('version',): version.version,
+        ('start', 'daemon'): daemonlaunch.start,
+        ('stop', 'kill'): daemonlaunch.kill_daemon,
+        ('restart',): restartonionr.restart,
+        ('add-address', 'addaddress', 'addadder'): keyadders.add_address,
+        ('openhome', 'gui', 'openweb', 'open-home', 'open-web'): openwebinterface.open_home,
+        ('add-site', 'addsite', 'addhtml', 'add-html'): filecommands.add_html,
+        ('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,
+        ('changeid', 'change-id'): pubkeymanager.change_ID,
+        ('add-vanity', 'addvanity'): pubkeymanager.add_vanity,
+        ('resettor', 'reset-tor'): resettor.reset_tor,
+        ('resetplugins', 'reset-plugins'): resetplugins.reset,
+        ('reset-tor-node-transport',): resettor.reset_tor_key_pair,
+        ('soft-reset', 'softreset'): softreset.soft_reset
+
+    }
+    return args
+
+def get_help(arg: str) -> str:
+    """Returns the help info string from a given command"""
+    arguments = get_arguments()
+    # Iterate the command alias tuples
+    for argument in arguments:
+        # Return the help message if its found in a command alias tuple
+        if arg in argument: return arguments[argument].onionr_help
+    raise KeyError
+
+def get_func(argument: str) -> Callable:
+    """Returns the function for a given command argument"""
+    argument = argument.lower()
+    args = get_arguments()
+
+    for arg in args.keys(): # Iterate command alias sets
+        if argument in arg: # If our argument is in the current alias set, return the command function
+            return args[arg]
+    raise onionrexceptions.NotFound
+
+
+
+
+
+
+
+

Functions

+
+
+def get_arguments() +
+
+

This is a function because we need to be able to dynamically modify them with plugins

+
+Source code +
def get_arguments()->dict:
+    """This is a function because we need to be able to dynamically modify them with plugins"""
+    args = {
+        ('blacklist', 'blacklist-block', 'remove-block', 'removeblock', 'banblock', 'ban-block'): banblocks.ban_block,
+        ('details', 'info'): onionrstatistics.show_details,
+        ('stats', 'statistics'): onionrstatistics.show_stats,
+        ('version',): version.version,
+        ('start', 'daemon'): daemonlaunch.start,
+        ('stop', 'kill'): daemonlaunch.kill_daemon,
+        ('restart',): restartonionr.restart,
+        ('add-address', 'addaddress', 'addadder'): keyadders.add_address,
+        ('openhome', 'gui', 'openweb', 'open-home', 'open-web'): openwebinterface.open_home,
+        ('add-site', 'addsite', 'addhtml', 'add-html'): filecommands.add_html,
+        ('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,
+        ('changeid', 'change-id'): pubkeymanager.change_ID,
+        ('add-vanity', 'addvanity'): pubkeymanager.add_vanity,
+        ('resettor', 'reset-tor'): resettor.reset_tor,
+        ('resetplugins', 'reset-plugins'): resetplugins.reset,
+        ('reset-tor-node-transport',): resettor.reset_tor_key_pair,
+        ('soft-reset', 'softreset'): softreset.soft_reset
+
+    }
+    return args
+
+
+
+def get_func(argument) +
+
+

Returns the function for a given command argument

+
+Source code +
def get_func(argument: str) -> Callable:
+    """Returns the function for a given command argument"""
+    argument = argument.lower()
+    args = get_arguments()
+
+    for arg in args.keys(): # Iterate command alias sets
+        if argument in arg: # If our argument is in the current alias set, return the command function
+            return args[arg]
+    raise onionrexceptions.NotFound
+
+
+
+def get_help(arg) +
+
+

Returns the help info string from a given command

+
+Source code +
def get_help(arg: str) -> str:
+    """Returns the help info string from a given command"""
+    arguments = get_arguments()
+    # Iterate the command alias tuples
+    for argument in arguments:
+        # Return the help message if its found in a command alias tuple
+        if arg in argument: return arguments[argument].onionr_help
+    raise KeyError
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/parser/index.html b/docs/html/onionr/onionrcommands/parser/index.html new file mode 100644 index 00000000..698ef6bf --- /dev/null +++ b/docs/html/onionr/onionrcommands/parser/index.html @@ -0,0 +1,287 @@ + + + + + + +onionr.onionrcommands.parser API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.parser

+
+
+

Onionr - Private P2P Communication

+

This module loads in the Onionr arguments and their help messages

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This module loads in the Onionr arguments and their help messages
+'''
+'''
+    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/>.
+'''
+import sys
+from etc import onionrvalues
+import logger, onionrexceptions
+import onionrplugins
+from onionrplugins import onionrpluginapi
+from . import arguments, recommend
+
+plugin_command = lambda cmd: 'on_%s_cmd' % (cmd,)
+
+def register_plugin_commands(cmd)->bool:
+    plugin_cmd = plugin_command(cmd)
+    for pl in onionrplugins.get_enabled_plugins():
+        pl = onionrplugins.get_plugin(pl)
+        if hasattr(pl, plugin_cmd):
+            getattr(pl, plugin_cmd)(onionrpluginapi.PluginAPI)
+            return True
+    return False
+
+def register():
+    """Registers commands and handles help command processing"""
+    def get_help_message(cmd: str, default: str = 'No help available for this command'):
+        """Return help message for a given command, supports plugin commands"""
+        pl_cmd = plugin_command(cmd)
+        for pl in onionrplugins.get_enabled_plugins():
+            pl = onionrplugins.get_plugin(pl)
+            if hasattr(pl, pl_cmd):
+                try:
+                    return getattr(pl, pl_cmd).onionr_help
+                except AttributeError:
+                    pass
+        
+        for i in arguments.get_arguments():
+            for alias in i:
+                try:
+                    return arguments.get_help(cmd)
+                except AttributeError:
+                    pass
+        return default # Return the help string
+
+    PROGRAM_NAME = "onionr"
+
+    # Get the command
+    try:
+        cmd = sys.argv[1]
+    except IndexError:
+        logger.debug("Detected Onionr run with no commands specified")
+        return
+
+    is_help_cmd = False
+    if cmd.replace('--', '').lower() == 'help': is_help_cmd = True
+
+    try:
+        arguments.get_func(cmd)()
+    except onionrexceptions.NotFound:
+        if not register_plugin_commands(cmd) and not is_help_cmd:
+            recommend.recommend()
+            sys.exit(3)
+    
+    if is_help_cmd:
+        try:
+            sys.argv[2]
+        except IndexError:
+            for i in arguments.get_arguments():
+                logger.info('%s <%s>: %s' % (PROGRAM_NAME, '/'.join(i), get_help_message(i[0])), terminal=True)
+            for pl in onionrplugins.get_enabled_plugins():
+                pl = onionrplugins.get_plugin(pl)
+                if hasattr(pl, 'ONIONR_COMMANDS'):
+                    print('')
+                    try:
+                        logger.info('%s commands:' % (pl.plugin_name,), terminal=True)
+                    except AttributeError:
+                        logger.info('%s commands:' % (pl.__name__,), terminal=True)
+                    for plugin_cmd in pl.ONIONR_COMMANDS:
+                        logger.info('%s %s: %s' % (PROGRAM_NAME, plugin_cmd, get_help_message(plugin_cmd)), terminal=True)
+                    print('')
+        else:
+            try:
+                logger.info('%s %s: %s' % (PROGRAM_NAME, sys.argv[2], get_help_message(sys.argv[2])), terminal=True)
+            except KeyError:
+                logger.error('%s: command does not exist.' % [sys.argv[2]], terminal=True)
+                sys.exit(3)
+        return
+    
+
+
+
+

Sub-modules

+
+
onionr.onionrcommands.parser.arguments
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcommands.parser.recommend
+
+
+
+
+
+
+
+
+

Functions

+
+
+def plugin_command(cmd) +
+
+
+
+Source code +
plugin_command = lambda cmd: 'on_%s_cmd' % (cmd,)
+
+
+
+def register() +
+
+

Registers commands and handles help command processing

+
+Source code +
def register():
+    """Registers commands and handles help command processing"""
+    def get_help_message(cmd: str, default: str = 'No help available for this command'):
+        """Return help message for a given command, supports plugin commands"""
+        pl_cmd = plugin_command(cmd)
+        for pl in onionrplugins.get_enabled_plugins():
+            pl = onionrplugins.get_plugin(pl)
+            if hasattr(pl, pl_cmd):
+                try:
+                    return getattr(pl, pl_cmd).onionr_help
+                except AttributeError:
+                    pass
+        
+        for i in arguments.get_arguments():
+            for alias in i:
+                try:
+                    return arguments.get_help(cmd)
+                except AttributeError:
+                    pass
+        return default # Return the help string
+
+    PROGRAM_NAME = "onionr"
+
+    # Get the command
+    try:
+        cmd = sys.argv[1]
+    except IndexError:
+        logger.debug("Detected Onionr run with no commands specified")
+        return
+
+    is_help_cmd = False
+    if cmd.replace('--', '').lower() == 'help': is_help_cmd = True
+
+    try:
+        arguments.get_func(cmd)()
+    except onionrexceptions.NotFound:
+        if not register_plugin_commands(cmd) and not is_help_cmd:
+            recommend.recommend()
+            sys.exit(3)
+    
+    if is_help_cmd:
+        try:
+            sys.argv[2]
+        except IndexError:
+            for i in arguments.get_arguments():
+                logger.info('%s <%s>: %s' % (PROGRAM_NAME, '/'.join(i), get_help_message(i[0])), terminal=True)
+            for pl in onionrplugins.get_enabled_plugins():
+                pl = onionrplugins.get_plugin(pl)
+                if hasattr(pl, 'ONIONR_COMMANDS'):
+                    print('')
+                    try:
+                        logger.info('%s commands:' % (pl.plugin_name,), terminal=True)
+                    except AttributeError:
+                        logger.info('%s commands:' % (pl.__name__,), terminal=True)
+                    for plugin_cmd in pl.ONIONR_COMMANDS:
+                        logger.info('%s %s: %s' % (PROGRAM_NAME, plugin_cmd, get_help_message(plugin_cmd)), terminal=True)
+                    print('')
+        else:
+            try:
+                logger.info('%s %s: %s' % (PROGRAM_NAME, sys.argv[2], get_help_message(sys.argv[2])), terminal=True)
+            except KeyError:
+                logger.error('%s: command does not exist.' % [sys.argv[2]], terminal=True)
+                sys.exit(3)
+        return
+
+
+
+def register_plugin_commands(cmd) +
+
+
+
+Source code +
def register_plugin_commands(cmd)->bool:
+    plugin_cmd = plugin_command(cmd)
+    for pl in onionrplugins.get_enabled_plugins():
+        pl = onionrplugins.get_plugin(pl)
+        if hasattr(pl, plugin_cmd):
+            getattr(pl, plugin_cmd)(onionrpluginapi.PluginAPI)
+            return True
+    return False
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/parser/recommend.html b/docs/html/onionr/onionrcommands/parser/recommend.html new file mode 100644 index 00000000..89644939 --- /dev/null +++ b/docs/html/onionr/onionrcommands/parser/recommend.html @@ -0,0 +1,98 @@ + + + + + + +onionr.onionrcommands.parser.recommend API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.parser.recommend

+
+
+
+Source code +
import sys
+from difflib import SequenceMatcher
+import logger
+from . import arguments
+
+def recommend(print_default: bool = True):
+    tried = sys.argv[1]
+    args = arguments.get_arguments()
+    print_message = 'Command not found:'
+    for key in args.keys():
+        for word in key:
+            if SequenceMatcher(None, tried, word).ratio() >= 0.75:
+                logger.warn('%s "%s", did you mean "%s"?' % (print_message, tried, word), terminal=True)
+                return
+    if print_default: logger.error('%s "%s"' % (print_message, tried), terminal=True)
+
+
+
+
+
+
+
+

Functions

+
+
+def recommend(print_default=True) +
+
+
+
+Source code +
def recommend(print_default: bool = True):
+    tried = sys.argv[1]
+    args = arguments.get_arguments()
+    print_message = 'Command not found:'
+    for key in args.keys():
+        for word in key:
+            if SequenceMatcher(None, tried, word).ratio() >= 0.75:
+                logger.warn('%s "%s", did you mean "%s"?' % (print_message, tried, word), terminal=True)
+                return
+    if print_default: logger.error('%s "%s"' % (print_message, tried), terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/pubkeymanager.html b/docs/html/onionr/onionrcommands/pubkeymanager.html new file mode 100644 index 00000000..06d36632 --- /dev/null +++ b/docs/html/onionr/onionrcommands/pubkeymanager.html @@ -0,0 +1,293 @@ + + + + + + +onionr.onionrcommands.pubkeymanager API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.pubkeymanager

+
+
+

Onionr - Private P2P Communication

+

This module defines user ID-related CLI commands

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This module defines user ID-related CLI commands
+'''
+'''
+    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/>.
+'''
+
+import sys, getpass
+
+import unpaddedbase32
+import vanityonionr
+import mnemonic
+
+import logger, onionrexceptions
+from onionrutils import stringvalidators, bytesconverter
+from onionrusers import onionrusers, contactmanager
+import config
+from coredb import keydb
+import keymanager, onionrcrypto
+from etc import onionrvalues
+
+DETERMINISTIC_REQUIREMENT = onionrvalues.PASSWORD_LENGTH
+def add_ID():
+    key_manager = keymanager.KeyManager()
+    try:
+        sys.argv[2]
+        if not sys.argv[2].lower() == 'true': raise ValueError
+    except (IndexError, ValueError) as e:
+        newID = key_manager.addKey()[0]
+    else:
+        logger.warn('Deterministic keys require random and long passphrases.', terminal=True)
+        logger.warn('If a good passphrase is not used, your key can be easily stolen.', terminal=True)
+        logger.warn('You should use a series of hard to guess words, see this for reference: https://www.xkcd.com/936/', terminal=True)
+        try:
+            pass1 = getpass.getpass(prompt='Enter at least %s characters: ' % (DETERMINISTIC_REQUIREMENT,))
+            pass2 = getpass.getpass(prompt='Confirm entry: ')
+        except KeyboardInterrupt:
+            sys.exit(42)
+        if onionrcrypto.cryptoutils.safe_compare(pass1, pass2):
+            try:
+                logger.info('Generating deterministic key. This can take a while.', terminal=True)
+                newID, privKey = onionrcrypto.generate_deterministic(pass1)
+            except onionrexceptions.PasswordStrengthError:
+                logger.error('Passphrase must use at least %s characters.' % (DETERMINISTIC_REQUIREMENT,), terminal=True)
+                sys.exit(1)
+        else:
+            logger.error('Passwords do not match.', terminal=True)
+            sys.exit(1)
+        try:
+            key_manager.addKey(pubKey=newID, 
+            privKey=privKey)
+        except ValueError:
+            logger.error('That ID is already available, you can change to it with the change-id command.', terminal=True)
+            return
+    logger.info('Added ID: %s' % (bytesconverter.bytes_to_str(newID),), terminal=True)
+
+add_ID.onionr_help = "If the first argument is true, Onionr will show a deterministic generation prompt. Otherwise it will generate & save a new random key pair."
+
+def change_ID():
+    key_manager = keymanager.KeyManager()
+    try:
+        key = sys.argv[2]
+        key = unpaddedbase32.repad(key.encode()).decode()
+    except IndexError:
+        logger.warn('Specify pubkey to use', terminal=True)
+    else:
+        if stringvalidators.validate_pub_key(key):
+            key_list = key_manager.getPubkeyList()
+            if key in key_list or key.replace('=', '') in key_list:
+                config.set('general.public_key', key)
+                config.save()
+                logger.info('Set active key to: %s' % (key,), terminal=True)
+                logger.info('Restart Onionr if it is running.', terminal=True)
+            else:
+                logger.warn('That key does not exist', terminal=True)
+        else:
+            logger.warn('Invalid key %s' % (key,), terminal=True)
+
+change_ID.onionr_help = "<pubkey>: Switches Onionr to use a different user ID key. You should immediately restart Onionr if it is running."
+
+def add_vanity():
+    key_manager = keymanager.KeyManager()
+    tell = lambda tell: logger.info(tell, terminal=True)
+    words = ''
+    m = mnemonic.Mnemonic('english')
+    length = len(sys.argv) - 2
+    if length == 0: return
+    for i in range(2, len(sys.argv)):
+        words += ' '
+        words += sys.argv[i]
+    try:
+        if length == 1:
+            tell('Finding vanity, this should only take a few moments.')
+        else:
+            tell('Finding vanity, this will probably take a really long time.')
+        try:
+            vanity = vanityonionr.find_multiprocess(words)
+        except ValueError:
+            logger.warn('Vanity words must be valid english bip39', terminal=True)
+        else:
+            b32_pub = unpaddedbase32.b32encode(vanity[0])
+            tell('Found vanity address:\n' + m.to_mnemonic(vanity[0]))
+            tell('Base32 Public key: %s' % (b32_pub.decode(),))
+            key_manager.addKey(b32_pub, unpaddedbase32.b32encode(vanity[1]))
+    except KeyboardInterrupt:
+        pass
+add_vanity.onionr_help = "<space separated bip32 words> - Generates and stores an Onionr vanity address (see https://github.com/trezor/python-mnemonic/blob/master/mnemonic/wordlist/english.txt)"
+
+
+
+
+
+
+
+

Functions

+
+
+def add_ID() +
+
+
+
+Source code +
def add_ID():
+    key_manager = keymanager.KeyManager()
+    try:
+        sys.argv[2]
+        if not sys.argv[2].lower() == 'true': raise ValueError
+    except (IndexError, ValueError) as e:
+        newID = key_manager.addKey()[0]
+    else:
+        logger.warn('Deterministic keys require random and long passphrases.', terminal=True)
+        logger.warn('If a good passphrase is not used, your key can be easily stolen.', terminal=True)
+        logger.warn('You should use a series of hard to guess words, see this for reference: https://www.xkcd.com/936/', terminal=True)
+        try:
+            pass1 = getpass.getpass(prompt='Enter at least %s characters: ' % (DETERMINISTIC_REQUIREMENT,))
+            pass2 = getpass.getpass(prompt='Confirm entry: ')
+        except KeyboardInterrupt:
+            sys.exit(42)
+        if onionrcrypto.cryptoutils.safe_compare(pass1, pass2):
+            try:
+                logger.info('Generating deterministic key. This can take a while.', terminal=True)
+                newID, privKey = onionrcrypto.generate_deterministic(pass1)
+            except onionrexceptions.PasswordStrengthError:
+                logger.error('Passphrase must use at least %s characters.' % (DETERMINISTIC_REQUIREMENT,), terminal=True)
+                sys.exit(1)
+        else:
+            logger.error('Passwords do not match.', terminal=True)
+            sys.exit(1)
+        try:
+            key_manager.addKey(pubKey=newID, 
+            privKey=privKey)
+        except ValueError:
+            logger.error('That ID is already available, you can change to it with the change-id command.', terminal=True)
+            return
+    logger.info('Added ID: %s' % (bytesconverter.bytes_to_str(newID),), terminal=True)
+
+
+
+def add_vanity() +
+
+
+
+Source code +
def add_vanity():
+    key_manager = keymanager.KeyManager()
+    tell = lambda tell: logger.info(tell, terminal=True)
+    words = ''
+    m = mnemonic.Mnemonic('english')
+    length = len(sys.argv) - 2
+    if length == 0: return
+    for i in range(2, len(sys.argv)):
+        words += ' '
+        words += sys.argv[i]
+    try:
+        if length == 1:
+            tell('Finding vanity, this should only take a few moments.')
+        else:
+            tell('Finding vanity, this will probably take a really long time.')
+        try:
+            vanity = vanityonionr.find_multiprocess(words)
+        except ValueError:
+            logger.warn('Vanity words must be valid english bip39', terminal=True)
+        else:
+            b32_pub = unpaddedbase32.b32encode(vanity[0])
+            tell('Found vanity address:\n' + m.to_mnemonic(vanity[0]))
+            tell('Base32 Public key: %s' % (b32_pub.decode(),))
+            key_manager.addKey(b32_pub, unpaddedbase32.b32encode(vanity[1]))
+    except KeyboardInterrupt:
+        pass
+
+
+
+def change_ID() +
+
+
+
+Source code +
def change_ID():
+    key_manager = keymanager.KeyManager()
+    try:
+        key = sys.argv[2]
+        key = unpaddedbase32.repad(key.encode()).decode()
+    except IndexError:
+        logger.warn('Specify pubkey to use', terminal=True)
+    else:
+        if stringvalidators.validate_pub_key(key):
+            key_list = key_manager.getPubkeyList()
+            if key in key_list or key.replace('=', '') in key_list:
+                config.set('general.public_key', key)
+                config.save()
+                logger.info('Set active key to: %s' % (key,), terminal=True)
+                logger.info('Restart Onionr if it is running.', terminal=True)
+            else:
+                logger.warn('That key does not exist', terminal=True)
+        else:
+            logger.warn('Invalid key %s' % (key,), terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/resetplugins.html b/docs/html/onionr/onionrcommands/resetplugins.html new file mode 100644 index 00000000..1b3f102b --- /dev/null +++ b/docs/html/onionr/onionrcommands/resetplugins.html @@ -0,0 +1,121 @@ + + + + + + +onionr.onionrcommands.resetplugins API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.resetplugins

+
+
+

Onionr - Private P2P Communication

+

Reset default plugins from source

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Reset default plugins from source
+'''
+'''
+    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/>.
+'''
+import os
+import shutil
+
+from utils import identifyhome
+from onionrsetup import setup_default_plugins
+import logger
+
+def reset():
+    """Reinstalls Onionr default plugins"""
+    home = identifyhome.identify_home()
+    plugin_dir = home + '/plugins/'
+    if not os.path.exists(home): return
+    if os.path.exists(plugin_dir): shutil.rmtree(plugin_dir)
+    
+    setup_default_plugins()
+    logger.info('Default plugins have been reset.', terminal=True)
+
+reset.onionr_help = "reinstalls default Onionr plugins (e.g. mail). Should be done after git pulls or plugin modification."
+
+
+
+
+
+
+
+

Functions

+
+
+def reset() +
+
+

Reinstalls Onionr default plugins

+
+Source code +
def reset():
+    """Reinstalls Onionr default plugins"""
+    home = identifyhome.identify_home()
+    plugin_dir = home + '/plugins/'
+    if not os.path.exists(home): return
+    if os.path.exists(plugin_dir): shutil.rmtree(plugin_dir)
+    
+    setup_default_plugins()
+    logger.info('Default plugins have been reset.', terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/resettor.html b/docs/html/onionr/onionrcommands/resettor.html new file mode 100644 index 00000000..408a3e5b --- /dev/null +++ b/docs/html/onionr/onionrcommands/resettor.html @@ -0,0 +1,131 @@ + + + + + + +onionr.onionrcommands.resettor API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.resettor

+
+
+

Onionr - Private P2P Communication

+

Command to delete the Tor data directory if its safe to do so

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Command to delete the Tor data directory if its safe to do so
+'''
+'''
+    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/>.
+'''
+import os, shutil
+import logger
+from onionrutils import localcommand
+from utils import identifyhome
+
+def __delete(directory):
+    tor_dir = '%s/%s/' % (identifyhome.identify_home(), directory)
+    if os.path.exists(tor_dir):
+        if localcommand.local_command('/ping') == 'pong!':
+            logger.warn('Cannot delete Tor data while Onionr is running', terminal=True)
+        else:
+            shutil.rmtree(tor_dir)
+            logger.info('Tor reset', terminal=True)
+
+def reset_tor():
+    __delete('tordata')
+
+reset_tor.onionr_help = "Deletes Onionr's Tor data directory. Only do this as a last resort if you have serious Tor issues."
+
+def reset_tor_key_pair():
+    __delete('hs')
+
+reset_tor_key_pair.onionr_help = "Delete's your Tor node address permanently. Note that through fingerprinting attackers may be able to know that your any new generated node address belongs to the same node as the deleted one."
+
+
+
+
+
+
+
+

Functions

+
+
+def reset_tor() +
+
+
+
+Source code +
def reset_tor():
+    __delete('tordata')
+
+
+
+def reset_tor_key_pair() +
+
+
+
+Source code +
def reset_tor_key_pair():
+    __delete('hs')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/restartonionr.html b/docs/html/onionr/onionrcommands/restartonionr.html new file mode 100644 index 00000000..a500c3f2 --- /dev/null +++ b/docs/html/onionr/onionrcommands/restartonionr.html @@ -0,0 +1,123 @@ + + + + + + +onionr.onionrcommands.restartonionr API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.restartonionr

+
+
+

Onionr - Private P2P Communication

+

Command to restart Onionr

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    Command to restart Onionr
+"""
+"""
+    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/>.
+"""
+import time
+import os
+import subprocess
+import platform
+
+from etc import onionrvalues
+from onionrutils import localcommand
+import logger
+
+from . import daemonlaunch
+
+SCRIPT_NAME = os.path.dirname(os.path.realpath(__file__)) + f'/../../{onionrvalues.SCRIPT_NAME}'
+
+def restart():
+    logger.info('Restarting Onionr', terminal=True)
+    daemonlaunch.kill_daemon()
+    while localcommand.local_command('ping', maxWait=8) == 'pong!':
+        time.sleep(0.3)
+    time.sleep(4)
+    subprocess.Popen([SCRIPT_NAME, 'start'])
+
+restart.onionr_help = 'Gracefully restart Onionr'
+
+
+
+
+
+
+
+

Functions

+
+
+def restart() +
+
+
+
+Source code +
def restart():
+    logger.info('Restarting Onionr', terminal=True)
+    daemonlaunch.kill_daemon()
+    while localcommand.local_command('ping', maxWait=8) == 'pong!':
+        time.sleep(0.3)
+    time.sleep(4)
+    subprocess.Popen([SCRIPT_NAME, 'start'])
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/softreset.html b/docs/html/onionr/onionrcommands/softreset.html new file mode 100644 index 00000000..df2034a8 --- /dev/null +++ b/docs/html/onionr/onionrcommands/softreset.html @@ -0,0 +1,131 @@ + + + + + + +onionr.onionrcommands.softreset API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.softreset

+
+
+

Onionr - Private P2P Communication

+

Command to soft-reset Onionr (deletes blocks)

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    Command to soft-reset Onionr (deletes 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/>.
+"""
+import os
+import shutil
+
+from onionrutils import localcommand
+from coredb import dbfiles
+import filepaths
+from onionrplugins import onionrevents
+import logger
+
+def _ignore_not_found_delete(path):
+    try:
+        os.remove(path)
+    except FileNotFoundError:
+        pass
+
+def soft_reset():
+    if localcommand.local_command('/ping') == 'pong!':
+        logger.warn('Cannot soft reset while Onionr is running', terminal=True)
+        return
+    path = filepaths.block_data_location
+    shutil.rmtree(path)
+    _ignore_not_found_delete(dbfiles.block_meta_db)
+    _ignore_not_found_delete(filepaths.upload_list)
+    onionrevents.event('softreset')
+    logger.info("Soft reset Onionr", terminal=True)
+
+soft_reset.onionr_help = "Deletes Onionr blocks and their associated metadata, except for any exported block files. Does NOT delete data on other nodes in the network."
+
+
+
+
+
+
+
+

Functions

+
+
+def soft_reset() +
+
+
+
+Source code +
def soft_reset():
+    if localcommand.local_command('/ping') == 'pong!':
+        logger.warn('Cannot soft reset while Onionr is running', terminal=True)
+        return
+    path = filepaths.block_data_location
+    shutil.rmtree(path)
+    _ignore_not_found_delete(dbfiles.block_meta_db)
+    _ignore_not_found_delete(filepaths.upload_list)
+    onionrevents.event('softreset')
+    logger.info("Soft reset Onionr", terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcommands/version.html b/docs/html/onionr/onionrcommands/version.html new file mode 100644 index 00000000..5bd9d6c1 --- /dev/null +++ b/docs/html/onionr/onionrcommands/version.html @@ -0,0 +1,109 @@ + + + + + + +onionr.onionrcommands.version API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcommands.version

+
+
+
+Source code +
import platform
+from utils import identifyhome
+from etc import onionrvalues
+import logger
+def version(verbosity = 5, function = logger.info):
+    '''
+        Displays the Onionr version
+    '''
+
+    function('Onionr v%s (%s) (API v%s)' % (onionrvalues.ONIONR_VERSION, platform.machine(), onionrvalues.API_VERSION), terminal=True)
+    if verbosity >= 1:
+        function(onionrvalues.ONIONR_TAGLINE, terminal=True)
+    if verbosity >= 2:
+        pf = platform.platform()
+        release = platform.release()
+        python_imp = platform.python_implementation()
+        python_version = platform.python_version()
+        function(f'{python_imp} {python_version} on {pf} {release}', terminal=True)
+        function('Onionr data dir: %s' % identifyhome.identify_home(), terminal=True)
+
+version.onionr_help = 'Shows environment details including Onionr version & data directory, OS and Python version'
+
+
+
+
+
+
+
+

Functions

+
+
+def version(verbosity=5, function=) +
+
+

Displays the Onionr version

+
+Source code +
def version(verbosity = 5, function = logger.info):
+    '''
+        Displays the Onionr version
+    '''
+
+    function('Onionr v%s (%s) (API v%s)' % (onionrvalues.ONIONR_VERSION, platform.machine(), onionrvalues.API_VERSION), terminal=True)
+    if verbosity >= 1:
+        function(onionrvalues.ONIONR_TAGLINE, terminal=True)
+    if verbosity >= 2:
+        pf = platform.platform()
+        release = platform.release()
+        python_imp = platform.python_implementation()
+        python_version = platform.python_version()
+        function(f'{python_imp} {python_version} on {pf} {release}', terminal=True)
+        function('Onionr data dir: %s' % identifyhome.identify_home(), terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/cryptoutils/index.html b/docs/html/onionr/onionrcrypto/cryptoutils/index.html new file mode 100644 index 00000000..8c2d47c2 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/cryptoutils/index.html @@ -0,0 +1,89 @@ + + + + + + +onionr.onionrcrypto.cryptoutils API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.cryptoutils

+
+
+
+Source code +
from . import safecompare, replayvalidation, randomshuffle, verifypow
+
+replay_validator = replayvalidation.replay_timestamp_validation
+random_shuffle = randomshuffle.random_shuffle
+safe_compare = safecompare.safe_compare
+verify_POW = verifypow.verify_POW
+
+
+
+

Sub-modules

+
+
onionr.onionrcrypto.cryptoutils.randomshuffle
+
+
+
+
onionr.onionrcrypto.cryptoutils.replayvalidation
+
+
+
+
onionr.onionrcrypto.cryptoutils.safecompare
+
+
+
+
onionr.onionrcrypto.cryptoutils.verifypow
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/cryptoutils/randomshuffle.html b/docs/html/onionr/onionrcrypto/cryptoutils/randomshuffle.html new file mode 100644 index 00000000..ffef2e48 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/cryptoutils/randomshuffle.html @@ -0,0 +1,98 @@ + + + + + + +onionr.onionrcrypto.cryptoutils.randomshuffle API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.cryptoutils.randomshuffle

+
+
+
+Source code +
import secrets
+def random_shuffle(theList):
+    myList = list(theList)
+    shuffledList = []
+    myListLength = len(myList) + 1
+    while myListLength > 0:
+        removed = secrets.randbelow(myListLength)
+        try:
+            shuffledList.append(myList.pop(removed))
+        except IndexError:
+            pass
+        myListLength = len(myList)
+    return shuffledList
+
+
+
+
+
+
+
+

Functions

+
+
+def random_shuffle(theList) +
+
+
+
+Source code +
def random_shuffle(theList):
+    myList = list(theList)
+    shuffledList = []
+    myListLength = len(myList) + 1
+    while myListLength > 0:
+        removed = secrets.randbelow(myListLength)
+        try:
+            shuffledList.append(myList.pop(removed))
+        except IndexError:
+            pass
+        myListLength = len(myList)
+    return shuffledList
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/cryptoutils/replayvalidation.html b/docs/html/onionr/onionrcrypto/cryptoutils/replayvalidation.html new file mode 100644 index 00000000..3db25e74 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/cryptoutils/replayvalidation.html @@ -0,0 +1,84 @@ + + + + + + +onionr.onionrcrypto.cryptoutils.replayvalidation API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.cryptoutils.replayvalidation

+
+
+
+Source code +
from onionrutils import epoch
+def replay_timestamp_validation(timestamp):
+    if epoch.get_epoch() - int(timestamp) > 2419200:
+        return False
+    else:
+        return True
+
+
+
+
+
+
+
+

Functions

+
+
+def replay_timestamp_validation(timestamp) +
+
+
+
+Source code +
def replay_timestamp_validation(timestamp):
+    if epoch.get_epoch() - int(timestamp) > 2419200:
+        return False
+    else:
+        return True
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/cryptoutils/safecompare.html b/docs/html/onionr/onionrcrypto/cryptoutils/safecompare.html new file mode 100644 index 00000000..0354dc8a --- /dev/null +++ b/docs/html/onionr/onionrcrypto/cryptoutils/safecompare.html @@ -0,0 +1,96 @@ + + + + + + +onionr.onionrcrypto.cryptoutils.safecompare API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.cryptoutils.safecompare

+
+
+
+Source code +
import hmac
+def safe_compare(one, two):
+    # Do encode here to avoid spawning core
+    try:
+        one = one.encode()
+    except AttributeError:
+        pass
+    try:
+        two = two.encode()
+    except AttributeError:
+        pass
+    return hmac.compare_digest(one, two)
+
+
+
+
+
+
+
+

Functions

+
+
+def safe_compare(one, two) +
+
+
+
+Source code +
def safe_compare(one, two):
+    # Do encode here to avoid spawning core
+    try:
+        one = one.encode()
+    except AttributeError:
+        pass
+    try:
+        two = two.encode()
+    except AttributeError:
+        pass
+    return hmac.compare_digest(one, two)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/cryptoutils/verifypow.html b/docs/html/onionr/onionrcrypto/cryptoutils/verifypow.html new file mode 100644 index 00000000..87378160 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/cryptoutils/verifypow.html @@ -0,0 +1,144 @@ + + + + + + +onionr.onionrcrypto.cryptoutils.verifypow API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.cryptoutils.verifypow

+
+
+
+Source code +
from .. import hashers
+import config, onionrproofs, logger
+import onionrexceptions
+def verify_POW(blockContent):
+    '''
+        Verifies the proof of work associated with a block
+    '''
+    retData = False
+
+    dataLen = len(blockContent)
+
+    try:
+        blockContent = blockContent.encode()
+    except AttributeError:
+        pass
+
+    blockHash = hashers.sha3_hash(blockContent)
+    try:
+        blockHash = blockHash.decode() # bytes on some versions for some reason
+    except AttributeError:
+        pass
+    
+    difficulty = onionrproofs.getDifficultyForNewBlock(blockContent)
+    
+    if difficulty < int(config.get('general.minimum_block_pow')):
+        difficulty = int(config.get('general.minimum_block_pow'))
+    mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
+    puzzle = mainHash[:difficulty]
+
+    if blockHash[:difficulty] == puzzle:
+        # logger.debug('Validated block pow')
+        retData = True
+    else:
+        logger.debug("Invalid token, bad proof")
+        raise onionrexceptions.InvalidProof('Proof for %s needs to be %s' % (blockHash, puzzle))
+
+    return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def verify_POW(blockContent) +
+
+

Verifies the proof of work associated with a block

+
+Source code +
def verify_POW(blockContent):
+    '''
+        Verifies the proof of work associated with a block
+    '''
+    retData = False
+
+    dataLen = len(blockContent)
+
+    try:
+        blockContent = blockContent.encode()
+    except AttributeError:
+        pass
+
+    blockHash = hashers.sha3_hash(blockContent)
+    try:
+        blockHash = blockHash.decode() # bytes on some versions for some reason
+    except AttributeError:
+        pass
+    
+    difficulty = onionrproofs.getDifficultyForNewBlock(blockContent)
+    
+    if difficulty < int(config.get('general.minimum_block_pow')):
+        difficulty = int(config.get('general.minimum_block_pow'))
+    mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
+    puzzle = mainHash[:difficulty]
+
+    if blockHash[:difficulty] == puzzle:
+        # logger.debug('Validated block pow')
+        retData = True
+    else:
+        logger.debug("Invalid token, bad proof")
+        raise onionrexceptions.InvalidProof('Proof for %s needs to be %s' % (blockHash, puzzle))
+
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/encryption/index.html b/docs/html/onionr/onionrcrypto/encryption/index.html new file mode 100644 index 00000000..addaccc4 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/encryption/index.html @@ -0,0 +1,168 @@ + + + + + + +onionr.onionrcrypto.encryption API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.encryption

+
+
+
+Source code +
import nacl.encoding, nacl.public, nacl.signing
+from .. import getourkeypair
+import unpaddedbase32
+from onionrutils import bytesconverter, stringvalidators
+pair = getourkeypair.get_keypair()
+our_pub_key = unpaddedbase32.repad(pair[0].encode())
+our_priv_key = unpaddedbase32.repad(pair[1].encode())
+
+def pub_key_encrypt(data, pubkey, encodedData=False):
+    '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)'''
+    pubkey = unpaddedbase32.repad(bytesconverter.str_to_bytes(pubkey))
+    retVal = ''
+    box = None
+    data = bytesconverter.str_to_bytes(data)
+    
+    pubkey = nacl.signing.VerifyKey(pubkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_public_key()
+
+    if encodedData:
+        encoding = nacl.encoding.Base64Encoder
+    else:
+        encoding = nacl.encoding.RawEncoder
+    
+    box = nacl.public.SealedBox(pubkey)
+    retVal = box.encrypt(data, encoder=encoding)
+
+    return retVal
+
+def pub_key_decrypt(data, pubkey='', privkey='', encodedData=False):
+    '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)'''
+    if pubkey != '':
+        pubkey = unpaddedbase32.repad(bytesconverter.str_to_bytes(pubkey))
+    decrypted = False
+    if encodedData:
+        encoding = nacl.encoding.Base64Encoder
+    else:
+        encoding = nacl.encoding.RawEncoder
+    if privkey == '':
+        privkey = our_priv_key
+    ownKey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key()
+
+    if stringvalidators.validate_pub_key(privkey):
+        privkey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key()
+        anonBox = nacl.public.SealedBox(privkey)
+    else:
+        anonBox = nacl.public.SealedBox(ownKey)
+    decrypted = anonBox.decrypt(data, encoder=encoding)
+    return decrypted
+
+
+
+
+
+
+
+

Functions

+
+
+def pub_key_decrypt(data, pubkey='', privkey='', encodedData=False) +
+
+

pubkey decrypt (Curve25519, taken from Ed25519 pubkey)

+
+Source code +
def pub_key_decrypt(data, pubkey='', privkey='', encodedData=False):
+    '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)'''
+    if pubkey != '':
+        pubkey = unpaddedbase32.repad(bytesconverter.str_to_bytes(pubkey))
+    decrypted = False
+    if encodedData:
+        encoding = nacl.encoding.Base64Encoder
+    else:
+        encoding = nacl.encoding.RawEncoder
+    if privkey == '':
+        privkey = our_priv_key
+    ownKey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key()
+
+    if stringvalidators.validate_pub_key(privkey):
+        privkey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key()
+        anonBox = nacl.public.SealedBox(privkey)
+    else:
+        anonBox = nacl.public.SealedBox(ownKey)
+    decrypted = anonBox.decrypt(data, encoder=encoding)
+    return decrypted
+
+
+
+def pub_key_encrypt(data, pubkey, encodedData=False) +
+
+

Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)

+
+Source code +
def pub_key_encrypt(data, pubkey, encodedData=False):
+    '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)'''
+    pubkey = unpaddedbase32.repad(bytesconverter.str_to_bytes(pubkey))
+    retVal = ''
+    box = None
+    data = bytesconverter.str_to_bytes(data)
+    
+    pubkey = nacl.signing.VerifyKey(pubkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_public_key()
+
+    if encodedData:
+        encoding = nacl.encoding.Base64Encoder
+    else:
+        encoding = nacl.encoding.RawEncoder
+    
+    box = nacl.public.SealedBox(pubkey)
+    retVal = box.encrypt(data, encoder=encoding)
+
+    return retVal
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/generate.html b/docs/html/onionr/onionrcrypto/generate.html new file mode 100644 index 00000000..ed8a76e2 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/generate.html @@ -0,0 +1,132 @@ + + + + + + +onionr.onionrcrypto.generate API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.generate

+
+
+
+Source code +
import nacl.signing, nacl.encoding, nacl.pwhash
+import onionrexceptions
+from onionrutils import bytesconverter
+from etc import onionrvalues
+def generate_pub_key():
+    '''Generate a Ed25519 public key pair, return tuple of base32encoded pubkey, privkey'''
+    private_key = nacl.signing.SigningKey.generate()
+    public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder())
+    return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode())
+
+def generate_deterministic(passphrase, bypassCheck=False):
+    '''Generate a Ed25519 public key pair from a password'''
+    passStrength = onionrvalues.PASSWORD_LENGTH
+    passphrase = bytesconverter.str_to_bytes(passphrase) # Convert to bytes if not already
+    # Validate passphrase length
+    if not bypassCheck:
+        if len(passphrase) < passStrength:
+            raise onionrexceptions.PasswordStrengthError("Passphase must be at least %s characters" % (passStrength,))
+    # KDF values
+    kdf = nacl.pwhash.argon2id.kdf
+    salt = b"U81Q7llrQcdTP0Ux" # Does not need to be unique or secret, but must be 16 bytes
+    ops = nacl.pwhash.argon2id.OPSLIMIT_SENSITIVE
+    mem = nacl.pwhash.argon2id.MEMLIMIT_SENSITIVE
+
+    key = kdf(32, passphrase, salt, opslimit=ops, memlimit=mem) # Generate seed for ed25519 key
+    key = nacl.signing.SigningKey(key)
+    return (key.verify_key.encode(nacl.encoding.Base32Encoder).decode(), key.encode(nacl.encoding.Base32Encoder).decode())
+
+
+
+
+
+
+
+

Functions

+
+
+def generate_deterministic(passphrase, bypassCheck=False) +
+
+

Generate a Ed25519 public key pair from a password

+
+Source code +
def generate_deterministic(passphrase, bypassCheck=False):
+    '''Generate a Ed25519 public key pair from a password'''
+    passStrength = onionrvalues.PASSWORD_LENGTH
+    passphrase = bytesconverter.str_to_bytes(passphrase) # Convert to bytes if not already
+    # Validate passphrase length
+    if not bypassCheck:
+        if len(passphrase) < passStrength:
+            raise onionrexceptions.PasswordStrengthError("Passphase must be at least %s characters" % (passStrength,))
+    # KDF values
+    kdf = nacl.pwhash.argon2id.kdf
+    salt = b"U81Q7llrQcdTP0Ux" # Does not need to be unique or secret, but must be 16 bytes
+    ops = nacl.pwhash.argon2id.OPSLIMIT_SENSITIVE
+    mem = nacl.pwhash.argon2id.MEMLIMIT_SENSITIVE
+
+    key = kdf(32, passphrase, salt, opslimit=ops, memlimit=mem) # Generate seed for ed25519 key
+    key = nacl.signing.SigningKey(key)
+    return (key.verify_key.encode(nacl.encoding.Base32Encoder).decode(), key.encode(nacl.encoding.Base32Encoder).decode())
+
+
+
+def generate_pub_key() +
+
+

Generate a Ed25519 public key pair, return tuple of base32encoded pubkey, privkey

+
+Source code +
def generate_pub_key():
+    '''Generate a Ed25519 public key pair, return tuple of base32encoded pubkey, privkey'''
+    private_key = nacl.signing.SigningKey.generate()
+    public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder())
+    return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode())
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/getourkeypair.html b/docs/html/onionr/onionrcrypto/getourkeypair.html new file mode 100644 index 00000000..407950e9 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/getourkeypair.html @@ -0,0 +1,125 @@ + + + + + + +onionr.onionrcrypto.getourkeypair API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.getourkeypair

+
+
+

Onionr - Private P2P Communication

+

returns our current active keypair

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    returns our current active keypair
+"""
+"""
+    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/>.
+"""
+import os
+import keymanager, config, filepaths
+from . import generate
+def get_keypair():
+    key_m = keymanager.KeyManager()
+    if os.path.exists(filepaths.keys_file):
+        if len(config.get('general.public_key', '')) > 0:
+            pubKey = config.get('general.public_key')
+        else:
+            pubKey = key_m.getPubkeyList()[0]
+        privKey = key_m.getPrivkey(pubKey)
+    else:
+        keys = generate.generate_pub_key()
+        pubKey = keys[0]
+        privKey = keys[1]
+        key_m.addKey(pubKey, privKey)
+    return (pubKey, privKey)
+
+
+
+
+
+
+
+

Functions

+
+
+def get_keypair() +
+
+
+
+Source code +
def get_keypair():
+    key_m = keymanager.KeyManager()
+    if os.path.exists(filepaths.keys_file):
+        if len(config.get('general.public_key', '')) > 0:
+            pubKey = config.get('general.public_key')
+        else:
+            pubKey = key_m.getPubkeyList()[0]
+        privKey = key_m.getPrivkey(pubKey)
+    else:
+        keys = generate.generate_pub_key()
+        pubKey = keys[0]
+        privKey = keys[1]
+        key_m.addKey(pubKey, privKey)
+    return (pubKey, privKey)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/hashers.html b/docs/html/onionr/onionrcrypto/hashers.html new file mode 100644 index 00000000..0271ad14 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/hashers.html @@ -0,0 +1,113 @@ + + + + + + +onionr.onionrcrypto.hashers API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.hashers

+
+
+
+Source code +
import hashlib, nacl.hash
+def sha3_hash(data):
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+    hasher = hashlib.sha3_256()
+    hasher.update(data)
+    return hasher.hexdigest()
+
+def blake2b_hash(data):
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+    return nacl.hash.blake2b(data)
+
+
+
+
+
+
+
+

Functions

+
+
+def blake2b_hash(data) +
+
+
+
+Source code +
def blake2b_hash(data):
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+    return nacl.hash.blake2b(data)
+
+
+
+def sha3_hash(data) +
+
+
+
+Source code +
def sha3_hash(data):
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+    hasher = hashlib.sha3_256()
+    hasher.update(data)
+    return hasher.hexdigest()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/index.html b/docs/html/onionr/onionrcrypto/index.html new file mode 100644 index 00000000..02f6115d --- /dev/null +++ b/docs/html/onionr/onionrcrypto/index.html @@ -0,0 +1,122 @@ + + + + + + +onionr.onionrcrypto API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto

+
+
+

Onionr - Private P2P Communication

+

This file handles Onionr's cryptography.

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file handles Onionr's cryptography.
+'''
+'''
+    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 generate, hashers, getourkeypair, signing, encryption, cryptoutils
+generate_deterministic = generate.generate_deterministic
+generate = generate.generate_pub_key
+
+keypair = getourkeypair.get_keypair()
+pub_key = keypair[0]
+priv_key = keypair[1]
+
+
+
+

Sub-modules

+
+
onionr.onionrcrypto.cryptoutils
+
+
+
+
onionr.onionrcrypto.encryption
+
+
+
+
onionr.onionrcrypto.generate
+
+
+
+
onionr.onionrcrypto.getourkeypair
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrcrypto.hashers
+
+
+
+
onionr.onionrcrypto.signing
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrcrypto/signing/index.html b/docs/html/onionr/onionrcrypto/signing/index.html new file mode 100644 index 00000000..e527efb3 --- /dev/null +++ b/docs/html/onionr/onionrcrypto/signing/index.html @@ -0,0 +1,176 @@ + + + + + + +onionr.onionrcrypto.signing API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrcrypto.signing

+
+
+
+Source code +
import base64, binascii
+
+import unpaddedbase32
+import nacl.encoding, nacl.signing, nacl.exceptions
+
+from onionrutils import bytesconverter
+from onionrutils import mnemonickeys
+import logger
+def ed_sign(data, key, encodeResult=False):
+    '''Ed25519 sign data'''
+    key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key))
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+    key = nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder)
+    retData = ''
+    if encodeResult:
+        retData = key.sign(data, encoder=nacl.encoding.Base64Encoder).signature.decode() # .encode() is not the same as nacl.encoding
+    else:
+        retData = key.sign(data).signature
+    return retData
+
+def ed_verify(data, key, sig, encodedData=True):
+    '''Verify signed data (combined in nacl) to an ed25519 key'''
+    key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key))
+    try:
+        key = nacl.signing.VerifyKey(key=key, encoder=nacl.encoding.Base32Encoder)
+    except nacl.exceptions.ValueError:
+        #logger.debug('Signature by unknown key (cannot reverse hash)')
+        return False
+    except binascii.Error:
+        logger.warn('Could not load key for verification, invalid padding')
+        return False
+    retData = False
+    sig = base64.b64decode(sig)
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+    if encodedData:
+        try:
+            retData = key.verify(data, sig) # .encode() is not the same as nacl.encoding
+        except nacl.exceptions.BadSignatureError:
+            pass
+    else:
+        try:
+            retData = key.verify(data, sig)
+        except nacl.exceptions.BadSignatureError:
+            pass
+    return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def ed_sign(data, key, encodeResult=False) +
+
+

Ed25519 sign data

+
+Source code +
def ed_sign(data, key, encodeResult=False):
+    '''Ed25519 sign data'''
+    key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key))
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+    key = nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder)
+    retData = ''
+    if encodeResult:
+        retData = key.sign(data, encoder=nacl.encoding.Base64Encoder).signature.decode() # .encode() is not the same as nacl.encoding
+    else:
+        retData = key.sign(data).signature
+    return retData
+
+
+
+def ed_verify(data, key, sig, encodedData=True) +
+
+

Verify signed data (combined in nacl) to an ed25519 key

+
+Source code +
def ed_verify(data, key, sig, encodedData=True):
+    '''Verify signed data (combined in nacl) to an ed25519 key'''
+    key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key))
+    try:
+        key = nacl.signing.VerifyKey(key=key, encoder=nacl.encoding.Base32Encoder)
+    except nacl.exceptions.ValueError:
+        #logger.debug('Signature by unknown key (cannot reverse hash)')
+        return False
+    except binascii.Error:
+        logger.warn('Could not load key for verification, invalid padding')
+        return False
+    retData = False
+    sig = base64.b64decode(sig)
+    try:
+        data = data.encode()
+    except AttributeError:
+        pass
+    if encodedData:
+        try:
+            retData = key.verify(data, sig) # .encode() is not the same as nacl.encoding
+        except nacl.exceptions.BadSignatureError:
+            pass
+    else:
+        try:
+            retData = key.verify(data, sig)
+        except nacl.exceptions.BadSignatureError:
+            pass
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrexceptions.html b/docs/html/onionr/onionrexceptions.html new file mode 100644 index 00000000..c2a24ac7 --- /dev/null +++ b/docs/html/onionr/onionrexceptions.html @@ -0,0 +1,650 @@ + + + + + + +onionr.onionrexceptions API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrexceptions

+
+
+

Onionr - Private P2P Communication

+

This file contains exceptions for onionr

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    This file contains exceptions for onionr
+"""
+"""
+    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/>.
+"""
+
+# general exceptions
+class NotFound(Exception):
+    pass
+class Unknown(Exception):
+    pass
+class Invalid(Exception):
+    pass
+
+# communicator exceptions
+class OnlinePeerNeeded(Exception):
+    pass
+
+# crypto exceptions
+class InvalidPubkey(Exception):
+    pass
+
+class KeyNotKnown(Exception):
+    pass
+
+class DecryptionError(Exception):
+    pass
+
+class PasswordStrengthError(Exception):
+    pass
+
+class SignatureError(Exception):
+    pass
+
+# block exceptions
+
+class ReplayAttack(Exception):
+    pass
+
+class DifficultyTooLarge(Exception):
+    pass
+
+class InvalidMetadata(Exception):
+    pass
+
+class BlacklistedBlock(Exception):
+    pass
+
+class DataExists(Exception):
+    pass
+
+class NoDataAvailable(Exception):
+    pass
+
+class InvalidHexHash(Exception):
+    """When a string is not a valid hex string of appropriate length for a hash value"""
+    pass
+
+class InvalidProof(Exception):
+    """When a proof is invalid or inadequate"""
+    pass
+
+# network level exceptions
+class MissingPort(Exception):
+    pass
+
+class InvalidAddress(Exception):
+    pass
+
+class InvalidAPIVersion(Exception):
+    pass
+
+class Timeout(Exception):
+    pass
+
+# file exceptions
+
+class DiskAllocationReached(Exception):
+    pass
+
+# onionrsocket exceptions
+
+class MissingAddress(Exception):
+    pass
+
+# Contact exceptions
+
+class ContactDeleted(Exception):
+    pass
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class BlacklistedBlock +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class BlacklistedBlock(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class ContactDeleted +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class ContactDeleted(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class DataExists +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class DataExists(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class DecryptionError +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class DecryptionError(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class DifficultyTooLarge +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class DifficultyTooLarge(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class DiskAllocationReached +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class DiskAllocationReached(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class Invalid +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class Invalid(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class InvalidAPIVersion +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class InvalidAPIVersion(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class InvalidAddress +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class InvalidAddress(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class InvalidHexHash +(*args, **kwargs) +
+
+

When a string is not a valid hex string of appropriate length for a hash value

+
+Source code +
class InvalidHexHash(Exception):
+    """When a string is not a valid hex string of appropriate length for a hash value"""
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class InvalidMetadata +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class InvalidMetadata(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class InvalidProof +(*args, **kwargs) +
+
+

When a proof is invalid or inadequate

+
+Source code +
class InvalidProof(Exception):
+    """When a proof is invalid or inadequate"""
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class InvalidPubkey +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class InvalidPubkey(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class KeyNotKnown +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class KeyNotKnown(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class MissingAddress +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class MissingAddress(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class MissingPort +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class MissingPort(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class NoDataAvailable +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class NoDataAvailable(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class NotFound +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class NotFound(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class OnlinePeerNeeded +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class OnlinePeerNeeded(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class PasswordStrengthError +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class PasswordStrengthError(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class ReplayAttack +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class ReplayAttack(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class SignatureError +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class SignatureError(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class Timeout +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class Timeout(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class Unknown +(*args, **kwargs) +
+
+

Common base class for all non-exit exceptions.

+
+Source code +
class Unknown(Exception):
+    pass
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrpeers/index.html b/docs/html/onionr/onionrpeers/index.html new file mode 100644 index 00000000..9e145929 --- /dev/null +++ b/docs/html/onionr/onionrpeers/index.html @@ -0,0 +1,82 @@ + + + + + + +onionr.onionrpeers API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrpeers

+
+
+
+Source code +
from . import scoresortedpeerlist, peercleanup, peerprofiles
+get_score_sorted_peer_list = scoresortedpeerlist.get_score_sorted_peer_list
+peer_cleanup = peercleanup.peer_cleanup
+PeerProfiles = peerprofiles.PeerProfiles
+
+
+
+

Sub-modules

+
+
onionr.onionrpeers.peercleanup
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrpeers.peerprofiles
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrpeers.scoresortedpeerlist
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrpeers/peercleanup.html b/docs/html/onionr/onionrpeers/peercleanup.html new file mode 100644 index 00000000..b281fad5 --- /dev/null +++ b/docs/html/onionr/onionrpeers/peercleanup.html @@ -0,0 +1,161 @@ + + + + + + +onionr.onionrpeers.peercleanup API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrpeers.peercleanup

+
+
+

Onionr - Private P2P Communication

+

Cleanup the peer database

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Cleanup the peer database
+'''
+'''
+    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/>.
+'''
+import sqlite3
+import logger
+from onionrutils import epoch
+from . import scoresortedpeerlist, peerprofiles
+from onionrblocks import onionrblacklist
+import config
+from coredb import keydb
+def peer_cleanup():
+    '''Removes peers who have been offline too long or score too low'''
+    logger.info('Cleaning peers...')
+    blacklist = onionrblacklist.OnionrBlackList()
+    adders = scoresortedpeerlist.get_score_sorted_peer_list()
+    adders.reverse()
+    
+    if len(adders) > 1:
+
+        min_score = int(config.get('peers.minimum_score', -100))
+        max_peers = int(config.get('peers.max_stored', 5000))
+
+        for address in adders:
+            # Remove peers that go below the negative score
+            if peerprofiles.PeerProfiles(address).score < min_score:
+                keydb.removekeys.remove_address(address)
+                try:
+                    if (int(epoch.get_epoch()) - int(keydb.transportinfo.get_address_info(address, 'lastConnect'))) >= 600:
+                        expireTime = 600
+                    else:
+                        expireTime = 86400
+                    blacklist.addToDB(address, dataType=1, expire=expireTime)
+                except sqlite3.IntegrityError: #TODO just make sure its not a unique constraint issue
+                    pass
+                except ValueError:
+                    pass
+                logger.warn('Removed address ' + address + '.')
+
+    # Unban probably not malicious peers TODO improve
+    blacklist.deleteExpired(dataType=1)
+
+
+
+
+
+
+
+

Functions

+
+
+def peer_cleanup() +
+
+

Removes peers who have been offline too long or score too low

+
+Source code +
def peer_cleanup():
+    '''Removes peers who have been offline too long or score too low'''
+    logger.info('Cleaning peers...')
+    blacklist = onionrblacklist.OnionrBlackList()
+    adders = scoresortedpeerlist.get_score_sorted_peer_list()
+    adders.reverse()
+    
+    if len(adders) > 1:
+
+        min_score = int(config.get('peers.minimum_score', -100))
+        max_peers = int(config.get('peers.max_stored', 5000))
+
+        for address in adders:
+            # Remove peers that go below the negative score
+            if peerprofiles.PeerProfiles(address).score < min_score:
+                keydb.removekeys.remove_address(address)
+                try:
+                    if (int(epoch.get_epoch()) - int(keydb.transportinfo.get_address_info(address, 'lastConnect'))) >= 600:
+                        expireTime = 600
+                    else:
+                        expireTime = 86400
+                    blacklist.addToDB(address, dataType=1, expire=expireTime)
+                except sqlite3.IntegrityError: #TODO just make sure its not a unique constraint issue
+                    pass
+                except ValueError:
+                    pass
+                logger.warn('Removed address ' + address + '.')
+
+    # Unban probably not malicious peers TODO improve
+    blacklist.deleteExpired(dataType=1)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrpeers/peerprofiles.html b/docs/html/onionr/onionrpeers/peerprofiles.html new file mode 100644 index 00000000..52016ea2 --- /dev/null +++ b/docs/html/onionr/onionrpeers/peerprofiles.html @@ -0,0 +1,297 @@ + + + + + + +onionr.onionrpeers.peerprofiles API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrpeers.peerprofiles

+
+
+

Onionr - Private P2P Communication

+

This file contains both the PeerProfiles class for network profiling of Onionr nodes

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file contains both the PeerProfiles class for network profiling of Onionr nodes
+'''
+'''
+    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 coredb import keydb
+from onionrutils import epoch
+from onionrutils import stringvalidators
+from onionrblocks import onionrblacklist
+import onionrexceptions
+
+UPDATE_DELAY = 300
+
+class PeerProfiles:
+    '''
+        PeerProfiles
+    '''
+    def __init__(self, address):
+        if not stringvalidators.validate_transport(address): raise onionrexceptions.InvalidAddress
+        self.address = address # node address
+        self.score = None
+        self.friendSigCount = 0
+        self.success = 0
+        self.failure = 0
+        self.connectTime = None
+
+        self.loadScore()
+        self.getConnectTime()
+
+        self.last_updated = {'connect_time': UPDATE_DELAY, 'score': UPDATE_DELAY} # Last time a given value was updated
+        
+        if not address in keydb.listkeys.list_adders() and not onionrblacklist.OnionrBlackList().inBlacklist(address):
+            keydb.addkeys.add_address(address)
+
+    def loadScore(self):
+        '''Load the node's score from the database'''
+        try:
+            self.success = int(keydb.transportinfo.get_address_info(self.address, 'success'))
+        except (TypeError, ValueError) as e:
+            self.success = 0
+        self.score = self.success
+    
+    def getConnectTime(self):
+        """set the connectTime variable for when we last connected to them, using the db value"""
+        try:
+            self.connectTime = int(keydb.transportinfo.get_address_info(self.address, 'lastConnect'))
+        except (KeyError, ValueError, TypeError) as e:
+            pass
+        else:
+            return self.connectTime
+    
+    def update_connect_time(self):
+        if epoch.get_epoch() - self.last_updated['connect_time'] >= UPDATE_DELAY:
+            self.last_updated['connect_time'] = epoch.get_epoch()
+            keydb.transportinfo.set_address_info(self.address, 'lastConnect', epoch.get_epoch())
+        
+    def saveScore(self):
+        '''Save the node's score to the database'''
+        if epoch.get_epoch() - self.last_updated['score'] >= UPDATE_DELAY:
+            self.last_updated['score'] = epoch.get_epoch()
+            keydb.transportinfo.set_address_info(self.address, 'success', self.score)
+        return
+
+    def addScore(self, toAdd):
+        '''Add to the peer's score (can add negative)'''
+        self.score += toAdd
+        self.saveScore()
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class PeerProfiles +(address) +
+
+

PeerProfiles

+
+Source code +
class PeerProfiles:
+    '''
+        PeerProfiles
+    '''
+    def __init__(self, address):
+        if not stringvalidators.validate_transport(address): raise onionrexceptions.InvalidAddress
+        self.address = address # node address
+        self.score = None
+        self.friendSigCount = 0
+        self.success = 0
+        self.failure = 0
+        self.connectTime = None
+
+        self.loadScore()
+        self.getConnectTime()
+
+        self.last_updated = {'connect_time': UPDATE_DELAY, 'score': UPDATE_DELAY} # Last time a given value was updated
+        
+        if not address in keydb.listkeys.list_adders() and not onionrblacklist.OnionrBlackList().inBlacklist(address):
+            keydb.addkeys.add_address(address)
+
+    def loadScore(self):
+        '''Load the node's score from the database'''
+        try:
+            self.success = int(keydb.transportinfo.get_address_info(self.address, 'success'))
+        except (TypeError, ValueError) as e:
+            self.success = 0
+        self.score = self.success
+    
+    def getConnectTime(self):
+        """set the connectTime variable for when we last connected to them, using the db value"""
+        try:
+            self.connectTime = int(keydb.transportinfo.get_address_info(self.address, 'lastConnect'))
+        except (KeyError, ValueError, TypeError) as e:
+            pass
+        else:
+            return self.connectTime
+    
+    def update_connect_time(self):
+        if epoch.get_epoch() - self.last_updated['connect_time'] >= UPDATE_DELAY:
+            self.last_updated['connect_time'] = epoch.get_epoch()
+            keydb.transportinfo.set_address_info(self.address, 'lastConnect', epoch.get_epoch())
+        
+    def saveScore(self):
+        '''Save the node's score to the database'''
+        if epoch.get_epoch() - self.last_updated['score'] >= UPDATE_DELAY:
+            self.last_updated['score'] = epoch.get_epoch()
+            keydb.transportinfo.set_address_info(self.address, 'success', self.score)
+        return
+
+    def addScore(self, toAdd):
+        '''Add to the peer's score (can add negative)'''
+        self.score += toAdd
+        self.saveScore()
+
+

Methods

+
+
+def addScore(self, toAdd) +
+
+

Add to the peer's score (can add negative)

+
+Source code +
def addScore(self, toAdd):
+    '''Add to the peer's score (can add negative)'''
+    self.score += toAdd
+    self.saveScore()
+
+
+
+def getConnectTime(self) +
+
+

set the connectTime variable for when we last connected to them, using the db value

+
+Source code +
def getConnectTime(self):
+    """set the connectTime variable for when we last connected to them, using the db value"""
+    try:
+        self.connectTime = int(keydb.transportinfo.get_address_info(self.address, 'lastConnect'))
+    except (KeyError, ValueError, TypeError) as e:
+        pass
+    else:
+        return self.connectTime
+
+
+
+def loadScore(self) +
+
+

Load the node's score from the database

+
+Source code +
def loadScore(self):
+    '''Load the node's score from the database'''
+    try:
+        self.success = int(keydb.transportinfo.get_address_info(self.address, 'success'))
+    except (TypeError, ValueError) as e:
+        self.success = 0
+    self.score = self.success
+
+
+
+def saveScore(self) +
+
+

Save the node's score to the database

+
+Source code +
def saveScore(self):
+    '''Save the node's score to the database'''
+    if epoch.get_epoch() - self.last_updated['score'] >= UPDATE_DELAY:
+        self.last_updated['score'] = epoch.get_epoch()
+        keydb.transportinfo.set_address_info(self.address, 'success', self.score)
+    return
+
+
+
+def update_connect_time(self) +
+
+
+
+Source code +
def update_connect_time(self):
+    if epoch.get_epoch() - self.last_updated['connect_time'] >= UPDATE_DELAY:
+        self.last_updated['connect_time'] = epoch.get_epoch()
+        keydb.transportinfo.set_address_info(self.address, 'lastConnect', epoch.get_epoch())
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrpeers/scoresortedpeerlist.html b/docs/html/onionr/onionrpeers/scoresortedpeerlist.html new file mode 100644 index 00000000..e7f1e675 --- /dev/null +++ b/docs/html/onionr/onionrpeers/scoresortedpeerlist.html @@ -0,0 +1,132 @@ + + + + + + +onionr.onionrpeers.scoresortedpeerlist API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrpeers.scoresortedpeerlist

+
+
+

Onionr - Private P2P Communication

+

Return a reliability score sorted list of peers

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Return a reliability score sorted list of peers
+'''
+'''
+    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 peerprofiles
+from coredb import keydb
+def get_score_sorted_peer_list():
+    peer_list = keydb.listkeys.list_adders()
+    peer_scores = {}
+    peer_times = {}
+
+    for address in peer_list:
+        # Load peer's profiles into a list
+        profile = peerprofiles.PeerProfiles(address)
+        peer_scores[address] = profile.score
+        if not isinstance(profile.connectTime, type(None)):
+            peer_times[address] = profile.connectTime
+        else:
+            peer_times[address] = 9000
+
+    # Sort peers by their score, greatest to least, and then last connected time
+    peer_list = sorted(peer_scores, key=peer_scores.get, reverse=True)
+    peer_list = sorted(peer_times, key=peer_times.get, reverse=True)
+    return peer_list
+
+
+
+
+
+
+
+

Functions

+
+
+def get_score_sorted_peer_list() +
+
+
+
+Source code +
def get_score_sorted_peer_list():
+    peer_list = keydb.listkeys.list_adders()
+    peer_scores = {}
+    peer_times = {}
+
+    for address in peer_list:
+        # Load peer's profiles into a list
+        profile = peerprofiles.PeerProfiles(address)
+        peer_scores[address] = profile.score
+        if not isinstance(profile.connectTime, type(None)):
+            peer_times[address] = profile.connectTime
+        else:
+            peer_times[address] = 9000
+
+    # Sort peers by their score, greatest to least, and then last connected time
+    peer_list = sorted(peer_scores, key=peer_scores.get, reverse=True)
+    peer_list = sorted(peer_times, key=peer_times.get, reverse=True)
+    return peer_list
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrplugins/index.html b/docs/html/onionr/onionrplugins/index.html new file mode 100644 index 00000000..4858d466 --- /dev/null +++ b/docs/html/onionr/onionrplugins/index.html @@ -0,0 +1,714 @@ + + + + + + +onionr.onionrplugins API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrplugins

+
+
+

Onionr - Private P2P Communication

+

This file deals with management of modules/plugins.

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file deals with management of modules/plugins.
+'''
+'''
+    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/>.
+'''
+import os, re, importlib
+
+from . import onionrevents as events
+import config, logger
+from utils import identifyhome
+
+# set data dir
+dataDir = identifyhome.identify_home()
+
+_pluginsfolder = dataDir + 'plugins/'
+_instances = dict()
+config.reload()
+
+def reload(onionr = None, stop_event = True):
+    '''
+        Reloads all the plugins
+    '''
+
+    check()
+
+    try:
+        enabled_plugins = get_enabled_plugins()
+
+        if stop_event is True:
+            logger.debug('Reloading all plugins...')
+        else:
+            logger.debug('Loading all plugins...')
+
+        if stop_event is True:
+            for plugin in enabled_plugins:
+                stop(plugin, onionr)
+
+        for plugin in enabled_plugins:
+            start(plugin, onionr)
+
+        return True
+    except:
+        logger.error('Failed to reload plugins.')
+
+    return False
+
+def enable(name, onionr = None, start_event = True):
+    '''
+        Enables a plugin
+    '''
+
+    check()
+
+    if exists(name):
+        enabled_plugins = get_enabled_plugins()
+        if not name in enabled_plugins:
+            try:
+                events.call(get_plugin(name), 'enable', onionr)
+            except ImportError as e: # Was getting import error on Gitlab CI test "data"
+                # NOTE: If you are experiencing issues with plugins not being enabled, it might be this resulting from an error in the module
+                # can happen inconsistently (especially between versions)
+                logger.debug('Failed to enable module; Import error: %s' % e)
+                return False
+            else:
+                enabled_plugins.append(name)
+                config.set('plugins.enabled', enabled_plugins, savefile=True)
+                
+                if start_event is True:
+                    start(name)
+                return True
+        else:
+            return False
+    else:
+        logger.error('Failed to enable plugin \"%s\", disabling plugin.' % name, terminal=True)
+        logger.debug('Plugins folder not found: %s' % get_plugins_folder(str(name).lower()))
+        disable(name)
+
+        return False
+
+
+def disable(name, onionr = None, stop_event = True):
+    '''
+        Disables a plugin
+    '''
+
+    check()
+
+    if is_enabled(name):
+        enabled_plugins = get_enabled_plugins()
+        enabled_plugins.remove(name)
+        config.set('plugins.enabled', enabled_plugins, True)
+
+    if exists(name):
+        events.call(get_plugin(name), 'disable', onionr)
+
+        if stop_event is True:
+            stop(name)
+
+def start(name, onionr = None):
+    '''
+        Starts the plugin
+    '''
+
+    check()
+
+    if exists(name):
+        try:
+            plugin = get_plugin(name)
+
+            if plugin is None:
+                raise Exception('Failed to import module.')
+            else:
+                events.call(plugin, 'start', onionr)
+
+            return plugin
+        except:
+            logger.error('Failed to start module \"%s\".' % name)
+    else:
+        logger.error('Failed to start nonexistant module \"%s\".' % name)
+
+    return None
+
+def stop(name, onionr = None):
+    '''
+        Stops the plugin
+    '''
+
+    check()
+
+    if exists(name):
+        try:
+            plugin = get_plugin(name)
+
+            if plugin is None:
+                raise Exception('Failed to import module.')
+            else:
+                events.call(plugin, 'stop', onionr)
+
+            return plugin
+        except:
+            logger.error('Failed to stop module \"%s\".' % name)
+    else:
+        logger.error('Failed to stop nonexistant module \"%s\".' % name)
+
+    return None
+
+# credit: https://stackoverflow.com/a/29589414
+def import_module_from_file(full_path_to_module):
+    """
+    Import a module given the full path/filename of the .py file
+
+    Python 3.4
+
+    """
+
+    module = None
+
+    # Get module name and path from full path
+    module_dir, module_file = os.path.split(full_path_to_module)
+    module_name, module_ext = os.path.splitext(module_file)
+
+    module_name = module_dir # Module name must be unique otherwise it will get written in other imports
+    
+    # Get module "spec" from filename
+    spec = importlib.util.spec_from_file_location(module_name,full_path_to_module)
+
+    module = spec.loader.load_module()
+
+    return module
+
+def get_plugin(name):
+    '''
+        Returns the instance of a module
+    '''
+
+    check()
+
+    if str(name).lower() in _instances:
+        return _instances[str(name).lower()]
+    else:
+        _instances[str(name).lower()] = import_module_from_file(get_plugins_folder(str(name).lower(), False) + 'main.py')
+        return get_plugin(name)
+
+def get_plugins():
+    '''
+        Returns a list of plugins (deprecated)
+    '''
+
+    return _instances
+
+def exists(name):
+    '''
+        Return value indicates whether or not the plugin exists
+    '''
+
+    return os.path.isdir(get_plugins_folder(str(name).lower()))
+
+def get_enabled_plugins():
+    '''
+        Returns a list of the enabled plugins
+    '''
+
+    check()
+
+    return list(config.get('plugins.enabled', list()))
+
+def is_enabled(name):
+    '''
+        Return value indicates whether or not the plugin is enabled
+    '''
+
+    return name in get_enabled_plugins()
+
+def get_plugins_folder(name = None, absolute = True):
+    '''
+        Returns the path to the plugins folder
+    '''
+
+    path = ''
+
+    if name is None:
+        path = _pluginsfolder
+    else:
+        # only allow alphanumeric characters
+        #path = _pluginsfolder + str(name.lower())
+        path = _pluginsfolder + re.sub('[^0-9a-zA-Z_]+', '', str(name).lower())
+
+    if absolute is True:
+        path = os.path.abspath(path)
+
+    return path + '/'
+
+def get_plugin_data_folder(name, absolute = True):
+    '''
+        Returns the location of a plugin's data folder
+    '''
+
+    return get_plugins_folder(name, absolute)
+
+def check():
+    '''
+        Checks to make sure files exist
+    '''
+
+    if not config.is_set('plugins'):
+        logger.debug('Generating plugin configuration data...')
+        config.set('plugins', {'enabled': []}, True)
+
+    if not os.path.exists(os.path.dirname(get_plugins_folder())):
+        logger.debug('Generating plugin data folder...')
+        try:
+            os.makedirs(os.path.dirname(get_plugins_folder()))
+        except FileExistsError:
+            pass
+    return
+
+
+
+

Sub-modules

+
+
onionr.onionrplugins.onionrevents
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrplugins.onionrpluginapi
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+

Functions

+
+
+def check() +
+
+

Checks to make sure files exist

+
+Source code +
def check():
+    '''
+        Checks to make sure files exist
+    '''
+
+    if not config.is_set('plugins'):
+        logger.debug('Generating plugin configuration data...')
+        config.set('plugins', {'enabled': []}, True)
+
+    if not os.path.exists(os.path.dirname(get_plugins_folder())):
+        logger.debug('Generating plugin data folder...')
+        try:
+            os.makedirs(os.path.dirname(get_plugins_folder()))
+        except FileExistsError:
+            pass
+    return
+
+
+
+def disable(name, onionr=None, stop_event=True) +
+
+

Disables a plugin

+
+Source code +
def disable(name, onionr = None, stop_event = True):
+    '''
+        Disables a plugin
+    '''
+
+    check()
+
+    if is_enabled(name):
+        enabled_plugins = get_enabled_plugins()
+        enabled_plugins.remove(name)
+        config.set('plugins.enabled', enabled_plugins, True)
+
+    if exists(name):
+        events.call(get_plugin(name), 'disable', onionr)
+
+        if stop_event is True:
+            stop(name)
+
+
+
+def enable(name, onionr=None, start_event=True) +
+
+

Enables a plugin

+
+Source code +
def enable(name, onionr = None, start_event = True):
+    '''
+        Enables a plugin
+    '''
+
+    check()
+
+    if exists(name):
+        enabled_plugins = get_enabled_plugins()
+        if not name in enabled_plugins:
+            try:
+                events.call(get_plugin(name), 'enable', onionr)
+            except ImportError as e: # Was getting import error on Gitlab CI test "data"
+                # NOTE: If you are experiencing issues with plugins not being enabled, it might be this resulting from an error in the module
+                # can happen inconsistently (especially between versions)
+                logger.debug('Failed to enable module; Import error: %s' % e)
+                return False
+            else:
+                enabled_plugins.append(name)
+                config.set('plugins.enabled', enabled_plugins, savefile=True)
+                
+                if start_event is True:
+                    start(name)
+                return True
+        else:
+            return False
+    else:
+        logger.error('Failed to enable plugin \"%s\", disabling plugin.' % name, terminal=True)
+        logger.debug('Plugins folder not found: %s' % get_plugins_folder(str(name).lower()))
+        disable(name)
+
+        return False
+
+
+
+def exists(name) +
+
+

Return value indicates whether or not the plugin exists

+
+Source code +
def exists(name):
+    '''
+        Return value indicates whether or not the plugin exists
+    '''
+
+    return os.path.isdir(get_plugins_folder(str(name).lower()))
+
+
+
+def get_enabled_plugins() +
+
+

Returns a list of the enabled plugins

+
+Source code +
def get_enabled_plugins():
+    '''
+        Returns a list of the enabled plugins
+    '''
+
+    check()
+
+    return list(config.get('plugins.enabled', list()))
+
+
+
+def get_plugin(name) +
+
+

Returns the instance of a module

+
+Source code +
def get_plugin(name):
+    '''
+        Returns the instance of a module
+    '''
+
+    check()
+
+    if str(name).lower() in _instances:
+        return _instances[str(name).lower()]
+    else:
+        _instances[str(name).lower()] = import_module_from_file(get_plugins_folder(str(name).lower(), False) + 'main.py')
+        return get_plugin(name)
+
+
+
+def get_plugin_data_folder(name, absolute=True) +
+
+

Returns the location of a plugin's data folder

+
+Source code +
def get_plugin_data_folder(name, absolute = True):
+    '''
+        Returns the location of a plugin's data folder
+    '''
+
+    return get_plugins_folder(name, absolute)
+
+
+
+def get_plugins() +
+
+

Returns a list of plugins (deprecated)

+
+Source code +
def get_plugins():
+    '''
+        Returns a list of plugins (deprecated)
+    '''
+
+    return _instances
+
+
+
+def get_plugins_folder(name=None, absolute=True) +
+
+

Returns the path to the plugins folder

+
+Source code +
def get_plugins_folder(name = None, absolute = True):
+    '''
+        Returns the path to the plugins folder
+    '''
+
+    path = ''
+
+    if name is None:
+        path = _pluginsfolder
+    else:
+        # only allow alphanumeric characters
+        #path = _pluginsfolder + str(name.lower())
+        path = _pluginsfolder + re.sub('[^0-9a-zA-Z_]+', '', str(name).lower())
+
+    if absolute is True:
+        path = os.path.abspath(path)
+
+    return path + '/'
+
+
+
+def import_module_from_file(full_path_to_module) +
+
+

Import a module given the full path/filename of the .py file

+

Python 3.4

+
+Source code +
def import_module_from_file(full_path_to_module):
+    """
+    Import a module given the full path/filename of the .py file
+
+    Python 3.4
+
+    """
+
+    module = None
+
+    # Get module name and path from full path
+    module_dir, module_file = os.path.split(full_path_to_module)
+    module_name, module_ext = os.path.splitext(module_file)
+
+    module_name = module_dir # Module name must be unique otherwise it will get written in other imports
+    
+    # Get module "spec" from filename
+    spec = importlib.util.spec_from_file_location(module_name,full_path_to_module)
+
+    module = spec.loader.load_module()
+
+    return module
+
+
+
+def is_enabled(name) +
+
+

Return value indicates whether or not the plugin is enabled

+
+Source code +
def is_enabled(name):
+    '''
+        Return value indicates whether or not the plugin is enabled
+    '''
+
+    return name in get_enabled_plugins()
+
+
+
+def reload(onionr=None, stop_event=True) +
+
+

Reloads all the plugins

+
+Source code +
def reload(onionr = None, stop_event = True):
+    '''
+        Reloads all the plugins
+    '''
+
+    check()
+
+    try:
+        enabled_plugins = get_enabled_plugins()
+
+        if stop_event is True:
+            logger.debug('Reloading all plugins...')
+        else:
+            logger.debug('Loading all plugins...')
+
+        if stop_event is True:
+            for plugin in enabled_plugins:
+                stop(plugin, onionr)
+
+        for plugin in enabled_plugins:
+            start(plugin, onionr)
+
+        return True
+    except:
+        logger.error('Failed to reload plugins.')
+
+    return False
+
+
+
+def start(name, onionr=None) +
+
+

Starts the plugin

+
+Source code +
def start(name, onionr = None):
+    '''
+        Starts the plugin
+    '''
+
+    check()
+
+    if exists(name):
+        try:
+            plugin = get_plugin(name)
+
+            if plugin is None:
+                raise Exception('Failed to import module.')
+            else:
+                events.call(plugin, 'start', onionr)
+
+            return plugin
+        except:
+            logger.error('Failed to start module \"%s\".' % name)
+    else:
+        logger.error('Failed to start nonexistant module \"%s\".' % name)
+
+    return None
+
+
+
+def stop(name, onionr=None) +
+
+

Stops the plugin

+
+Source code +
def stop(name, onionr = None):
+    '''
+        Stops the plugin
+    '''
+
+    check()
+
+    if exists(name):
+        try:
+            plugin = get_plugin(name)
+
+            if plugin is None:
+                raise Exception('Failed to import module.')
+            else:
+                events.call(plugin, 'stop', onionr)
+
+            return plugin
+        except:
+            logger.error('Failed to stop module \"%s\".' % name)
+    else:
+        logger.error('Failed to stop nonexistant module \"%s\".' % name)
+
+    return None
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrplugins/onionrevents.html b/docs/html/onionr/onionrplugins/onionrevents.html new file mode 100644 index 00000000..0d885240 --- /dev/null +++ b/docs/html/onionr/onionrplugins/onionrevents.html @@ -0,0 +1,202 @@ + + + + + + +onionr.onionrplugins.onionrevents API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrplugins.onionrevents

+
+
+

Onionr - Private P2P Communication

+

This file deals with configuration management.

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file deals with configuration management.
+'''
+'''
+    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 threading import Thread
+
+import config, logger
+import onionrplugins as plugins
+from . import onionrpluginapi as pluginapi
+
+
+def get_pluginapi(data):
+    return pluginapi.SharedAPI(data)
+
+def __event_caller(event_name, data = {}):
+    '''
+        DO NOT call this function, this is for threading code only.
+        Instead, call onionrevents.event
+    '''
+    for plugin in plugins.get_enabled_plugins():
+        try:
+            call(plugins.get_plugin(plugin), event_name, data, get_pluginapi(data))
+        except ModuleNotFoundError as e:
+            logger.warn('Disabling nonexistant plugin "%s"...' % plugin, terminal=True)
+            plugins.disable(plugin, stop_event = False)
+        except Exception as e:
+            logger.warn('Event "%s" failed for plugin "%s".' % (event_name, plugin), terminal=True)
+            logger.debug(str(e), terminal=True)
+
+def event(event_name, data = {}, threaded = True):
+    '''
+        Calls an event on all plugins (if defined)
+    '''
+
+    if threaded:
+        thread = Thread(target = __event_caller, args = (event_name, data))
+        thread.start()
+        return thread
+    else:
+        __event_caller(event_name, data)
+
+def call(plugin, event_name, data = None, pluginapi = None):
+    '''
+        Calls an event on a plugin if one is defined
+    '''
+
+    if not plugin is None:
+        try:
+            attribute = 'on_' + str(event_name).lower()
+            if pluginapi is None:
+                pluginapi = get_pluginapi(data)
+            if hasattr(plugin, attribute):
+                return getattr(plugin, attribute)(pluginapi, data)
+
+            return True
+        except Exception as e:
+            #logger.error(str(e), terminal=True)
+            return False
+    else:
+        return True
+
+
+
+
+
+
+
+

Functions

+
+
+def call(plugin, event_name, data=None, pluginapi=None) +
+
+

Calls an event on a plugin if one is defined

+
+Source code +
def call(plugin, event_name, data = None, pluginapi = None):
+    '''
+        Calls an event on a plugin if one is defined
+    '''
+
+    if not plugin is None:
+        try:
+            attribute = 'on_' + str(event_name).lower()
+            if pluginapi is None:
+                pluginapi = get_pluginapi(data)
+            if hasattr(plugin, attribute):
+                return getattr(plugin, attribute)(pluginapi, data)
+
+            return True
+        except Exception as e:
+            #logger.error(str(e), terminal=True)
+            return False
+    else:
+        return True
+
+
+
+def event(event_name, data={}, threaded=True) +
+
+

Calls an event on all plugins (if defined)

+
+Source code +
def event(event_name, data = {}, threaded = True):
+    '''
+        Calls an event on all plugins (if defined)
+    '''
+
+    if threaded:
+        thread = Thread(target = __event_caller, args = (event_name, data))
+        thread.start()
+        return thread
+    else:
+        __event_caller(event_name, data)
+
+
+
+def get_pluginapi(data) +
+
+
+
+Source code +
def get_pluginapi(data):
+    return pluginapi.SharedAPI(data)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrplugins/onionrpluginapi.html b/docs/html/onionr/onionrplugins/onionrpluginapi.html new file mode 100644 index 00000000..5c23340d --- /dev/null +++ b/docs/html/onionr/onionrplugins/onionrpluginapi.html @@ -0,0 +1,435 @@ + + + + + + +onionr.onionrplugins.onionrpluginapi API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrplugins.onionrpluginapi

+
+
+

Onionr - Private P2P Communication

+

This file deals with the object that is passed with each event

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file deals with the object that is passed with each event
+'''
+'''
+    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/>.
+'''
+
+import onionrplugins, logger
+from onionrutils import localcommand
+from coredb import daemonqueue
+
+class PluginAPI:
+    def __init__(self, pluginapi):
+        self.pluginapi = pluginapi
+
+    def start(self, name):
+        onionrplugins.start(name)
+
+    def stop(self, name):
+        onionrplugins.stop(name)
+
+    def reload(self, name):
+        onionrplugins.reload(name)
+
+    def enable(self, name):
+        onionrplugins.enable(name)
+
+    def disable(self, name):
+        onionrplugins.disable(name)
+
+    def event(self, name, data = {}):
+        events.event(name, data = data)
+
+    def is_enabled(self, name):
+        return onionrplugins.is_enabled(name)
+
+    def get_enabled_plugins(self):
+        return onionrplugins.get_enabled()
+
+    def get_folder(self, name = None, absolute = True):
+        return onionrplugins.get_plugins_folder(name = name, absolute = absolute)
+
+    def get_data_folder(self, name, absolute = True):
+        return onionrplugins.get_plugin_data_folder(name, absolute = absolute)
+
+    def daemon_event(self, event, plugin = None):
+        return # later make local command like /client/?action=makeEvent&event=eventname&module=modulename
+
+class CommandAPI:
+    def __init__(self, pluginapi):
+        self.pluginapi = pluginapi
+
+    def call(self, name):
+        self.pluginapi.get_onionr().execute(name)
+
+class SharedAPI:
+    def __init__(self, data):
+        self.data = data
+        self.plugins = PluginAPI(self)
+
+    def get_data(self):
+        return self.data
+
+    def get_daemonapi(self):
+        return self.daemon
+
+    def get_pluginapi(self):
+        return self.plugins
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class CommandAPI +(pluginapi) +
+
+
+
+Source code +
class CommandAPI:
+    def __init__(self, pluginapi):
+        self.pluginapi = pluginapi
+
+    def call(self, name):
+        self.pluginapi.get_onionr().execute(name)
+
+

Methods

+
+
+def call(self, name) +
+
+
+
+Source code +
def call(self, name):
+    self.pluginapi.get_onionr().execute(name)
+
+
+
+
+
+class PluginAPI +(pluginapi) +
+
+
+
+Source code +
class PluginAPI:
+    def __init__(self, pluginapi):
+        self.pluginapi = pluginapi
+
+    def start(self, name):
+        onionrplugins.start(name)
+
+    def stop(self, name):
+        onionrplugins.stop(name)
+
+    def reload(self, name):
+        onionrplugins.reload(name)
+
+    def enable(self, name):
+        onionrplugins.enable(name)
+
+    def disable(self, name):
+        onionrplugins.disable(name)
+
+    def event(self, name, data = {}):
+        events.event(name, data = data)
+
+    def is_enabled(self, name):
+        return onionrplugins.is_enabled(name)
+
+    def get_enabled_plugins(self):
+        return onionrplugins.get_enabled()
+
+    def get_folder(self, name = None, absolute = True):
+        return onionrplugins.get_plugins_folder(name = name, absolute = absolute)
+
+    def get_data_folder(self, name, absolute = True):
+        return onionrplugins.get_plugin_data_folder(name, absolute = absolute)
+
+    def daemon_event(self, event, plugin = None):
+        return # later make local command like /client/?action=makeEvent&event=eventname&module=modulename
+
+

Methods

+
+
+def daemon_event(self, event, plugin=None) +
+
+
+
+Source code +
def daemon_event(self, event, plugin = None):
+    return # later make local command like /client/?action=makeEvent&event=eventname&module=modulename
+
+
+
+def disable(self, name) +
+
+
+
+Source code +
def disable(self, name):
+    onionrplugins.disable(name)
+
+
+
+def enable(self, name) +
+
+
+
+Source code +
def enable(self, name):
+    onionrplugins.enable(name)
+
+
+
+def event(self, name, data={}) +
+
+
+
+Source code +
def event(self, name, data = {}):
+    events.event(name, data = data)
+
+
+
+def get_data_folder(self, name, absolute=True) +
+
+
+
+Source code +
def get_data_folder(self, name, absolute = True):
+    return onionrplugins.get_plugin_data_folder(name, absolute = absolute)
+
+
+
+def get_enabled_plugins(self) +
+
+
+
+Source code +
def get_enabled_plugins(self):
+    return onionrplugins.get_enabled()
+
+
+
+def get_folder(self, name=None, absolute=True) +
+
+
+
+Source code +
def get_folder(self, name = None, absolute = True):
+    return onionrplugins.get_plugins_folder(name = name, absolute = absolute)
+
+
+
+def is_enabled(self, name) +
+
+
+
+Source code +
def is_enabled(self, name):
+    return onionrplugins.is_enabled(name)
+
+
+
+def reload(self, name) +
+
+
+
+Source code +
def reload(self, name):
+    onionrplugins.reload(name)
+
+
+
+def start(self, name) +
+
+
+
+Source code +
def start(self, name):
+    onionrplugins.start(name)
+
+
+
+def stop(self, name) +
+
+
+
+Source code +
def stop(self, name):
+    onionrplugins.stop(name)
+
+
+
+
+
+class SharedAPI +(data) +
+
+
+
+Source code +
class SharedAPI:
+    def __init__(self, data):
+        self.data = data
+        self.plugins = PluginAPI(self)
+
+    def get_data(self):
+        return self.data
+
+    def get_daemonapi(self):
+        return self.daemon
+
+    def get_pluginapi(self):
+        return self.plugins
+
+

Methods

+
+
+def get_daemonapi(self) +
+
+
+
+Source code +
def get_daemonapi(self):
+    return self.daemon
+
+
+
+def get_data(self) +
+
+
+
+Source code +
def get_data(self):
+    return self.data
+
+
+
+def get_pluginapi(self) +
+
+
+
+Source code +
def get_pluginapi(self):
+    return self.plugins
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrproofs/index.html b/docs/html/onionr/onionrproofs/index.html new file mode 100644 index 00000000..f171c101 --- /dev/null +++ b/docs/html/onionr/onionrproofs/index.html @@ -0,0 +1,847 @@ + + + + + + +onionr.onionrproofs API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrproofs

+
+
+

Onionr - Private P2P Communication

+

Proof of work module

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Proof of work module
+'''
+'''
+    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/>.
+'''
+import multiprocessing, time, math, threading, binascii, sys, json
+import nacl.encoding, nacl.hash, nacl.utils
+
+import config, logger
+from onionrblocks import onionrblockapi, storagecounter
+from onionrutils import bytesconverter
+from onionrcrypto import hashers
+config.reload()
+
+def getDifficultyModifier():
+    '''returns the difficulty modifier for block storage based 
+    on a variety of factors, currently only disk use.
+    '''
+    percentUse = storagecounter.StorageCounter().get_percent()
+    difficultyIncrease = math.floor(4 * percentUse) # difficulty increase is a step function
+
+    return difficultyIncrease
+
+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)) + getDifficultyModifier()
+
+    return totalDifficulty
+
+    return retData
+
+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
+
+class DataPOW:
+    def __init__(self, data, minDifficulty = 0, threadCount = 1):
+        self.data = data
+        self.threadCount = threadCount
+        self.difficulty = max(minDifficulty, getDifficultyForNewBlock(data))
+        self.rounds = 0
+        self.hashing = False
+        self.foundHash = False
+
+        try:
+            self.data = self.data.encode()
+        except AttributeError:
+            pass
+        
+        self.data = nacl.hash.blake2b(self.data)
+
+        logger.info('Computing POW (difficulty: %s)...' % self.difficulty)
+
+        self.mainHash = '0' * 70
+        self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]
+        
+        for i in range(max(1, threadCount)):
+            t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,))
+            t.start()
+
+    def pow(self, reporting = False):
+        startTime = math.floor(time.time())
+        self.hashing = True
+        self.reporting = reporting
+        iFound = False # if current thread is the one that found the answer
+        
+        while self.hashing:
+            rand = nacl.utils.random()
+            token = nacl.hash.blake2b(rand + self.data).decode()
+            self.rounds += 1
+            #print(token)
+            if self.puzzle == token[0:self.difficulty]:
+                self.hashing = False
+                iFound = True
+                break
+                
+        if iFound:
+            endTime = math.floor(time.time())
+            if self.reporting:
+                logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
+                logger.debug('Round count: %s' % (self.rounds,))
+            self.result = (token, rand)
+
+    def shutdown(self):
+        self.hashing = False
+        self.puzzle = ''
+
+    def changeDifficulty(self, newDiff):
+        self.difficulty = newDiff
+
+    def getResult(self):
+        '''
+            Returns the result then sets to false, useful to automatically clear the result
+        '''
+        
+        try:
+            retVal = self.result
+        except AttributeError:
+            retVal = False
+            
+        self.result = False
+        return retVal
+
+    def waitForResult(self):
+        '''
+            Returns the result only when it has been found, False if not running and not found
+        '''
+        result = False
+        try:
+            while True:
+                result = self.getResult()
+                if not self.hashing:
+                    break
+                else:
+                    time.sleep(1)
+        except KeyboardInterrupt:
+            self.shutdown()
+            logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
+        return result
+
+class POW:
+    def __init__(self, metadata, data, threadCount = 1, minDifficulty=0):
+        self.foundHash = False
+        self.difficulty = 0
+        self.data = data
+        self.metadata = metadata
+        self.threadCount = threadCount
+        self.hashing = False
+
+        json_metadata = json.dumps(metadata).encode()
+
+        try:
+            self.data = self.data.encode()
+        except AttributeError:
+            pass
+            
+        if minDifficulty > 0:
+            self.difficulty = minDifficulty
+        else:
+            # Calculate difficulty. Dumb for now, may use good algorithm in the future.
+            self.difficulty = getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data))
+            
+        logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))
+
+        self.mainHash = '0' * 64
+        self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]
+
+        for i in range(max(1, threadCount)):
+            t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,))
+            t.start()
+
+    def pow(self, reporting = False):
+        startTime = math.floor(time.time())
+        self.hashing = True
+        self.reporting = reporting
+        iFound = False # if current thread is the one that found the answer
+        nonce = int(binascii.hexlify(nacl.utils.random(64)), 16)
+        while self.hashing:
+            #token = nacl.hash.blake2b(rand + self.data).decode()
+            self.metadata['pow'] = nonce
+            payload = json.dumps(self.metadata).encode() + b'\n' + self.data
+            token = hashers.sha3_hash(payload)
+            try:
+                # on some versions, token is bytes
+                token = token.decode()
+            except AttributeError:
+                pass
+            if self.puzzle == token[0:self.difficulty]:
+                self.hashing = False
+                iFound = True
+                self.result = payload
+                break
+            nonce += 1
+                
+        if iFound:
+            endTime = math.floor(time.time())
+            if self.reporting:
+                logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
+
+    def shutdown(self):
+        self.hashing = False
+        self.puzzle = ''
+
+    def changeDifficulty(self, newDiff):
+        self.difficulty = newDiff
+
+    def getResult(self):
+        '''
+            Returns the result then sets to false, useful to automatically clear the result
+        '''
+        
+        try:
+            retVal = self.result
+        except AttributeError:
+            retVal = False
+            
+        self.result = False
+        return retVal
+
+    def waitForResult(self):
+        '''
+            Returns the result only when it has been found, False if not running and not found
+        '''
+        result = False
+        try:
+            while True:
+                result = self.getResult()
+                if not self.hashing:
+                    break
+                else:
+                    time.sleep(1)
+        except KeyboardInterrupt:
+            self.shutdown()
+            logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
+        return result
+
+
+
+

Sub-modules

+
+
onionr.onionrproofs.subprocesspow
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+

Functions

+
+
+def getDifficultyForNewBlock(data) +
+
+

Get difficulty for block. Accepts size in integer, Block instance, or str/bytes full block contents

+
+Source code +
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)) + getDifficultyModifier()
+
+    return totalDifficulty
+
+    return retData
+
+
+
+def getDifficultyModifier() +
+
+

returns the difficulty modifier for block storage based +on a variety of factors, currently only disk use.

+
+Source code +
def getDifficultyModifier():
+    '''returns the difficulty modifier for block storage based 
+    on a variety of factors, currently only disk use.
+    '''
+    percentUse = storagecounter.StorageCounter().get_percent()
+    difficultyIncrease = math.floor(4 * percentUse) # difficulty increase is a step function
+
+    return difficultyIncrease
+
+
+
+def getHashDifficulty(h) +
+
+

Return the amount of leading zeroes in a hex hash string (hexHash)

+
+Source code +
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

+
+Source code +
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
+
+
+
+
+
+

Classes

+
+
+class DataPOW +(data, minDifficulty=0, threadCount=1) +
+
+
+
+Source code +
class DataPOW:
+    def __init__(self, data, minDifficulty = 0, threadCount = 1):
+        self.data = data
+        self.threadCount = threadCount
+        self.difficulty = max(minDifficulty, getDifficultyForNewBlock(data))
+        self.rounds = 0
+        self.hashing = False
+        self.foundHash = False
+
+        try:
+            self.data = self.data.encode()
+        except AttributeError:
+            pass
+        
+        self.data = nacl.hash.blake2b(self.data)
+
+        logger.info('Computing POW (difficulty: %s)...' % self.difficulty)
+
+        self.mainHash = '0' * 70
+        self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]
+        
+        for i in range(max(1, threadCount)):
+            t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,))
+            t.start()
+
+    def pow(self, reporting = False):
+        startTime = math.floor(time.time())
+        self.hashing = True
+        self.reporting = reporting
+        iFound = False # if current thread is the one that found the answer
+        
+        while self.hashing:
+            rand = nacl.utils.random()
+            token = nacl.hash.blake2b(rand + self.data).decode()
+            self.rounds += 1
+            #print(token)
+            if self.puzzle == token[0:self.difficulty]:
+                self.hashing = False
+                iFound = True
+                break
+                
+        if iFound:
+            endTime = math.floor(time.time())
+            if self.reporting:
+                logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
+                logger.debug('Round count: %s' % (self.rounds,))
+            self.result = (token, rand)
+
+    def shutdown(self):
+        self.hashing = False
+        self.puzzle = ''
+
+    def changeDifficulty(self, newDiff):
+        self.difficulty = newDiff
+
+    def getResult(self):
+        '''
+            Returns the result then sets to false, useful to automatically clear the result
+        '''
+        
+        try:
+            retVal = self.result
+        except AttributeError:
+            retVal = False
+            
+        self.result = False
+        return retVal
+
+    def waitForResult(self):
+        '''
+            Returns the result only when it has been found, False if not running and not found
+        '''
+        result = False
+        try:
+            while True:
+                result = self.getResult()
+                if not self.hashing:
+                    break
+                else:
+                    time.sleep(1)
+        except KeyboardInterrupt:
+            self.shutdown()
+            logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
+        return result
+
+

Methods

+
+
+def changeDifficulty(self, newDiff) +
+
+
+
+Source code +
def changeDifficulty(self, newDiff):
+    self.difficulty = newDiff
+
+
+
+def getResult(self) +
+
+

Returns the result then sets to false, useful to automatically clear the result

+
+Source code +
def getResult(self):
+    '''
+        Returns the result then sets to false, useful to automatically clear the result
+    '''
+    
+    try:
+        retVal = self.result
+    except AttributeError:
+        retVal = False
+        
+    self.result = False
+    return retVal
+
+
+
+def pow(self, reporting=False) +
+
+
+
+Source code +
def pow(self, reporting = False):
+    startTime = math.floor(time.time())
+    self.hashing = True
+    self.reporting = reporting
+    iFound = False # if current thread is the one that found the answer
+    
+    while self.hashing:
+        rand = nacl.utils.random()
+        token = nacl.hash.blake2b(rand + self.data).decode()
+        self.rounds += 1
+        #print(token)
+        if self.puzzle == token[0:self.difficulty]:
+            self.hashing = False
+            iFound = True
+            break
+            
+    if iFound:
+        endTime = math.floor(time.time())
+        if self.reporting:
+            logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
+            logger.debug('Round count: %s' % (self.rounds,))
+        self.result = (token, rand)
+
+
+
+def shutdown(self) +
+
+
+
+Source code +
def shutdown(self):
+    self.hashing = False
+    self.puzzle = ''
+
+
+
+def waitForResult(self) +
+
+

Returns the result only when it has been found, False if not running and not found

+
+Source code +
def waitForResult(self):
+    '''
+        Returns the result only when it has been found, False if not running and not found
+    '''
+    result = False
+    try:
+        while True:
+            result = self.getResult()
+            if not self.hashing:
+                break
+            else:
+                time.sleep(1)
+    except KeyboardInterrupt:
+        self.shutdown()
+        logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
+    return result
+
+
+
+
+
+class POW +(metadata, data, threadCount=1, minDifficulty=0) +
+
+
+
+Source code +
class POW:
+    def __init__(self, metadata, data, threadCount = 1, minDifficulty=0):
+        self.foundHash = False
+        self.difficulty = 0
+        self.data = data
+        self.metadata = metadata
+        self.threadCount = threadCount
+        self.hashing = False
+
+        json_metadata = json.dumps(metadata).encode()
+
+        try:
+            self.data = self.data.encode()
+        except AttributeError:
+            pass
+            
+        if minDifficulty > 0:
+            self.difficulty = minDifficulty
+        else:
+            # Calculate difficulty. Dumb for now, may use good algorithm in the future.
+            self.difficulty = getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data))
+            
+        logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))
+
+        self.mainHash = '0' * 64
+        self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]
+
+        for i in range(max(1, threadCount)):
+            t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,))
+            t.start()
+
+    def pow(self, reporting = False):
+        startTime = math.floor(time.time())
+        self.hashing = True
+        self.reporting = reporting
+        iFound = False # if current thread is the one that found the answer
+        nonce = int(binascii.hexlify(nacl.utils.random(64)), 16)
+        while self.hashing:
+            #token = nacl.hash.blake2b(rand + self.data).decode()
+            self.metadata['pow'] = nonce
+            payload = json.dumps(self.metadata).encode() + b'\n' + self.data
+            token = hashers.sha3_hash(payload)
+            try:
+                # on some versions, token is bytes
+                token = token.decode()
+            except AttributeError:
+                pass
+            if self.puzzle == token[0:self.difficulty]:
+                self.hashing = False
+                iFound = True
+                self.result = payload
+                break
+            nonce += 1
+                
+        if iFound:
+            endTime = math.floor(time.time())
+            if self.reporting:
+                logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
+
+    def shutdown(self):
+        self.hashing = False
+        self.puzzle = ''
+
+    def changeDifficulty(self, newDiff):
+        self.difficulty = newDiff
+
+    def getResult(self):
+        '''
+            Returns the result then sets to false, useful to automatically clear the result
+        '''
+        
+        try:
+            retVal = self.result
+        except AttributeError:
+            retVal = False
+            
+        self.result = False
+        return retVal
+
+    def waitForResult(self):
+        '''
+            Returns the result only when it has been found, False if not running and not found
+        '''
+        result = False
+        try:
+            while True:
+                result = self.getResult()
+                if not self.hashing:
+                    break
+                else:
+                    time.sleep(1)
+        except KeyboardInterrupt:
+            self.shutdown()
+            logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
+        return result
+
+

Methods

+
+
+def changeDifficulty(self, newDiff) +
+
+
+
+Source code +
def changeDifficulty(self, newDiff):
+    self.difficulty = newDiff
+
+
+
+def getResult(self) +
+
+

Returns the result then sets to false, useful to automatically clear the result

+
+Source code +
def getResult(self):
+    '''
+        Returns the result then sets to false, useful to automatically clear the result
+    '''
+    
+    try:
+        retVal = self.result
+    except AttributeError:
+        retVal = False
+        
+    self.result = False
+    return retVal
+
+
+
+def pow(self, reporting=False) +
+
+
+
+Source code +
def pow(self, reporting = False):
+    startTime = math.floor(time.time())
+    self.hashing = True
+    self.reporting = reporting
+    iFound = False # if current thread is the one that found the answer
+    nonce = int(binascii.hexlify(nacl.utils.random(64)), 16)
+    while self.hashing:
+        #token = nacl.hash.blake2b(rand + self.data).decode()
+        self.metadata['pow'] = nonce
+        payload = json.dumps(self.metadata).encode() + b'\n' + self.data
+        token = hashers.sha3_hash(payload)
+        try:
+            # on some versions, token is bytes
+            token = token.decode()
+        except AttributeError:
+            pass
+        if self.puzzle == token[0:self.difficulty]:
+            self.hashing = False
+            iFound = True
+            self.result = payload
+            break
+        nonce += 1
+            
+    if iFound:
+        endTime = math.floor(time.time())
+        if self.reporting:
+            logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
+
+
+
+def shutdown(self) +
+
+
+
+Source code +
def shutdown(self):
+    self.hashing = False
+    self.puzzle = ''
+
+
+
+def waitForResult(self) +
+
+

Returns the result only when it has been found, False if not running and not found

+
+Source code +
def waitForResult(self):
+    '''
+        Returns the result only when it has been found, False if not running and not found
+    '''
+    result = False
+    try:
+        while True:
+            result = self.getResult()
+            if not self.hashing:
+                break
+            else:
+                time.sleep(1)
+    except KeyboardInterrupt:
+        self.shutdown()
+        logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
+    return result
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrproofs/subprocesspow.html b/docs/html/onionr/onionrproofs/subprocesspow.html new file mode 100644 index 00000000..02a90812 --- /dev/null +++ b/docs/html/onionr/onionrproofs/subprocesspow.html @@ -0,0 +1,339 @@ + + + + + + +onionr.onionrproofs.subprocesspow API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrproofs.subprocesspow

+
+
+

Onionr - Private P2P Communication

+

Multiprocess proof of work

+
+Source code +
#!/usr/bin/env python3
+'''
+    Onionr - Private P2P Communication
+
+    Multiprocess proof of work
+'''
+'''
+    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/>.
+'''
+
+import subprocess, os
+import multiprocessing, threading, time, json
+from multiprocessing import Pipe, Process
+from onionrblocks import onionrblockapi
+import config, onionrutils, logger, onionrproofs, onionrcrypto as crypto
+from onionrutils import bytesconverter
+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
+
+            Do to Python GIL multiprocessing or 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 because the pow token must be added
+        json_metadata = json.dumps(metadata).encode()
+
+        self.data = bytesconverter.str_to_bytes(data)
+        # Calculate difficulty. Dumb for now, may use good algorithm in the future.
+        self.difficulty = onionrproofs.getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data))
+        
+        logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))
+
+        self.mainHash = '0' * 64
+        self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]
+        self.shutdown = False
+        self.payload = None
+
+    def start(self):
+        # Create a new thread for each subprocess
+        for x in range(self.subproc_count):
+            threading.Thread(target=self._spawn_proc).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
+        parent_conn, child_conn = Pipe()
+        p = Process(target=self.do_pow, args=(child_conn,))
+        p.start()
+        p.join()
+        payload = None
+        try:
+            while True:
+                data = parent_conn.recv()
+                if len(data) >= 1:
+                    payload = data
+                    break
+        except KeyboardInterrupt:
+            pass
+        finally:
+            parent_conn.send('shutdown')
+            self.payload = payload
+
+    def do_pow(self, pipe):
+        nonce = -10000000 # Start nonce at negative 10 million so that the chosen nonce is likely to be small in length
+        nonceStart = nonce
+        data = self.data
+        metadata = self.metadata
+        puzzle = self.puzzle
+        difficulty = self.difficulty
+        while True:
+            # Break if shutdown received
+            if pipe.poll() and pipe.recv() == 'shutdown':
+                break
+            # Load nonce into block metadata
+            metadata['pow'] = 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 = crypto.hashers.sha3_hash(payload)
+            token = bytesconverter.bytes_to_str(token) # ensure token is string
+            if puzzle == token[0:difficulty]:
+                pipe.send(payload)
+                break
+            nonce += 1
+        
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class SubprocessPOW +(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

+

Do to Python GIL multiprocessing or use of external libraries is necessary to accelerate CPU bound tasks

+
+Source code +
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
+
+            Do to Python GIL multiprocessing or 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 because the pow token must be added
+        json_metadata = json.dumps(metadata).encode()
+
+        self.data = bytesconverter.str_to_bytes(data)
+        # Calculate difficulty. Dumb for now, may use good algorithm in the future.
+        self.difficulty = onionrproofs.getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data))
+        
+        logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))
+
+        self.mainHash = '0' * 64
+        self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]
+        self.shutdown = False
+        self.payload = None
+
+    def start(self):
+        # Create a new thread for each subprocess
+        for x in range(self.subproc_count):
+            threading.Thread(target=self._spawn_proc).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
+        parent_conn, child_conn = Pipe()
+        p = Process(target=self.do_pow, args=(child_conn,))
+        p.start()
+        p.join()
+        payload = None
+        try:
+            while True:
+                data = parent_conn.recv()
+                if len(data) >= 1:
+                    payload = data
+                    break
+        except KeyboardInterrupt:
+            pass
+        finally:
+            parent_conn.send('shutdown')
+            self.payload = payload
+
+    def do_pow(self, pipe):
+        nonce = -10000000 # Start nonce at negative 10 million so that the chosen nonce is likely to be small in length
+        nonceStart = nonce
+        data = self.data
+        metadata = self.metadata
+        puzzle = self.puzzle
+        difficulty = self.difficulty
+        while True:
+            # Break if shutdown received
+            if pipe.poll() and pipe.recv() == 'shutdown':
+                break
+            # Load nonce into block metadata
+            metadata['pow'] = 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 = crypto.hashers.sha3_hash(payload)
+            token = bytesconverter.bytes_to_str(token) # ensure token is string
+            if puzzle == token[0:difficulty]:
+                pipe.send(payload)
+                break
+            nonce += 1
+
+

Methods

+
+
+def do_pow(self, pipe) +
+
+
+
+Source code +
def do_pow(self, pipe):
+    nonce = -10000000 # Start nonce at negative 10 million so that the chosen nonce is likely to be small in length
+    nonceStart = nonce
+    data = self.data
+    metadata = self.metadata
+    puzzle = self.puzzle
+    difficulty = self.difficulty
+    while True:
+        # Break if shutdown received
+        if pipe.poll() and pipe.recv() == 'shutdown':
+            break
+        # Load nonce into block metadata
+        metadata['pow'] = 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 = crypto.hashers.sha3_hash(payload)
+        token = bytesconverter.bytes_to_str(token) # ensure token is string
+        if puzzle == token[0:difficulty]:
+            pipe.send(payload)
+            break
+        nonce += 1
+
+
+
+def start(self) +
+
+
+
+Source code +
def start(self):
+    # Create a new thread for each subprocess
+    for x in range(self.subproc_count):
+        threading.Thread(target=self._spawn_proc).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
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/bootstrapservice.html b/docs/html/onionr/onionrservices/bootstrapservice.html new file mode 100644 index 00000000..abb61ca1 --- /dev/null +++ b/docs/html/onionr/onionrservices/bootstrapservice.html @@ -0,0 +1,270 @@ + + + + + + +onionr.onionrservices.bootstrapservice API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.bootstrapservice

+
+
+

Onionr - Private P2P Communication

+

Bootstrap onion direct connections for the clients

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Bootstrap onion direct connections for the clients
+'''
+'''
+    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/>.
+'''
+import time, threading, uuid, os
+from gevent.pywsgi import WSGIServer, WSGIHandler
+from stem.control import Controller
+from flask import Flask, Response
+from netcontroller import get_open_port
+from . import httpheaders
+from onionrutils import stringvalidators, epoch
+import logger
+import config, onionrblocks, filepaths
+import onionrexceptions
+import deadsimplekv as simplekv
+from . import pool
+
+def __bootstrap_timeout(server: WSGIServer, timeout: int, signal_object):
+    time.sleep(timeout)
+    signal_object.timed_out = True
+    server.stop()
+
+def bootstrap_client_service(peer, comm_inst=None, bootstrap_timeout=300):
+    '''
+        Bootstrap client services
+    '''
+    if not stringvalidators.validate_pub_key(peer):
+        raise ValueError('Peer must be valid base32 ed25519 public key')
+    
+    connection_pool = None
+
+    # here we use a lambda for the timeout thread to set to true
+    timed_out = lambda: None
+    timed_out.timed_out = False
+
+    bootstrap_port = get_open_port()
+    bootstrap_app = Flask(__name__)
+    bootstrap_app.config['MAX_CONTENT_LENGTH'] = 1 * 1024
+
+    http_server = WSGIServer(('127.0.0.1', bootstrap_port), bootstrap_app, log=None)
+    try:
+        if comm_inst is None: raise ValueError
+    except (AttributeError, ValueError) as e:
+        pass
+    else:
+        comm_inst.service_greenlets.append(http_server)
+        connection_pool = comm_inst.shared_state.get(pool.ServicePool)
+    
+    bootstrap_address = ''
+    shutdown = False
+    bs_id = str(uuid.uuid4())
+    key_store = simplekv.DeadSimpleKV(filepaths.cached_storage)
+
+    @bootstrap_app.route('/ping')
+    def get_ping():
+        return "pong!"
+
+    @bootstrap_app.after_request
+    def afterReq(resp):
+        # Security headers
+        resp = httpheaders.set_default_onionr_http_headers(resp)
+        return resp
+
+    @bootstrap_app.route('/bs/<address>', methods=['POST'])
+    def get_bootstrap(address):
+        if stringvalidators.validate_transport(address + '.onion'):
+            # Set the bootstrap address then close the server
+            bootstrap_address = address + '.onion'
+            key_store.put(bs_id, bootstrap_address)
+            http_server.stop()
+            return Response("success")
+        else:
+            return Response("")
+
+    with Controller.from_port(port=config.get('tor.controlPort')) as controller:
+        if not connection_pool is None: connection_pool.bootstrap_pending.append(peer)
+        # Connect to the Tor process for Onionr
+        controller.authenticate(config.get('tor.controlpassword'))
+        # Create the v3 onion service
+        response = controller.create_ephemeral_hidden_service({80: bootstrap_port}, key_type = 'NEW', key_content = 'ED25519-V3', await_publication = True)
+        onionrblocks.insert(response.service_id, header='con', sign=True, encryptType='asym', 
+        asymPeer=peer, disableForward=True, expire=(epoch.get_epoch() + bootstrap_timeout))
+        
+        threading.Thread(target=__bootstrap_timeout, args=[http_server, bootstrap_timeout, timed_out], daemon=True).start()
+
+        # Run the bootstrap server
+        try:
+            http_server.serve_forever()
+        except TypeError:
+            pass
+        # This line reached when server is shutdown by being bootstrapped
+    # Add the address to the client pool
+    if not comm_inst is None:
+        connection_pool.bootstrap_pending.remove(peer)
+        if timed_out.timed_out:
+            logger.warn('Could not connect to %s due to timeout' % (peer,))
+            return None
+        comm_inst.direct_connection_clients[peer] = response.service_id
+
+    # Now that the bootstrap server has received a server, return the address
+    return key_store.get(bs_id)
+
+
+
+
+
+
+
+

Functions

+
+
+def bootstrap_client_service(peer, comm_inst=None, bootstrap_timeout=300) +
+
+

Bootstrap client services

+
+Source code +
def bootstrap_client_service(peer, comm_inst=None, bootstrap_timeout=300):
+    '''
+        Bootstrap client services
+    '''
+    if not stringvalidators.validate_pub_key(peer):
+        raise ValueError('Peer must be valid base32 ed25519 public key')
+    
+    connection_pool = None
+
+    # here we use a lambda for the timeout thread to set to true
+    timed_out = lambda: None
+    timed_out.timed_out = False
+
+    bootstrap_port = get_open_port()
+    bootstrap_app = Flask(__name__)
+    bootstrap_app.config['MAX_CONTENT_LENGTH'] = 1 * 1024
+
+    http_server = WSGIServer(('127.0.0.1', bootstrap_port), bootstrap_app, log=None)
+    try:
+        if comm_inst is None: raise ValueError
+    except (AttributeError, ValueError) as e:
+        pass
+    else:
+        comm_inst.service_greenlets.append(http_server)
+        connection_pool = comm_inst.shared_state.get(pool.ServicePool)
+    
+    bootstrap_address = ''
+    shutdown = False
+    bs_id = str(uuid.uuid4())
+    key_store = simplekv.DeadSimpleKV(filepaths.cached_storage)
+
+    @bootstrap_app.route('/ping')
+    def get_ping():
+        return "pong!"
+
+    @bootstrap_app.after_request
+    def afterReq(resp):
+        # Security headers
+        resp = httpheaders.set_default_onionr_http_headers(resp)
+        return resp
+
+    @bootstrap_app.route('/bs/<address>', methods=['POST'])
+    def get_bootstrap(address):
+        if stringvalidators.validate_transport(address + '.onion'):
+            # Set the bootstrap address then close the server
+            bootstrap_address = address + '.onion'
+            key_store.put(bs_id, bootstrap_address)
+            http_server.stop()
+            return Response("success")
+        else:
+            return Response("")
+
+    with Controller.from_port(port=config.get('tor.controlPort')) as controller:
+        if not connection_pool is None: connection_pool.bootstrap_pending.append(peer)
+        # Connect to the Tor process for Onionr
+        controller.authenticate(config.get('tor.controlpassword'))
+        # Create the v3 onion service
+        response = controller.create_ephemeral_hidden_service({80: bootstrap_port}, key_type = 'NEW', key_content = 'ED25519-V3', await_publication = True)
+        onionrblocks.insert(response.service_id, header='con', sign=True, encryptType='asym', 
+        asymPeer=peer, disableForward=True, expire=(epoch.get_epoch() + bootstrap_timeout))
+        
+        threading.Thread(target=__bootstrap_timeout, args=[http_server, bootstrap_timeout, timed_out], daemon=True).start()
+
+        # Run the bootstrap server
+        try:
+            http_server.serve_forever()
+        except TypeError:
+            pass
+        # This line reached when server is shutdown by being bootstrapped
+    # Add the address to the client pool
+    if not comm_inst is None:
+        connection_pool.bootstrap_pending.remove(peer)
+        if timed_out.timed_out:
+            logger.warn('Could not connect to %s due to timeout' % (peer,))
+            return None
+        comm_inst.direct_connection_clients[peer] = response.service_id
+
+    # Now that the bootstrap server has received a server, return the address
+    return key_store.get(bs_id)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/connectionserver.html b/docs/html/onionr/onionrservices/connectionserver.html new file mode 100644 index 00000000..98eeae93 --- /dev/null +++ b/docs/html/onionr/onionrservices/connectionserver.html @@ -0,0 +1,226 @@ + + + + + + +onionr.onionrservices.connectionserver API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.connectionserver

+
+
+

Onionr - Private P2P Communication

+

This module does the second part of the bootstrap block handshake and creates the API server

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This module does the second part of the bootstrap block handshake and creates the API server
+'''
+'''
+    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 gevent.pywsgi import WSGIServer
+from stem.control import Controller
+from flask import Flask
+import logger, httpapi
+import onionrexceptions, config, filepaths
+from netcontroller import get_open_port
+from httpapi import apiutils
+from onionrutils import stringvalidators, basicrequests, bytesconverter
+from . import httpheaders
+import deadsimplekv as simplekv
+
+class ConnectionServer:
+    def __init__(self, peer, address, comm_inst=None):
+
+        if not stringvalidators.validate_pub_key(peer):
+            raise ValueError('Peer must be valid base32 ed25519 public key')
+        
+        socks = config.get('tor.socksport') # Load config for Tor socks port for proxy
+        service_app = Flask(__name__) # Setup Flask app for server.
+        service_port = get_open_port()
+        service_ip = apiutils.setbindip.set_bind_IP()
+        http_server = WSGIServer(('127.0.0.1', service_port), service_app, log=None)
+        comm_inst.service_greenlets.append(http_server)
+        key_store = simplekv.DeadSimpleKV(filepaths.cached_storage)
+
+        # TODO define basic endpoints useful for direct connections like stats
+        
+        httpapi.load_plugin_blueprints(service_app, blueprint='direct_blueprint')
+
+        @service_app.route('/ping')
+        def get_ping():
+            return "pong!"
+        
+        @service_app.route('/close')
+        def shutdown_server():
+            comm_inst.service_greenlets.remove(http_server)
+            http_server.stop()
+            return Response('goodbye')
+
+        @service_app.after_request
+        def afterReq(resp):
+            # Security headers
+            resp = httpheaders.set_default_onionr_http_headers(resp)
+            return resp
+
+        with Controller.from_port(port=config.get('tor.controlPort')) as controller:
+            # Connect to the Tor process for Onionr
+            controller.authenticate(config.get('tor.controlpassword'))
+            # Create the v3 onion service for the peer to connect to
+            response = controller.create_ephemeral_hidden_service({80: service_port}, await_publication = True, key_type='NEW', key_content = 'ED25519-V3')
+
+            try:
+                for x in range(3):
+                    attempt = basicrequests.do_post_request('http://' + address + '/bs/' + response.service_id, port=socks)
+                    if attempt == 'success':
+                        break
+                else:
+                    raise ConnectionError
+            except ConnectionError:
+                # Re-raise
+                raise ConnectionError('Could not reach %s bootstrap address %s' % (peer, address))
+            else:
+                # If no connection error, create the service and save it to local global key store
+                peer = bytesconverter.bytes_to_str(peer)
+                key_store.put('dc-' + peer, response.service_id)
+                key_store.flush()
+                logger.info('hosting on %s with %s' % (response.service_id,  peer))
+                http_server.serve_forever()
+                http_server.stop()
+                key_store.delete('dc-' + peer)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class ConnectionServer +(peer, address, comm_inst=None) +
+
+
+
+Source code +
class ConnectionServer:
+    def __init__(self, peer, address, comm_inst=None):
+
+        if not stringvalidators.validate_pub_key(peer):
+            raise ValueError('Peer must be valid base32 ed25519 public key')
+        
+        socks = config.get('tor.socksport') # Load config for Tor socks port for proxy
+        service_app = Flask(__name__) # Setup Flask app for server.
+        service_port = get_open_port()
+        service_ip = apiutils.setbindip.set_bind_IP()
+        http_server = WSGIServer(('127.0.0.1', service_port), service_app, log=None)
+        comm_inst.service_greenlets.append(http_server)
+        key_store = simplekv.DeadSimpleKV(filepaths.cached_storage)
+
+        # TODO define basic endpoints useful for direct connections like stats
+        
+        httpapi.load_plugin_blueprints(service_app, blueprint='direct_blueprint')
+
+        @service_app.route('/ping')
+        def get_ping():
+            return "pong!"
+        
+        @service_app.route('/close')
+        def shutdown_server():
+            comm_inst.service_greenlets.remove(http_server)
+            http_server.stop()
+            return Response('goodbye')
+
+        @service_app.after_request
+        def afterReq(resp):
+            # Security headers
+            resp = httpheaders.set_default_onionr_http_headers(resp)
+            return resp
+
+        with Controller.from_port(port=config.get('tor.controlPort')) as controller:
+            # Connect to the Tor process for Onionr
+            controller.authenticate(config.get('tor.controlpassword'))
+            # Create the v3 onion service for the peer to connect to
+            response = controller.create_ephemeral_hidden_service({80: service_port}, await_publication = True, key_type='NEW', key_content = 'ED25519-V3')
+
+            try:
+                for x in range(3):
+                    attempt = basicrequests.do_post_request('http://' + address + '/bs/' + response.service_id, port=socks)
+                    if attempt == 'success':
+                        break
+                else:
+                    raise ConnectionError
+            except ConnectionError:
+                # Re-raise
+                raise ConnectionError('Could not reach %s bootstrap address %s' % (peer, address))
+            else:
+                # If no connection error, create the service and save it to local global key store
+                peer = bytesconverter.bytes_to_str(peer)
+                key_store.put('dc-' + peer, response.service_id)
+                key_store.flush()
+                logger.info('hosting on %s with %s' % (response.service_id,  peer))
+                http_server.serve_forever()
+                http_server.stop()
+                key_store.delete('dc-' + peer)
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/httpheaders.html b/docs/html/onionr/onionrservices/httpheaders.html new file mode 100644 index 00000000..eafd2cf2 --- /dev/null +++ b/docs/html/onionr/onionrservices/httpheaders.html @@ -0,0 +1,122 @@ + + + + + + +onionr.onionrservices.httpheaders API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.httpheaders

+
+
+

Onionr - Private P2P Communication

+

Set default onionr http headers

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Set default onionr http headers
+'''
+'''
+    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/>.
+'''
+FEATURE_POLICY = """vibrate; vr; webauthn; usb; sync-xhr; speaker; 
+picture-in-picture; payment; midi; microphone; magnetometer; gyroscope; 
+geolocation; fullscreen; encrypted-media; document-domain; 
+camera; accelerometer; ambient-light-sensor""".replace('\n', '') # have to remove \n for flask
+def set_default_onionr_http_headers(flask_response):
+    '''Response headers'''
+    flask_response.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:"
+    flask_response.headers['X-Frame-Options'] = 'deny'
+    flask_response.headers['X-Content-Type-Options'] = "nosniff"
+    flask_response.headers['Server'] = ''
+    flask_response.headers['Date'] = 'Thu, 1 Jan 1970 00:00:00 GMT' # Clock info is probably useful to attackers. Set to unix epoch.
+    flask_response.headers['Connection'] = "close"
+    flask_response.headers['Clear-Site-Data'] = '"cache", "cookies", "storage", "executionContexts"'
+    flask_response.headers['Feature-Policy'] = FEATURE_POLICY
+    flask_response.headers['Referrer-Policy'] = 'same-origin'
+    return flask_response
+
+
+
+
+
+
+
+

Functions

+
+
+def set_default_onionr_http_headers(flask_response) +
+
+

Response headers

+
+Source code +
def set_default_onionr_http_headers(flask_response):
+    '''Response headers'''
+    flask_response.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:"
+    flask_response.headers['X-Frame-Options'] = 'deny'
+    flask_response.headers['X-Content-Type-Options'] = "nosniff"
+    flask_response.headers['Server'] = ''
+    flask_response.headers['Date'] = 'Thu, 1 Jan 1970 00:00:00 GMT' # Clock info is probably useful to attackers. Set to unix epoch.
+    flask_response.headers['Connection'] = "close"
+    flask_response.headers['Clear-Site-Data'] = '"cache", "cookies", "storage", "executionContexts"'
+    flask_response.headers['Feature-Policy'] = FEATURE_POLICY
+    flask_response.headers['Referrer-Policy'] = 'same-origin'
+    return flask_response
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/index.html b/docs/html/onionr/onionrservices/index.html new file mode 100644 index 00000000..e0318765 --- /dev/null +++ b/docs/html/onionr/onionrservices/index.html @@ -0,0 +1,275 @@ + + + + + + +onionr.onionrservices API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices

+
+
+

Onionr - Private P2P Communication

+

Onionr services provide the server component to direct connections

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Onionr services provide the server component to direct connections
+'''
+'''
+    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/>.
+'''
+import time
+import stem
+from . import connectionserver, bootstrapservice, serverexists
+from onionrutils import stringvalidators, basicrequests
+import config
+server_exists = serverexists.server_exists
+class OnionrServices:
+    '''
+        Create a client or server for connecting to peer interfaces
+    '''
+    def __init__(self):
+        self.servers = {}
+        self.clients = {}
+        self.shutdown = False
+    
+    def create_server(self, peer, address, comm_inst):
+        '''
+            When a client wants to connect, contact their bootstrap address and tell them our
+            ephemeral address for our service by creating a new ConnectionServer instance
+        '''
+        if not stringvalidators.validate_transport(address): raise ValueError('address must be valid')
+        BOOTSTRAP_TRIES = 10 # How many times to attempt contacting the bootstrap server
+        TRY_WAIT = 3 # Seconds to wait before trying bootstrap again
+        # HTTP is fine because .onion/i2p is encrypted/authenticated
+        base_url = 'http://%s/' % (address,)
+        socks = config.get('tor.socksport')
+        for x in range(BOOTSTRAP_TRIES):
+            if basicrequests.do_get_request(base_url + 'ping', port=socks, ignoreAPI=True) == 'pong!':
+                # if bootstrap sever is online, tell them our service address
+                connectionserver.ConnectionServer(peer, address, comm_inst=comm_inst)
+            else:
+                time.sleep(TRY_WAIT)
+        else:
+            return False
+
+    @staticmethod
+    def create_client(peer, comm_inst=None):
+        # Create ephemeral onion service to bootstrap connection to server
+        if not comm_inst == None:
+            try:
+                return comm_inst.direct_connection_clients[peer]
+            except KeyError:
+                pass
+        address = bootstrapservice.bootstrap_client_service(peer, comm_inst)
+        return address
+
+
+
+

Sub-modules

+
+
onionr.onionrservices.bootstrapservice
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrservices.connectionserver
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrservices.httpheaders
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrservices.pool
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrservices.serverexists
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrservices.warden
+
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class OnionrServices +
+
+

Create a client or server for connecting to peer interfaces

+
+Source code +
class OnionrServices:
+    '''
+        Create a client or server for connecting to peer interfaces
+    '''
+    def __init__(self):
+        self.servers = {}
+        self.clients = {}
+        self.shutdown = False
+    
+    def create_server(self, peer, address, comm_inst):
+        '''
+            When a client wants to connect, contact their bootstrap address and tell them our
+            ephemeral address for our service by creating a new ConnectionServer instance
+        '''
+        if not stringvalidators.validate_transport(address): raise ValueError('address must be valid')
+        BOOTSTRAP_TRIES = 10 # How many times to attempt contacting the bootstrap server
+        TRY_WAIT = 3 # Seconds to wait before trying bootstrap again
+        # HTTP is fine because .onion/i2p is encrypted/authenticated
+        base_url = 'http://%s/' % (address,)
+        socks = config.get('tor.socksport')
+        for x in range(BOOTSTRAP_TRIES):
+            if basicrequests.do_get_request(base_url + 'ping', port=socks, ignoreAPI=True) == 'pong!':
+                # if bootstrap sever is online, tell them our service address
+                connectionserver.ConnectionServer(peer, address, comm_inst=comm_inst)
+            else:
+                time.sleep(TRY_WAIT)
+        else:
+            return False
+
+    @staticmethod
+    def create_client(peer, comm_inst=None):
+        # Create ephemeral onion service to bootstrap connection to server
+        if not comm_inst == None:
+            try:
+                return comm_inst.direct_connection_clients[peer]
+            except KeyError:
+                pass
+        address = bootstrapservice.bootstrap_client_service(peer, comm_inst)
+        return address
+
+

Static methods

+
+
+def create_client(peer, comm_inst=None) +
+
+
+
+Source code +
@staticmethod
+def create_client(peer, comm_inst=None):
+    # Create ephemeral onion service to bootstrap connection to server
+    if not comm_inst == None:
+        try:
+            return comm_inst.direct_connection_clients[peer]
+        except KeyError:
+            pass
+    address = bootstrapservice.bootstrap_client_service(peer, comm_inst)
+    return address
+
+
+
+

Methods

+
+
+def create_server(self, peer, address, comm_inst) +
+
+

When a client wants to connect, contact their bootstrap address and tell them our +ephemeral address for our service by creating a new ConnectionServer instance

+
+Source code +
def create_server(self, peer, address, comm_inst):
+    '''
+        When a client wants to connect, contact their bootstrap address and tell them our
+        ephemeral address for our service by creating a new ConnectionServer instance
+    '''
+    if not stringvalidators.validate_transport(address): raise ValueError('address must be valid')
+    BOOTSTRAP_TRIES = 10 # How many times to attempt contacting the bootstrap server
+    TRY_WAIT = 3 # Seconds to wait before trying bootstrap again
+    # HTTP is fine because .onion/i2p is encrypted/authenticated
+    base_url = 'http://%s/' % (address,)
+    socks = config.get('tor.socksport')
+    for x in range(BOOTSTRAP_TRIES):
+        if basicrequests.do_get_request(base_url + 'ping', port=socks, ignoreAPI=True) == 'pong!':
+            # if bootstrap sever is online, tell them our service address
+            connectionserver.ConnectionServer(peer, address, comm_inst=comm_inst)
+        else:
+            time.sleep(TRY_WAIT)
+    else:
+        return False
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/pool.html b/docs/html/onionr/onionrservices/pool.html new file mode 100644 index 00000000..b558e2f6 --- /dev/null +++ b/docs/html/onionr/onionrservices/pool.html @@ -0,0 +1,131 @@ + + + + + + +onionr.onionrservices.pool API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.pool

+
+
+

Onionr - Private P2P Communication

+

Holds active onionrservices clients and servers

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Holds active onionrservices clients and servers
+'''
+'''
+    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 onionrutils import epoch
+class ServicePool:
+    def __init__(self):
+        self.servers = []
+        self.clients = []
+        self.bootstrap_pending = []
+    
+    def add_server(self, service):
+        self.servers.append((service, epoch.get_epoch()))
+        
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class ServicePool +
+
+
+
+Source code +
class ServicePool:
+    def __init__(self):
+        self.servers = []
+        self.clients = []
+        self.bootstrap_pending = []
+    
+    def add_server(self, service):
+        self.servers.append((service, epoch.get_epoch()))
+
+

Methods

+
+
+def add_server(self, service) +
+
+
+
+Source code +
def add_server(self, service):
+    self.servers.append((service, epoch.get_epoch()))
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/serverexists.html b/docs/html/onionr/onionrservices/serverexists.html new file mode 100644 index 00000000..2b7a6add --- /dev/null +++ b/docs/html/onionr/onionrservices/serverexists.html @@ -0,0 +1,111 @@ + + + + + + +onionr.onionrservices.serverexists API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.serverexists

+
+
+

Onionr - Private P2P Communication

+

Function to check if an onion server is created for a peer or not

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Function to check if an onion server is created for a peer or not
+'''
+'''
+    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/>.
+'''
+import deadsimplekv
+
+import filepaths
+from onionrutils import bytesconverter
+
+def server_exists(peer: str) -> bool:
+    '''checks if an onion server is created for a peer or not'''
+    peer = bytesconverter.bytes_to_str(peer)
+    kv = deadsimplekv.DeadSimpleKV(filepaths.cached_storage)
+    kv.refresh()
+    return not kv.get('dc-' + peer) is None
+
+
+
+
+
+
+
+

Functions

+
+
+def server_exists(peer) +
+
+

checks if an onion server is created for a peer or not

+
+Source code +
def server_exists(peer: str) -> bool:
+    '''checks if an onion server is created for a peer or not'''
+    peer = bytesconverter.bytes_to_str(peer)
+    kv = deadsimplekv.DeadSimpleKV(filepaths.cached_storage)
+    kv.refresh()
+    return not kv.get('dc-' + peer) is None
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/warden/bootstrap.html b/docs/html/onionr/onionrservices/warden/bootstrap.html new file mode 100644 index 00000000..59ff7088 --- /dev/null +++ b/docs/html/onionr/onionrservices/warden/bootstrap.html @@ -0,0 +1,77 @@ + + + + + + +onionr.onionrservices.warden.bootstrap API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.warden.bootstrap

+
+
+

Onionr - Private P2P Communication

+

Bootstrap warden monitors the bootstrap server

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Bootstrap warden monitors the bootstrap server
+'''
+'''
+    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/>.
+'''
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/warden/client.html b/docs/html/onionr/onionrservices/warden/client.html new file mode 100644 index 00000000..c08abf6c --- /dev/null +++ b/docs/html/onionr/onionrservices/warden/client.html @@ -0,0 +1,53 @@ + + + + + + +onionr.onionrservices.warden.client API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.warden.client

+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/warden/index.html b/docs/html/onionr/onionrservices/warden/index.html new file mode 100644 index 00000000..2a8d7ea7 --- /dev/null +++ b/docs/html/onionr/onionrservices/warden/index.html @@ -0,0 +1,81 @@ + + + + + + +onionr.onionrservices.warden API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.warden

+
+
+
+Source code +
from . import client # Client connection warden. Monitors & validates connection security.
+from . import server # Server connection warden. Monitors and validates server security
+#from . import watchdog # Watchdog. Oversees running services for statistic collection and TTL control
+
+
+
+

Sub-modules

+
+
onionr.onionrservices.warden.bootstrap
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrservices.warden.client
+
+
+
+
onionr.onionrservices.warden.server
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrservices/warden/server.html b/docs/html/onionr/onionrservices/warden/server.html new file mode 100644 index 00000000..fa572d8a --- /dev/null +++ b/docs/html/onionr/onionrservices/warden/server.html @@ -0,0 +1,53 @@ + + + + + + +onionr.onionrservices.warden.server API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrservices.warden.server

+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrsetup/dbcreator.html b/docs/html/onionr/onionrsetup/dbcreator.html new file mode 100644 index 00000000..a5878dd7 --- /dev/null +++ b/docs/html/onionr/onionrsetup/dbcreator.html @@ -0,0 +1,492 @@ + + + + + + +onionr.onionrsetup.dbcreator API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrsetup.dbcreator

+
+
+

Onionr - Private P2P Communication

+

DBCreator, creates sqlite3 databases used by Onionr

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    DBCreator, creates sqlite3 databases used by Onionr
+'''
+'''
+    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/>.
+'''
+import sqlite3, os
+from coredb import dbfiles
+import filepaths
+
+def createAddressDB():
+    '''
+        Generate the address database
+
+        types:
+            1: I2P b32 address
+            2: Tor v2 (like facebookcorewwwi.onion)
+            3: Tor v3
+    '''
+    if os.path.exists(dbfiles.address_info_db):
+        raise FileExistsError("Address database already exists")
+    conn = sqlite3.connect(dbfiles.address_info_db)
+    c = conn.cursor()
+    c.execute('''CREATE TABLE adders(
+        address text,
+        type int,
+        knownPeer text,
+        speed int,
+        success int,
+        powValue text,
+        failure int,
+        lastConnect int,
+        lastConnectAttempt int,
+        trust int,
+        introduced int
+        );
+    ''')
+    conn.commit()
+    conn.close()
+
+def createPeerDB():
+    '''
+        Generate the peer sqlite3 database and populate it with the peers table.
+    '''
+    if os.path.exists(dbfiles.user_id_info_db):
+        raise FileExistsError("User database already exists")
+    # generate the peer database
+    conn = sqlite3.connect(dbfiles.user_id_info_db)
+    c = conn.cursor()
+    c.execute('''CREATE TABLE peers(
+        ID text not null,
+        name text,
+        adders text,
+        dateSeen not null,
+        trust int,
+        hashID text);
+    ''')
+    c.execute('''CREATE TABLE forwardKeys(
+    peerKey text not null,
+    forwardKey text not null,
+    date int not null,
+    expire int not null
+    );''')
+    conn.commit()
+    conn.close()
+    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():
+    '''
+        Create the forward secrecy key db (*for *OUR* keys*)
+    '''
+    if os.path.exists(dbfiles.forward_keys_db):
+        raise FileExistsError("Block database already exists")
+    conn = sqlite3.connect(dbfiles.forward_keys_db)
+    c = conn.cursor()
+    c.execute('''CREATE TABLE myForwardKeys(
+        peer text not null,
+        publickey text not null,
+        privatekey text not null,
+        date int not null,
+        expire int not null
+        );
+    ''')
+    conn.commit()
+    conn.close()
+    return
+
+def createDaemonDB():
+    '''
+        Create the daemon queue database
+    '''
+    if os.path.exists(dbfiles.daemon_queue_db):
+        raise FileExistsError("Daemon queue db already exists")
+    conn = sqlite3.connect(dbfiles.daemon_queue_db, timeout=10)
+    c = conn.cursor()
+    # Create table
+    c.execute('''CREATE TABLE commands (id integer primary key autoincrement, command text, data text, date text, responseID text)''')
+    conn.commit()
+    conn.close()
+
+def create_blacklist_db():
+    if os.path.exists(dbfiles.blacklist_db):
+        raise FileExistsError("Blacklist db already exists")
+    conn = sqlite3.connect(dbfiles.blacklist_db, timeout=10)
+    c = conn.cursor()
+    # Create table
+    c.execute('''CREATE TABLE blacklist(
+            hash text primary key not null,
+            dataType int,
+            blacklistDate int,
+            expire int
+            );
+        ''')
+    conn.commit()
+    conn.close()
+
+
+create_funcs = [createAddressDB, createPeerDB, createBlockDB, createBlockDataDB, createForwardKeyDB, createDaemonDB, create_blacklist_db]
+
+
+
+
+
+
+
+

Functions

+
+
+def createAddressDB() +
+
+

Generate the address database

+

types: +1: I2P b32 address +2: Tor v2 (like facebookcorewwwi.onion) +3: Tor v3

+
+Source code +
def createAddressDB():
+    '''
+        Generate the address database
+
+        types:
+            1: I2P b32 address
+            2: Tor v2 (like facebookcorewwwi.onion)
+            3: Tor v3
+    '''
+    if os.path.exists(dbfiles.address_info_db):
+        raise FileExistsError("Address database already exists")
+    conn = sqlite3.connect(dbfiles.address_info_db)
+    c = conn.cursor()
+    c.execute('''CREATE TABLE adders(
+        address text,
+        type int,
+        knownPeer text,
+        speed int,
+        success int,
+        powValue text,
+        failure int,
+        lastConnect int,
+        lastConnectAttempt int,
+        trust int,
+        introduced int
+        );
+    ''')
+    conn.commit()
+    conn.close()
+
+
+
+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

+
+Source code +
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() +
+
+
+
+Source code +
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 createDaemonDB() +
+
+

Create the daemon queue database

+
+Source code +
def createDaemonDB():
+    '''
+        Create the daemon queue database
+    '''
+    if os.path.exists(dbfiles.daemon_queue_db):
+        raise FileExistsError("Daemon queue db already exists")
+    conn = sqlite3.connect(dbfiles.daemon_queue_db, timeout=10)
+    c = conn.cursor()
+    # Create table
+    c.execute('''CREATE TABLE commands (id integer primary key autoincrement, command text, data text, date text, responseID text)''')
+    conn.commit()
+    conn.close()
+
+
+
+def createForwardKeyDB() +
+
+

Create the forward secrecy key db (for OUR keys)

+
+Source code +
def createForwardKeyDB():
+    '''
+        Create the forward secrecy key db (*for *OUR* keys*)
+    '''
+    if os.path.exists(dbfiles.forward_keys_db):
+        raise FileExistsError("Block database already exists")
+    conn = sqlite3.connect(dbfiles.forward_keys_db)
+    c = conn.cursor()
+    c.execute('''CREATE TABLE myForwardKeys(
+        peer text not null,
+        publickey text not null,
+        privatekey text not null,
+        date int not null,
+        expire int not null
+        );
+    ''')
+    conn.commit()
+    conn.close()
+    return
+
+
+
+def createPeerDB() +
+
+

Generate the peer sqlite3 database and populate it with the peers table.

+
+Source code +
def createPeerDB():
+    '''
+        Generate the peer sqlite3 database and populate it with the peers table.
+    '''
+    if os.path.exists(dbfiles.user_id_info_db):
+        raise FileExistsError("User database already exists")
+    # generate the peer database
+    conn = sqlite3.connect(dbfiles.user_id_info_db)
+    c = conn.cursor()
+    c.execute('''CREATE TABLE peers(
+        ID text not null,
+        name text,
+        adders text,
+        dateSeen not null,
+        trust int,
+        hashID text);
+    ''')
+    c.execute('''CREATE TABLE forwardKeys(
+    peerKey text not null,
+    forwardKey text not null,
+    date int not null,
+    expire int not null
+    );''')
+    conn.commit()
+    conn.close()
+    return
+
+
+
+def create_blacklist_db() +
+
+
+
+Source code +
def create_blacklist_db():
+    if os.path.exists(dbfiles.blacklist_db):
+        raise FileExistsError("Blacklist db already exists")
+    conn = sqlite3.connect(dbfiles.blacklist_db, timeout=10)
+    c = conn.cursor()
+    # Create table
+    c.execute('''CREATE TABLE blacklist(
+            hash text primary key not null,
+            dataType int,
+            blacklistDate int,
+            expire int
+            );
+        ''')
+    conn.commit()
+    conn.close()
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrsetup/defaultpluginsetup.html b/docs/html/onionr/onionrsetup/defaultpluginsetup.html new file mode 100644 index 00000000..7782b823 --- /dev/null +++ b/docs/html/onionr/onionrsetup/defaultpluginsetup.html @@ -0,0 +1,138 @@ + + + + + + +onionr.onionrsetup.defaultpluginsetup API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrsetup.defaultpluginsetup

+
+
+

Onionr - Private P2P Communication

+

Installs default plugins

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    Installs default plugins
+"""
+"""
+    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/>.
+"""
+import os, shutil
+
+import onionrplugins as plugins
+import logger
+import filepaths
+
+def setup_default_plugins():
+    # Copy default plugins into plugins folder
+    if not os.path.exists(plugins.get_plugins_folder()):
+        if os.path.exists('../static-data/default-plugins/'):
+            names = [f for f in os.listdir("../static-data/default-plugins/")]
+            shutil.copytree('../static-data/default-plugins/', plugins.get_plugins_folder())
+
+            # Enable plugins
+            for name in names:
+                if not name in plugins.get_enabled_plugins():
+                    plugins.enable(name)
+
+    for name in plugins.get_enabled_plugins():
+        if not os.path.exists(plugins.get_plugin_data_folder(name)):
+            try:
+                os.mkdir(plugins.get_plugin_data_folder(name))
+            except Exception as e:
+                #logger.warn('Error enabling plugin: ' + str(e), terminal=True)
+                plugins.disable(name, stop_event = False)
+
+
+
+
+
+
+
+

Functions

+
+
+def setup_default_plugins() +
+
+
+
+Source code +
def setup_default_plugins():
+    # Copy default plugins into plugins folder
+    if not os.path.exists(plugins.get_plugins_folder()):
+        if os.path.exists('../static-data/default-plugins/'):
+            names = [f for f in os.listdir("../static-data/default-plugins/")]
+            shutil.copytree('../static-data/default-plugins/', plugins.get_plugins_folder())
+
+            # Enable plugins
+            for name in names:
+                if not name in plugins.get_enabled_plugins():
+                    plugins.enable(name)
+
+    for name in plugins.get_enabled_plugins():
+        if not os.path.exists(plugins.get_plugin_data_folder(name)):
+            try:
+                os.mkdir(plugins.get_plugin_data_folder(name))
+            except Exception as e:
+                #logger.warn('Error enabling plugin: ' + str(e), terminal=True)
+                plugins.disable(name, stop_event = False)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrsetup/index.html b/docs/html/onionr/onionrsetup/index.html new file mode 100644 index 00000000..645890d0 --- /dev/null +++ b/docs/html/onionr/onionrsetup/index.html @@ -0,0 +1,81 @@ + + + + + + +onionr.onionrsetup API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrsetup

+
+
+
+Source code +
from . import defaultpluginsetup, setupconfig
+setup_default_plugins = defaultpluginsetup.setup_default_plugins
+setup_config = setupconfig.setup_config
+
+
+
+

Sub-modules

+
+
onionr.onionrsetup.dbcreator
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrsetup.defaultpluginsetup
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrsetup.setupconfig
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrsetup/setupconfig.html b/docs/html/onionr/onionrsetup/setupconfig.html new file mode 100644 index 00000000..e03bf93a --- /dev/null +++ b/docs/html/onionr/onionrsetup/setupconfig.html @@ -0,0 +1,220 @@ + + + + + + +onionr.onionrsetup.setupconfig API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrsetup.setupconfig

+
+
+

Onionr - Private P2P Communication

+

Initialize Onionr configuration

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Initialize Onionr configuration
+'''
+'''
+    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/>.
+'''
+import os, json, base64
+import config, logger, netcontroller
+from etc import onionrvalues
+from logger.settings import *
+from utils import readstatic
+
+def setup_config():
+    
+    if not os.path.exists(config._configfile):
+        # this is the default config, it will be overwritten if a config file already exists. Else, it saves it
+        conf_data = readstatic.read_static('default_config.json', ret_bin=False)
+        config.set_config(json.loads(conf_data))
+
+        config.save()
+
+    config.reload()
+
+    settings = 0b000
+    if config.get('log.console.color', True):
+        settings = settings | USE_ANSI
+    if config.get('log.console.output', True):
+        settings = settings | OUTPUT_TO_CONSOLE
+    if config.get('log.file.output', True):
+        settings = settings | OUTPUT_TO_FILE
+    set_settings(settings)
+
+    verbosity = str(config.get('log.verbosity', 'default')).lower().strip()
+    if not verbosity in ['default', 'null', 'none', 'nil']:
+        map = {
+            str(LEVEL_DEBUG) : LEVEL_DEBUG,
+            'verbose' : LEVEL_DEBUG,
+            'debug' : LEVEL_DEBUG,
+            str(LEVEL_INFO) : LEVEL_INFO,
+            'info' : LEVEL_INFO,
+            'information' : LEVEL_INFO,
+            str(LEVEL_WARN) : LEVEL_WARN,
+            'warn' : LEVEL_WARN,
+            'warning' : LEVEL_WARN,
+            'warnings' : LEVEL_WARN,
+            str(LEVEL_ERROR) : LEVEL_ERROR,
+            'err' : LEVEL_ERROR,
+            'error' : LEVEL_ERROR,
+            'errors' : LEVEL_ERROR,
+            str(LEVEL_FATAL) : LEVEL_FATAL,
+            'fatal' : LEVEL_FATAL,
+            str(LEVEL_IMPORTANT) : LEVEL_IMPORTANT,
+            'silent' : LEVEL_IMPORTANT,
+            'quiet' : LEVEL_IMPORTANT,
+            'important' : LEVEL_IMPORTANT
+        }
+
+        if verbosity in map:
+            set_level(map[verbosity])
+        else:
+            logger.warn('Verbosity level %s is not valid, using default verbosity.' % verbosity)
+
+    if type(config.get('client.webpassword')) is type(None):
+        config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True)
+    if type(config.get('client.client.port')) is type(None):
+        randomPort = netcontroller.get_open_port()
+        config.set('client.client.port', randomPort, savefile=True)
+    if type(config.get('client.public.port')) is type(None):
+        randomPort = netcontroller.get_open_port()
+        config.set('client.public.port', randomPort, savefile=True)
+    if type(config.get('client.api_version')) is type(None):
+        config.set('client.api_version', onionrvalues.API_VERSION, savefile=True)
+
+
+
+
+
+
+
+

Functions

+
+
+def setup_config() +
+
+
+
+Source code +
def setup_config():
+    
+    if not os.path.exists(config._configfile):
+        # this is the default config, it will be overwritten if a config file already exists. Else, it saves it
+        conf_data = readstatic.read_static('default_config.json', ret_bin=False)
+        config.set_config(json.loads(conf_data))
+
+        config.save()
+
+    config.reload()
+
+    settings = 0b000
+    if config.get('log.console.color', True):
+        settings = settings | USE_ANSI
+    if config.get('log.console.output', True):
+        settings = settings | OUTPUT_TO_CONSOLE
+    if config.get('log.file.output', True):
+        settings = settings | OUTPUT_TO_FILE
+    set_settings(settings)
+
+    verbosity = str(config.get('log.verbosity', 'default')).lower().strip()
+    if not verbosity in ['default', 'null', 'none', 'nil']:
+        map = {
+            str(LEVEL_DEBUG) : LEVEL_DEBUG,
+            'verbose' : LEVEL_DEBUG,
+            'debug' : LEVEL_DEBUG,
+            str(LEVEL_INFO) : LEVEL_INFO,
+            'info' : LEVEL_INFO,
+            'information' : LEVEL_INFO,
+            str(LEVEL_WARN) : LEVEL_WARN,
+            'warn' : LEVEL_WARN,
+            'warning' : LEVEL_WARN,
+            'warnings' : LEVEL_WARN,
+            str(LEVEL_ERROR) : LEVEL_ERROR,
+            'err' : LEVEL_ERROR,
+            'error' : LEVEL_ERROR,
+            'errors' : LEVEL_ERROR,
+            str(LEVEL_FATAL) : LEVEL_FATAL,
+            'fatal' : LEVEL_FATAL,
+            str(LEVEL_IMPORTANT) : LEVEL_IMPORTANT,
+            'silent' : LEVEL_IMPORTANT,
+            'quiet' : LEVEL_IMPORTANT,
+            'important' : LEVEL_IMPORTANT
+        }
+
+        if verbosity in map:
+            set_level(map[verbosity])
+        else:
+            logger.warn('Verbosity level %s is not valid, using default verbosity.' % verbosity)
+
+    if type(config.get('client.webpassword')) is type(None):
+        config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True)
+    if type(config.get('client.client.port')) is type(None):
+        randomPort = netcontroller.get_open_port()
+        config.set('client.client.port', randomPort, savefile=True)
+    if type(config.get('client.public.port')) is type(None):
+        randomPort = netcontroller.get_open_port()
+        config.set('client.public.port', randomPort, savefile=True)
+    if type(config.get('client.api_version')) is type(None):
+        config.set('client.api_version', onionrvalues.API_VERSION, savefile=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrstorage/index.html b/docs/html/onionr/onionrstorage/index.html new file mode 100644 index 00000000..45645fca --- /dev/null +++ b/docs/html/onionr/onionrstorage/index.html @@ -0,0 +1,247 @@ + + + + + + +onionr.onionrstorage API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrstorage

+
+
+

Onionr - Private P2P Communication

+

This file handles block storage, providing an abstraction for storing blocks between file system and database

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This file handles block storage, providing an abstraction for storing blocks between file system and database
+'''
+'''
+    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/>.
+'''
+import sys, sqlite3, os
+from onionrutils import bytesconverter, stringvalidators
+from coredb import dbfiles
+import filepaths, onionrcrypto, onionrexceptions
+from onionrsetup import dbcreator
+from onionrcrypto import hashers
+from . import setdata
+DB_ENTRY_SIZE_LIMIT = 10000 # Will be a config option
+
+set_data = setdata.set_data
+
+def _dbInsert(blockHash, data):
+    conn = sqlite3.connect(dbfiles.block_data_db, timeout=10)
+    c = conn.cursor()
+    data = (blockHash, data)
+    c.execute('INSERT INTO blockData (hash, data) VALUES(?, ?);', data)
+    conn.commit()
+    conn.close()
+
+def _dbFetch(blockHash):
+    conn = sqlite3.connect(dbfiles.block_data_db, timeout=10)
+    c = conn.cursor()
+    for i in c.execute('SELECT data from blockData where hash = ?', (blockHash,)):
+        return i[0]
+    conn.commit()
+    conn.close()
+    return None
+
+def deleteBlock(blockHash):
+    # You should call core.removeBlock if you automatically want to remove storage byte count
+    if os.path.exists('%s/%s.dat' % (filepaths.block_data_location, blockHash)):
+        os.remove('%s/%s.dat' % (filepaths.block_data_location, blockHash))
+        return True
+    conn = sqlite3.connect(dbfiles.block_data_db, timeout=10)
+    c = conn.cursor()
+    data = (blockHash,)
+    c.execute('DELETE FROM blockData where hash = ?', data)
+    conn.commit()
+    conn.close()
+    return True
+
+def store(data, blockHash=''):
+    if not stringvalidators.validate_hash(blockHash): raise ValueError
+    ourHash = hashers.sha3_hash(data)
+    if blockHash != '':
+        if not ourHash == blockHash: raise ValueError('Hash specified does not meet internal hash check')
+    else:
+        blockHash = ourHash
+    
+    if DB_ENTRY_SIZE_LIMIT >= sys.getsizeof(data):
+        _dbInsert(blockHash, data)
+    else:
+        with open('%s/%s.dat' % (filepaths.block_data_location, blockHash), 'wb') as blockFile:
+            blockFile.write(data)
+
+def getData(bHash):
+    if not stringvalidators.validate_hash(bHash): raise ValueError
+
+    bHash = bytesconverter.bytes_to_str(bHash)
+
+    # First check DB for data entry by hash
+    # if no entry, check disk
+    # If no entry in either, raise an exception
+    retData = None
+    fileLocation = '%s/%s.dat' % (filepaths.block_data_location, bHash)
+    if os.path.exists(fileLocation):
+        with open(fileLocation, 'rb') as block:
+            retData = block.read()
+    else:
+        retData = _dbFetch(bHash)
+        if retData is None:
+            raise onionrexceptions.NoDataAvailable("Block data for %s is not available" % [bHash])
+    return retData
+
+
+
+

Sub-modules

+
+
onionr.onionrstorage.removeblock
+
+
+
+
onionr.onionrstorage.setdata
+
+
+
+
+
+
+
+
+

Functions

+
+
+def deleteBlock(blockHash) +
+
+
+
+Source code +
def deleteBlock(blockHash):
+    # You should call core.removeBlock if you automatically want to remove storage byte count
+    if os.path.exists('%s/%s.dat' % (filepaths.block_data_location, blockHash)):
+        os.remove('%s/%s.dat' % (filepaths.block_data_location, blockHash))
+        return True
+    conn = sqlite3.connect(dbfiles.block_data_db, timeout=10)
+    c = conn.cursor()
+    data = (blockHash,)
+    c.execute('DELETE FROM blockData where hash = ?', data)
+    conn.commit()
+    conn.close()
+    return True
+
+
+
+def getData(bHash) +
+
+
+
+Source code +
def getData(bHash):
+    if not stringvalidators.validate_hash(bHash): raise ValueError
+
+    bHash = bytesconverter.bytes_to_str(bHash)
+
+    # First check DB for data entry by hash
+    # if no entry, check disk
+    # If no entry in either, raise an exception
+    retData = None
+    fileLocation = '%s/%s.dat' % (filepaths.block_data_location, bHash)
+    if os.path.exists(fileLocation):
+        with open(fileLocation, 'rb') as block:
+            retData = block.read()
+    else:
+        retData = _dbFetch(bHash)
+        if retData is None:
+            raise onionrexceptions.NoDataAvailable("Block data for %s is not available" % [bHash])
+    return retData
+
+
+
+def store(data, blockHash='') +
+
+
+
+Source code +
def store(data, blockHash=''):
+    if not stringvalidators.validate_hash(blockHash): raise ValueError
+    ourHash = hashers.sha3_hash(data)
+    if blockHash != '':
+        if not ourHash == blockHash: raise ValueError('Hash specified does not meet internal hash check')
+    else:
+        blockHash = ourHash
+    
+    if DB_ENTRY_SIZE_LIMIT >= sys.getsizeof(data):
+        _dbInsert(blockHash, data)
+    else:
+        with open('%s/%s.dat' % (filepaths.block_data_location, blockHash), 'wb') as blockFile:
+            blockFile.write(data)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrstorage/removeblock.html b/docs/html/onionr/onionrstorage/removeblock.html new file mode 100644 index 00000000..391152ad --- /dev/null +++ b/docs/html/onionr/onionrstorage/removeblock.html @@ -0,0 +1,115 @@ + + + + + + +onionr.onionrstorage.removeblock API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrstorage.removeblock

+
+
+
+Source code +
import sys, sqlite3
+import onionrexceptions, onionrstorage
+from onionrutils import stringvalidators
+from coredb import dbfiles
+from onionrblocks import 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):
+        conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+        c = conn.cursor()
+        t = (block,)
+        c.execute('Delete from hashes where hash=?;', t)
+        conn.commit()
+        conn.close()
+        dataSize = sys.getsizeof(onionrstorage.getData(block))
+        storagecounter.StorageCounter().remove_bytes(dataSize)
+    else:
+        raise onionrexceptions.InvalidHexHash
+
+
+
+
+
+
+
+

Functions

+
+
+def remove_block(block) +
+
+

remove a block from this node (does not automatically blacklist)

+

**You may want blacklist.addToDB(blockHash)

+
+Source code +
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):
+        conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+        c = conn.cursor()
+        t = (block,)
+        c.execute('Delete from hashes where hash=?;', t)
+        conn.commit()
+        conn.close()
+        dataSize = sys.getsizeof(onionrstorage.getData(block))
+        storagecounter.StorageCounter().remove_bytes(dataSize)
+    else:
+        raise onionrexceptions.InvalidHexHash
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrstorage/setdata.html b/docs/html/onionr/onionrstorage/setdata.html new file mode 100644 index 00000000..e3ce2301 --- /dev/null +++ b/docs/html/onionr/onionrstorage/setdata.html @@ -0,0 +1,151 @@ + + + + + + +onionr.onionrstorage.setdata API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrstorage.setdata

+
+
+
+Source code +
import sys, sqlite3
+import onionrstorage, onionrexceptions, onionrcrypto as crypto
+import filepaths
+from onionrblocks import storagecounter
+from coredb import dbfiles
+from onionrutils import blockmetadata, bytesconverter
+def set_data(data)->str:
+    '''
+        Set the data assciated with a hash
+    '''
+    storage_counter = storagecounter.StorageCounter()
+    data = data
+    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()
+    blockFileName = filepaths.block_data_location + dataHash + '.dat'
+    try:
+        onionrstorage.getData(dataHash)
+    except onionrexceptions.NoDataAvailable:
+        if storage_counter.add_bytes(dataSize) != False:
+            onionrstorage.store(data, blockHash=dataHash)
+            conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+            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
+
+
+
+
+
+
+
+

Functions

+
+
+def set_data(data) +
+
+

Set the data assciated with a hash

+
+Source code +
def set_data(data)->str:
+    '''
+        Set the data assciated with a hash
+    '''
+    storage_counter = storagecounter.StorageCounter()
+    data = data
+    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()
+    blockFileName = filepaths.block_data_location + dataHash + '.dat'
+    try:
+        onionrstorage.getData(dataHash)
+    except onionrexceptions.NoDataAvailable:
+        if storage_counter.add_bytes(dataSize) != False:
+            onionrstorage.store(data, blockHash=dataHash)
+            conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30)
+            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
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrusers/contactmanager.html b/docs/html/onionr/onionrusers/contactmanager.html new file mode 100644 index 00000000..0f3de3f2 --- /dev/null +++ b/docs/html/onionr/onionrusers/contactmanager.html @@ -0,0 +1,283 @@ + + + + + + +onionr.onionrusers.contactmanager API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrusers.contactmanager

+
+
+

Onionr - Private P2P Communication

+

Sets more abstract information related to a peer. Can be thought of as traditional 'contact' system

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Sets more abstract information related to a peer. Can be thought of as traditional 'contact' system
+'''
+'''
+    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/>.
+'''
+import os, json, onionrexceptions
+import unpaddedbase32
+from onionrusers import onionrusers
+from onionrutils import bytesconverter, epoch
+from utils import identifyhome
+
+import mnemonic
+class ContactManager(onionrusers.OnionrUser):
+    def __init__(self, publicKey, saveUser=False, recordExpireSeconds=5):
+        try:
+            if " " in publicKey:
+                publicKey = mnemonic.Mnemonic('english').to_entropy(publicKey)
+                publicKey = unpaddedbase32.b32encode(bytesconverter.str_to_bytes(publicKey))
+        except ValueError:
+            pass
+        publicKey = bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(publicKey)))
+        super(ContactManager, self).__init__(publicKey, saveUser=saveUser)
+        home = identifyhome.identify_home()
+        self.dataDir = home + '/contacts/'
+        self.dataFile = '%s/contacts/%s.json' % (home, publicKey)
+        self.lastRead = 0
+        self.recordExpire = recordExpireSeconds
+        self.data = self._loadData()
+        self.deleted = False
+        
+        if not os.path.exists(self.dataDir):
+            os.mkdir(self.dataDir)
+    
+    def _writeData(self):
+        data = json.dumps(self.data)
+        with open(self.dataFile, 'w') as dataFile:
+            dataFile.write(data)
+
+    def _loadData(self):
+        self.lastRead = epoch.get_epoch()
+        retData = {}
+        if os.path.exists(self.dataFile):
+            with open(self.dataFile, 'r') as dataFile:
+                retData = json.loads(dataFile.read())
+        return retData
+
+    def set_info(self, key, value, autoWrite=True):
+        if self.deleted:
+            raise onionrexceptions.ContactDeleted
+
+        self.data[key] = value
+        if autoWrite:
+            self._writeData()
+        return
+    
+    def get_info(self, key, forceReload=False):
+        if self.deleted:
+            raise onionrexceptions.ContactDeleted
+
+        if (epoch.get_epoch() - self.lastRead >= self.recordExpire) or forceReload:
+            self.data = self._loadData()
+        try:
+            return self.data[key]
+        except KeyError:
+            return None
+
+    def delete_contact(self):
+        self.deleted = True
+        if os.path.exists(self.dataFile):
+            os.remove(self.dataFile)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class ContactManager +(publicKey, saveUser=False, recordExpireSeconds=5) +
+
+

OnionrUser is an abstraction for "users" of the network.

+

Takes a base32 encoded ed25519 public key, and a bool saveUser +saveUser determines if we should add a user to our peer database or not.

+
+Source code +
class ContactManager(onionrusers.OnionrUser):
+    def __init__(self, publicKey, saveUser=False, recordExpireSeconds=5):
+        try:
+            if " " in publicKey:
+                publicKey = mnemonic.Mnemonic('english').to_entropy(publicKey)
+                publicKey = unpaddedbase32.b32encode(bytesconverter.str_to_bytes(publicKey))
+        except ValueError:
+            pass
+        publicKey = bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(publicKey)))
+        super(ContactManager, self).__init__(publicKey, saveUser=saveUser)
+        home = identifyhome.identify_home()
+        self.dataDir = home + '/contacts/'
+        self.dataFile = '%s/contacts/%s.json' % (home, publicKey)
+        self.lastRead = 0
+        self.recordExpire = recordExpireSeconds
+        self.data = self._loadData()
+        self.deleted = False
+        
+        if not os.path.exists(self.dataDir):
+            os.mkdir(self.dataDir)
+    
+    def _writeData(self):
+        data = json.dumps(self.data)
+        with open(self.dataFile, 'w') as dataFile:
+            dataFile.write(data)
+
+    def _loadData(self):
+        self.lastRead = epoch.get_epoch()
+        retData = {}
+        if os.path.exists(self.dataFile):
+            with open(self.dataFile, 'r') as dataFile:
+                retData = json.loads(dataFile.read())
+        return retData
+
+    def set_info(self, key, value, autoWrite=True):
+        if self.deleted:
+            raise onionrexceptions.ContactDeleted
+
+        self.data[key] = value
+        if autoWrite:
+            self._writeData()
+        return
+    
+    def get_info(self, key, forceReload=False):
+        if self.deleted:
+            raise onionrexceptions.ContactDeleted
+
+        if (epoch.get_epoch() - self.lastRead >= self.recordExpire) or forceReload:
+            self.data = self._loadData()
+        try:
+            return self.data[key]
+        except KeyError:
+            return None
+
+    def delete_contact(self):
+        self.deleted = True
+        if os.path.exists(self.dataFile):
+            os.remove(self.dataFile)
+
+

Ancestors

+
    +
  • onionrusers.onionrusers.OnionrUser
  • +
+

Methods

+
+
+def delete_contact(self) +
+
+
+
+Source code +
def delete_contact(self):
+    self.deleted = True
+    if os.path.exists(self.dataFile):
+        os.remove(self.dataFile)
+
+
+
+def get_info(self, key, forceReload=False) +
+
+
+
+Source code +
def get_info(self, key, forceReload=False):
+    if self.deleted:
+        raise onionrexceptions.ContactDeleted
+
+    if (epoch.get_epoch() - self.lastRead >= self.recordExpire) or forceReload:
+        self.data = self._loadData()
+    try:
+        return self.data[key]
+    except KeyError:
+        return None
+
+
+
+def set_info(self, key, value, autoWrite=True) +
+
+
+
+Source code +
def set_info(self, key, value, autoWrite=True):
+    if self.deleted:
+        raise onionrexceptions.ContactDeleted
+
+    self.data[key] = value
+    if autoWrite:
+        self._writeData()
+    return
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrusers/index.html b/docs/html/onionr/onionrusers/index.html new file mode 100644 index 00000000..de49e7d4 --- /dev/null +++ b/docs/html/onionr/onionrusers/index.html @@ -0,0 +1,70 @@ + + + + + + +onionr.onionrusers API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrusers

+
+
+
+
+

Sub-modules

+
+
onionr.onionrusers.contactmanager
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrusers.onionrusers
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrusers/onionrusers.html b/docs/html/onionr/onionrusers/onionrusers.html new file mode 100644 index 00000000..abe5f7be --- /dev/null +++ b/docs/html/onionr/onionrusers/onionrusers.html @@ -0,0 +1,768 @@ + + + + + + +onionr.onionrusers.onionrusers API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrusers.onionrusers

+
+
+

Onionr - Private P2P Communication

+

Contains abstractions for interacting with users of Onionr

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Contains abstractions for interacting with users of Onionr
+'''
+'''
+    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/>.
+'''
+import logger, onionrexceptions, json, sqlite3, time
+from onionrutils import stringvalidators, bytesconverter, epoch
+import unpaddedbase32
+import nacl.exceptions
+from coredb import keydb, dbfiles
+import onionrcrypto
+
+def deleteExpiredKeys():
+    # Fetch the keys we generated for the peer, that are still around
+    conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
+    c = conn.cursor()
+
+    curTime = epoch.get_epoch()
+    c.execute("DELETE from myForwardKeys where expire <= ?", (curTime,))
+    conn.commit()
+    conn.execute("VACUUM")
+    conn.close()
+    return
+
+def deleteTheirExpiredKeys(pubkey):
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+    c = conn.cursor()
+
+    # Prepare the insert
+    command = (pubkey, epoch.get_epoch())
+
+    c.execute("DELETE from forwardKeys where peerKey = ? and expire <= ?", command)
+
+    conn.commit()
+    conn.close()
+
+DEFAULT_KEY_EXPIRE = 604800
+
+class OnionrUser:
+
+    def __init__(self, publicKey, saveUser=False):
+        '''
+            OnionrUser is an abstraction for "users" of the network. 
+            
+            Takes a base32 encoded ed25519 public key, and a bool saveUser
+            saveUser determines if we should add a user to our peer database or not.
+        '''
+        publicKey = unpaddedbase32.repad(bytesconverter.str_to_bytes(publicKey)).decode()
+
+        self.trust = 0
+        self.publicKey = publicKey
+
+        if saveUser:
+            try:
+                keydb.addkeys.add_peer(publicKey)
+            except AssertionError:
+                pass
+
+        self.trust = keydb.userinfo.get_user_info(self.publicKey, 'trust')
+        return
+
+    def setTrust(self, newTrust):
+        '''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate'''
+        keydb.userinfo.set_user_info(self.publicKey, 'trust', newTrust)
+
+    def isFriend(self):
+        if keydb.userinfo.set_peer_info(self.publicKey, 'trust') == 1:
+            return True
+        return False
+
+    def getName(self):
+        retData = 'anonymous'
+        name = keydb.userinfo.get_user_info(self.publicKey, 'name')
+        try:
+            if len(name) > 0:
+                retData = name
+        except ValueError:
+            pass
+        return retData
+
+    def encrypt(self, data):
+        encrypted = onionrcrypto.encryption.pub_key_encrypt(data, self.publicKey, encodedData=True)
+        return encrypted
+
+    def decrypt(self, data):
+        decrypted = onionrcrypto.encryption.pub_key_decrypt(data, self.publicKey, encodedData=True)
+        return decrypted
+
+    def forwardEncrypt(self, data):
+        deleteTheirExpiredKeys(self.publicKey)
+        deleteExpiredKeys()
+        retData = ''
+        forwardKey = self._getLatestForwardKey()
+        if stringvalidators.validate_pub_key(forwardKey[0]):
+            retData = onionrcrypto.encryption.pub_key_encrypt(data, forwardKey[0], encodedData=True)
+        else:
+            raise onionrexceptions.InvalidPubkey("No valid forward secrecy key available for this user")
+        #self.generateForwardKey()
+        return (retData, forwardKey[0], forwardKey[1])
+
+    def forwardDecrypt(self, encrypted):
+        retData = ""
+        for key in self.getGeneratedForwardKeys(False):
+            try:
+                retData = onionrcrypto.encryption.pub_key_decrypt(encrypted, privkey=key[1], encodedData=True)
+            except nacl.exceptions.CryptoError:
+                retData = False
+            else:
+                break
+        else:
+            raise onionrexceptions.DecryptionError("Could not decrypt forward secrecy content")
+        return retData
+
+    def _getLatestForwardKey(self):
+        # Get the latest forward secrecy key for a peer
+        key = ""
+        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+        c = conn.cursor()
+
+        # TODO: account for keys created at the same time (same epoch)
+        for row in c.execute("SELECT forwardKey, max(EXPIRE) FROM forwardKeys WHERE peerKey = ? ORDER BY expire DESC", (self.publicKey,)):
+            key = (row[0], row[1])
+            break
+
+        conn.commit()
+        conn.close()
+
+        return key
+
+    def _getForwardKeys(self):
+        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+        c = conn.cursor()
+        keyList = []
+
+        for row in c.execute("SELECT forwardKey, date FROM forwardKeys WHERE peerKey = ? ORDER BY expire DESC", (self.publicKey,)):
+            keyList.append((row[0], row[1]))
+
+        conn.commit()
+        conn.close()
+
+        return list(keyList)
+
+    def generateForwardKey(self, expire=DEFAULT_KEY_EXPIRE):
+
+        # Generate a forward secrecy key for the peer
+        conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
+        c = conn.cursor()
+        # Prepare the insert
+        time = epoch.get_epoch()
+        newKeys = onionrcrypto.generate()
+        newPub = bytesconverter.bytes_to_str(newKeys[0])
+        newPriv = bytesconverter.bytes_to_str(newKeys[1])
+
+        command = (self.publicKey, newPub, newPriv, time, expire + time)
+
+        c.execute("INSERT INTO myForwardKeys VALUES(?, ?, ?, ?, ?);", command)
+
+        conn.commit()
+        conn.close()
+        return newPub
+
+    def getGeneratedForwardKeys(self, genNew=True):
+        # Fetch the keys we generated for the peer, that are still around
+        conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
+        c = conn.cursor()
+        pubkey = self.publicKey
+        pubkey = bytesconverter.bytes_to_str(pubkey)
+        command = (pubkey,)
+        keyList = [] # list of tuples containing pub, private for peer
+
+        for result in c.execute("SELECT * FROM myForwardKeys WHERE peer = ?", command):
+            keyList.append((result[1], result[2]))
+
+        if len(keyList) == 0:
+            if genNew:
+                self.generateForwardKey()
+                keyList = self.getGeneratedForwardKeys()
+        return list(keyList)
+
+    def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE):
+        newKey = bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(newKey)))
+        if not stringvalidators.validate_pub_key(newKey):
+            # Do not add if something went wrong with the key
+            raise onionrexceptions.InvalidPubkey(newKey)
+
+        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+        c = conn.cursor()
+
+        # Get the time we're inserting the key at
+        timeInsert = epoch.get_epoch()
+
+        # Look at our current keys for duplicate key data or time
+        for entry in self._getForwardKeys():
+            if entry[0] == newKey:
+                return False
+            if entry[1] == timeInsert:
+                timeInsert += 1
+                time.sleep(1) # Sleep if our time is the same in order to prevent duplicate time records
+
+        # Add a forward secrecy key for the peer
+        # Prepare the insert
+        command = (self.publicKey, newKey, timeInsert, timeInsert + expire)
+
+        c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command)
+
+        conn.commit()
+        conn.close()
+        return True
+    
+    @classmethod
+    def list_friends(cls):
+        friendList = []
+        for x in keydb.listkeys.list_peers(trust=1):
+            friendList.append(cls(x))
+        return list(friendList)
+
+
+
+
+
+
+
+

Functions

+
+
+def deleteExpiredKeys() +
+
+
+
+Source code +
def deleteExpiredKeys():
+    # Fetch the keys we generated for the peer, that are still around
+    conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
+    c = conn.cursor()
+
+    curTime = epoch.get_epoch()
+    c.execute("DELETE from myForwardKeys where expire <= ?", (curTime,))
+    conn.commit()
+    conn.execute("VACUUM")
+    conn.close()
+    return
+
+
+
+def deleteTheirExpiredKeys(pubkey) +
+
+
+
+Source code +
def deleteTheirExpiredKeys(pubkey):
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+    c = conn.cursor()
+
+    # Prepare the insert
+    command = (pubkey, epoch.get_epoch())
+
+    c.execute("DELETE from forwardKeys where peerKey = ? and expire <= ?", command)
+
+    conn.commit()
+    conn.close()
+
+
+
+
+
+

Classes

+
+
+class OnionrUser +(publicKey, saveUser=False) +
+
+

OnionrUser is an abstraction for "users" of the network.

+

Takes a base32 encoded ed25519 public key, and a bool saveUser +saveUser determines if we should add a user to our peer database or not.

+
+Source code +
class OnionrUser:
+
+    def __init__(self, publicKey, saveUser=False):
+        '''
+            OnionrUser is an abstraction for "users" of the network. 
+            
+            Takes a base32 encoded ed25519 public key, and a bool saveUser
+            saveUser determines if we should add a user to our peer database or not.
+        '''
+        publicKey = unpaddedbase32.repad(bytesconverter.str_to_bytes(publicKey)).decode()
+
+        self.trust = 0
+        self.publicKey = publicKey
+
+        if saveUser:
+            try:
+                keydb.addkeys.add_peer(publicKey)
+            except AssertionError:
+                pass
+
+        self.trust = keydb.userinfo.get_user_info(self.publicKey, 'trust')
+        return
+
+    def setTrust(self, newTrust):
+        '''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate'''
+        keydb.userinfo.set_user_info(self.publicKey, 'trust', newTrust)
+
+    def isFriend(self):
+        if keydb.userinfo.set_peer_info(self.publicKey, 'trust') == 1:
+            return True
+        return False
+
+    def getName(self):
+        retData = 'anonymous'
+        name = keydb.userinfo.get_user_info(self.publicKey, 'name')
+        try:
+            if len(name) > 0:
+                retData = name
+        except ValueError:
+            pass
+        return retData
+
+    def encrypt(self, data):
+        encrypted = onionrcrypto.encryption.pub_key_encrypt(data, self.publicKey, encodedData=True)
+        return encrypted
+
+    def decrypt(self, data):
+        decrypted = onionrcrypto.encryption.pub_key_decrypt(data, self.publicKey, encodedData=True)
+        return decrypted
+
+    def forwardEncrypt(self, data):
+        deleteTheirExpiredKeys(self.publicKey)
+        deleteExpiredKeys()
+        retData = ''
+        forwardKey = self._getLatestForwardKey()
+        if stringvalidators.validate_pub_key(forwardKey[0]):
+            retData = onionrcrypto.encryption.pub_key_encrypt(data, forwardKey[0], encodedData=True)
+        else:
+            raise onionrexceptions.InvalidPubkey("No valid forward secrecy key available for this user")
+        #self.generateForwardKey()
+        return (retData, forwardKey[0], forwardKey[1])
+
+    def forwardDecrypt(self, encrypted):
+        retData = ""
+        for key in self.getGeneratedForwardKeys(False):
+            try:
+                retData = onionrcrypto.encryption.pub_key_decrypt(encrypted, privkey=key[1], encodedData=True)
+            except nacl.exceptions.CryptoError:
+                retData = False
+            else:
+                break
+        else:
+            raise onionrexceptions.DecryptionError("Could not decrypt forward secrecy content")
+        return retData
+
+    def _getLatestForwardKey(self):
+        # Get the latest forward secrecy key for a peer
+        key = ""
+        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+        c = conn.cursor()
+
+        # TODO: account for keys created at the same time (same epoch)
+        for row in c.execute("SELECT forwardKey, max(EXPIRE) FROM forwardKeys WHERE peerKey = ? ORDER BY expire DESC", (self.publicKey,)):
+            key = (row[0], row[1])
+            break
+
+        conn.commit()
+        conn.close()
+
+        return key
+
+    def _getForwardKeys(self):
+        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+        c = conn.cursor()
+        keyList = []
+
+        for row in c.execute("SELECT forwardKey, date FROM forwardKeys WHERE peerKey = ? ORDER BY expire DESC", (self.publicKey,)):
+            keyList.append((row[0], row[1]))
+
+        conn.commit()
+        conn.close()
+
+        return list(keyList)
+
+    def generateForwardKey(self, expire=DEFAULT_KEY_EXPIRE):
+
+        # Generate a forward secrecy key for the peer
+        conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
+        c = conn.cursor()
+        # Prepare the insert
+        time = epoch.get_epoch()
+        newKeys = onionrcrypto.generate()
+        newPub = bytesconverter.bytes_to_str(newKeys[0])
+        newPriv = bytesconverter.bytes_to_str(newKeys[1])
+
+        command = (self.publicKey, newPub, newPriv, time, expire + time)
+
+        c.execute("INSERT INTO myForwardKeys VALUES(?, ?, ?, ?, ?);", command)
+
+        conn.commit()
+        conn.close()
+        return newPub
+
+    def getGeneratedForwardKeys(self, genNew=True):
+        # Fetch the keys we generated for the peer, that are still around
+        conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
+        c = conn.cursor()
+        pubkey = self.publicKey
+        pubkey = bytesconverter.bytes_to_str(pubkey)
+        command = (pubkey,)
+        keyList = [] # list of tuples containing pub, private for peer
+
+        for result in c.execute("SELECT * FROM myForwardKeys WHERE peer = ?", command):
+            keyList.append((result[1], result[2]))
+
+        if len(keyList) == 0:
+            if genNew:
+                self.generateForwardKey()
+                keyList = self.getGeneratedForwardKeys()
+        return list(keyList)
+
+    def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE):
+        newKey = bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(newKey)))
+        if not stringvalidators.validate_pub_key(newKey):
+            # Do not add if something went wrong with the key
+            raise onionrexceptions.InvalidPubkey(newKey)
+
+        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+        c = conn.cursor()
+
+        # Get the time we're inserting the key at
+        timeInsert = epoch.get_epoch()
+
+        # Look at our current keys for duplicate key data or time
+        for entry in self._getForwardKeys():
+            if entry[0] == newKey:
+                return False
+            if entry[1] == timeInsert:
+                timeInsert += 1
+                time.sleep(1) # Sleep if our time is the same in order to prevent duplicate time records
+
+        # Add a forward secrecy key for the peer
+        # Prepare the insert
+        command = (self.publicKey, newKey, timeInsert, timeInsert + expire)
+
+        c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command)
+
+        conn.commit()
+        conn.close()
+        return True
+    
+    @classmethod
+    def list_friends(cls):
+        friendList = []
+        for x in keydb.listkeys.list_peers(trust=1):
+            friendList.append(cls(x))
+        return list(friendList)
+
+

Static methods

+
+
+def list_friends() +
+
+
+
+Source code +
@classmethod
+def list_friends(cls):
+    friendList = []
+    for x in keydb.listkeys.list_peers(trust=1):
+        friendList.append(cls(x))
+    return list(friendList)
+
+
+
+

Methods

+
+
+def addForwardKey(self, newKey, expire=604800) +
+
+
+
+Source code +
def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE):
+    newKey = bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(newKey)))
+    if not stringvalidators.validate_pub_key(newKey):
+        # Do not add if something went wrong with the key
+        raise onionrexceptions.InvalidPubkey(newKey)
+
+    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
+    c = conn.cursor()
+
+    # Get the time we're inserting the key at
+    timeInsert = epoch.get_epoch()
+
+    # Look at our current keys for duplicate key data or time
+    for entry in self._getForwardKeys():
+        if entry[0] == newKey:
+            return False
+        if entry[1] == timeInsert:
+            timeInsert += 1
+            time.sleep(1) # Sleep if our time is the same in order to prevent duplicate time records
+
+    # Add a forward secrecy key for the peer
+    # Prepare the insert
+    command = (self.publicKey, newKey, timeInsert, timeInsert + expire)
+
+    c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command)
+
+    conn.commit()
+    conn.close()
+    return True
+
+
+
+def decrypt(self, data) +
+
+
+
+Source code +
def decrypt(self, data):
+    decrypted = onionrcrypto.encryption.pub_key_decrypt(data, self.publicKey, encodedData=True)
+    return decrypted
+
+
+
+def encrypt(self, data) +
+
+
+
+Source code +
def encrypt(self, data):
+    encrypted = onionrcrypto.encryption.pub_key_encrypt(data, self.publicKey, encodedData=True)
+    return encrypted
+
+
+
+def forwardDecrypt(self, encrypted) +
+
+
+
+Source code +
def forwardDecrypt(self, encrypted):
+    retData = ""
+    for key in self.getGeneratedForwardKeys(False):
+        try:
+            retData = onionrcrypto.encryption.pub_key_decrypt(encrypted, privkey=key[1], encodedData=True)
+        except nacl.exceptions.CryptoError:
+            retData = False
+        else:
+            break
+    else:
+        raise onionrexceptions.DecryptionError("Could not decrypt forward secrecy content")
+    return retData
+
+
+
+def forwardEncrypt(self, data) +
+
+
+
+Source code +
def forwardEncrypt(self, data):
+    deleteTheirExpiredKeys(self.publicKey)
+    deleteExpiredKeys()
+    retData = ''
+    forwardKey = self._getLatestForwardKey()
+    if stringvalidators.validate_pub_key(forwardKey[0]):
+        retData = onionrcrypto.encryption.pub_key_encrypt(data, forwardKey[0], encodedData=True)
+    else:
+        raise onionrexceptions.InvalidPubkey("No valid forward secrecy key available for this user")
+    #self.generateForwardKey()
+    return (retData, forwardKey[0], forwardKey[1])
+
+
+
+def generateForwardKey(self, expire=604800) +
+
+
+
+Source code +
def generateForwardKey(self, expire=DEFAULT_KEY_EXPIRE):
+
+    # Generate a forward secrecy key for the peer
+    conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
+    c = conn.cursor()
+    # Prepare the insert
+    time = epoch.get_epoch()
+    newKeys = onionrcrypto.generate()
+    newPub = bytesconverter.bytes_to_str(newKeys[0])
+    newPriv = bytesconverter.bytes_to_str(newKeys[1])
+
+    command = (self.publicKey, newPub, newPriv, time, expire + time)
+
+    c.execute("INSERT INTO myForwardKeys VALUES(?, ?, ?, ?, ?);", command)
+
+    conn.commit()
+    conn.close()
+    return newPub
+
+
+
+def getGeneratedForwardKeys(self, genNew=True) +
+
+
+
+Source code +
def getGeneratedForwardKeys(self, genNew=True):
+    # Fetch the keys we generated for the peer, that are still around
+    conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
+    c = conn.cursor()
+    pubkey = self.publicKey
+    pubkey = bytesconverter.bytes_to_str(pubkey)
+    command = (pubkey,)
+    keyList = [] # list of tuples containing pub, private for peer
+
+    for result in c.execute("SELECT * FROM myForwardKeys WHERE peer = ?", command):
+        keyList.append((result[1], result[2]))
+
+    if len(keyList) == 0:
+        if genNew:
+            self.generateForwardKey()
+            keyList = self.getGeneratedForwardKeys()
+    return list(keyList)
+
+
+
+def getName(self) +
+
+
+
+Source code +
def getName(self):
+    retData = 'anonymous'
+    name = keydb.userinfo.get_user_info(self.publicKey, 'name')
+    try:
+        if len(name) > 0:
+            retData = name
+    except ValueError:
+        pass
+    return retData
+
+
+
+def isFriend(self) +
+
+
+
+Source code +
def isFriend(self):
+    if keydb.userinfo.set_peer_info(self.publicKey, 'trust') == 1:
+        return True
+    return False
+
+
+
+def setTrust(self, newTrust) +
+
+

Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate

+
+Source code +
def setTrust(self, newTrust):
+    '''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate'''
+    keydb.userinfo.set_user_info(self.publicKey, 'trust', newTrust)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/basicrequests.html b/docs/html/onionr/onionrutils/basicrequests.html new file mode 100644 index 00000000..ff20a54c --- /dev/null +++ b/docs/html/onionr/onionrutils/basicrequests.html @@ -0,0 +1,251 @@ + + + + + + +onionr.onionrutils.basicrequests API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.basicrequests

+
+
+

Onionr - Private P2P Communication

+

Do HTTP GET or POST requests through a proxy

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Do HTTP GET or POST requests through a proxy
+'''
+'''
+    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/>.
+'''
+import requests, streamedrequests
+import logger, onionrexceptions
+from etc import onionrvalues
+from . import localcommand
+def do_post_request(url, data={}, port=0, proxyType='tor', max_size=10000, content_type: str = ''):
+    '''
+    Do a POST request through a local tor or i2p instance
+    '''
+    if proxyType == 'tor':
+        if port == 0:
+            port = localcommand.local_command('/gettorsocks')
+        proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
+    elif proxyType == 'i2p':
+        proxies = {'http': 'http://127.0.0.1:4444'}
+    else:
+        return
+    headers = {'User-Agent': 'PyOnionr', 'Connection':'close'}
+    if len(content_type) > 0:
+        headers['Content-Type'] = content_type
+    try:
+        proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
+        #r = requests.post(url, data=data, headers=headers, proxies=proxies, allow_redirects=False, timeout=(15, 30))
+        r = streamedrequests.post(url, post_data=data, request_headers=headers, proxy=proxies, connect_timeout=15, stream_timeout=30, max_size=max_size, allow_redirects=False)
+        retData = r[1]
+    except KeyboardInterrupt:
+        raise KeyboardInterrupt
+    except requests.exceptions.RequestException as e:
+        logger.debug('Error: %s' % str(e))
+        retData = False
+    return retData
+
+def do_get_request(url, port=0, proxyType='tor', ignoreAPI=False, returnHeaders=False, max_size=5242880):
+    '''
+    Do a get request through a local tor or i2p instance
+    '''
+    API_VERSION = onionrvalues.API_VERSION
+    retData = False
+    if proxyType == 'tor':
+        if port == 0:
+            raise onionrexceptions.MissingPort('Socks port required for Tor HTTP get request')
+        proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
+    elif proxyType == 'i2p':
+        proxies = {'http': 'http://127.0.0.1:4444'}
+    else:
+        return
+    headers = {'User-Agent': 'PyOnionr', 'Connection':'close'}
+    response_headers = dict()
+    try:
+        proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
+        r = streamedrequests.get(url, request_headers=headers, allow_redirects=False, proxy=proxies, connect_timeout=15, stream_timeout=120, max_size=max_size)
+        # Check server is using same API version as us
+        if not ignoreAPI:
+            try:
+                response_headers = r[0].headers
+                if r[0].headers['X-API'] != str(API_VERSION):
+                    raise onionrexceptions.InvalidAPIVersion
+            except KeyError:
+                raise onionrexceptions.InvalidAPIVersion
+        retData = r[1]
+    except KeyboardInterrupt:
+        raise KeyboardInterrupt
+    except ValueError as e:
+        pass
+    except onionrexceptions.InvalidAPIVersion:
+        if 'X-API' in response_headers:
+            logger.debug('Using API version %s. Cannot communicate with node\'s API version of %s.' % (API_VERSION, response_headers['X-API']))
+        else:
+            logger.debug('Using API version %s. API version was not sent with the request.' % API_VERSION)
+    except requests.exceptions.RequestException as e:
+        if not 'ConnectTimeoutError' in str(e) and not 'Request rejected or failed' in str(e):
+            logger.debug('Error: %s' % str(e))
+        retData = False
+    if returnHeaders:
+        return (retData, response_headers)
+    else:
+        return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def do_get_request(url, port=0, proxyType='tor', ignoreAPI=False, returnHeaders=False, max_size=5242880) +
+
+

Do a get request through a local tor or i2p instance

+
+Source code +
def do_get_request(url, port=0, proxyType='tor', ignoreAPI=False, returnHeaders=False, max_size=5242880):
+    '''
+    Do a get request through a local tor or i2p instance
+    '''
+    API_VERSION = onionrvalues.API_VERSION
+    retData = False
+    if proxyType == 'tor':
+        if port == 0:
+            raise onionrexceptions.MissingPort('Socks port required for Tor HTTP get request')
+        proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
+    elif proxyType == 'i2p':
+        proxies = {'http': 'http://127.0.0.1:4444'}
+    else:
+        return
+    headers = {'User-Agent': 'PyOnionr', 'Connection':'close'}
+    response_headers = dict()
+    try:
+        proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
+        r = streamedrequests.get(url, request_headers=headers, allow_redirects=False, proxy=proxies, connect_timeout=15, stream_timeout=120, max_size=max_size)
+        # Check server is using same API version as us
+        if not ignoreAPI:
+            try:
+                response_headers = r[0].headers
+                if r[0].headers['X-API'] != str(API_VERSION):
+                    raise onionrexceptions.InvalidAPIVersion
+            except KeyError:
+                raise onionrexceptions.InvalidAPIVersion
+        retData = r[1]
+    except KeyboardInterrupt:
+        raise KeyboardInterrupt
+    except ValueError as e:
+        pass
+    except onionrexceptions.InvalidAPIVersion:
+        if 'X-API' in response_headers:
+            logger.debug('Using API version %s. Cannot communicate with node\'s API version of %s.' % (API_VERSION, response_headers['X-API']))
+        else:
+            logger.debug('Using API version %s. API version was not sent with the request.' % API_VERSION)
+    except requests.exceptions.RequestException as e:
+        if not 'ConnectTimeoutError' in str(e) and not 'Request rejected or failed' in str(e):
+            logger.debug('Error: %s' % str(e))
+        retData = False
+    if returnHeaders:
+        return (retData, response_headers)
+    else:
+        return retData
+
+
+
+def do_post_request(url, data={}, port=0, proxyType='tor', max_size=10000, content_type='') +
+
+

Do a POST request through a local tor or i2p instance

+
+Source code +
def do_post_request(url, data={}, port=0, proxyType='tor', max_size=10000, content_type: str = ''):
+    '''
+    Do a POST request through a local tor or i2p instance
+    '''
+    if proxyType == 'tor':
+        if port == 0:
+            port = localcommand.local_command('/gettorsocks')
+        proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
+    elif proxyType == 'i2p':
+        proxies = {'http': 'http://127.0.0.1:4444'}
+    else:
+        return
+    headers = {'User-Agent': 'PyOnionr', 'Connection':'close'}
+    if len(content_type) > 0:
+        headers['Content-Type'] = content_type
+    try:
+        proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
+        #r = requests.post(url, data=data, headers=headers, proxies=proxies, allow_redirects=False, timeout=(15, 30))
+        r = streamedrequests.post(url, post_data=data, request_headers=headers, proxy=proxies, connect_timeout=15, stream_timeout=30, max_size=max_size, allow_redirects=False)
+        retData = r[1]
+    except KeyboardInterrupt:
+        raise KeyboardInterrupt
+    except requests.exceptions.RequestException as e:
+        logger.debug('Error: %s' % str(e))
+        retData = False
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/blockmetadata/fromdata.html b/docs/html/onionr/onionrutils/blockmetadata/fromdata.html new file mode 100644 index 00000000..bf8e7cf1 --- /dev/null +++ b/docs/html/onionr/onionrutils/blockmetadata/fromdata.html @@ -0,0 +1,146 @@ + + + + + + +onionr.onionrutils.blockmetadata.fromdata API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.blockmetadata.fromdata

+
+
+

Onionr - Private P2P Communication

+

Return a useful tuple of (metadata (header), meta, and data) by accepting raw block data

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Return a useful tuple of (metadata (header), meta, and data) by accepting raw block data
+'''
+'''
+    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/>.
+'''
+
+import json
+
+from onionrutils import bytesconverter
+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 json.decoder.JSONDecodeError:
+        pass
+    else:
+        data = block_data[block_data.find(b'\n'):]
+
+        meta = metadata['meta']
+    return (metadata, meta, data)
+
+
+
+
+
+
+
+

Functions

+
+
+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).

+
+Source code +
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 json.decoder.JSONDecodeError:
+        pass
+    else:
+        data = block_data[block_data.find(b'\n'):]
+
+        meta = metadata['meta']
+    return (metadata, meta, data)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/blockmetadata/hasblock.html b/docs/html/onionr/onionrutils/blockmetadata/hasblock.html new file mode 100644 index 00000000..f76f5f28 --- /dev/null +++ b/docs/html/onionr/onionrutils/blockmetadata/hasblock.html @@ -0,0 +1,135 @@ + + + + + + +onionr.onionrutils.blockmetadata.hasblock API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.blockmetadata.hasblock

+
+
+

Onionr - Private P2P Communication

+

Returns a bool if a block is in the block metadata db or not

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Returns a bool if a block is in the block metadata db or not
+'''
+'''
+    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/>.
+'''
+import sqlite3
+from coredb import dbfiles
+import onionrexceptions
+from .. import stringvalidators
+
+def has_block(hash: str) -> bool:
+    '''
+        Check for new block in the block meta db
+    '''
+    conn = sqlite3.connect(dbfiles.block_meta_db)
+    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
+
+
+
+
+
+
+
+

Functions

+
+
+def has_block(hash) +
+
+

Check for new block in the block meta db

+
+Source code +
def has_block(hash: str) -> bool:
+    '''
+        Check for new block in the block meta db
+    '''
+    conn = sqlite3.connect(dbfiles.block_meta_db)
+    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
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/blockmetadata/index.html b/docs/html/onionr/onionrutils/blockmetadata/index.html new file mode 100644 index 00000000..09ab2d56 --- /dev/null +++ b/docs/html/onionr/onionrutils/blockmetadata/index.html @@ -0,0 +1,104 @@ + + + + + + +onionr.onionrutils.blockmetadata API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.blockmetadata

+
+
+

Onionr - Private P2P Communication

+

Module to work with block metadata

+
+Source code +
'''
+    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
+
+
+
+

Sub-modules

+
+
onionr.onionrutils.blockmetadata.fromdata
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.blockmetadata.hasblock
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.blockmetadata.process
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/blockmetadata/process.html b/docs/html/onionr/onionrutils/blockmetadata/process.html new file mode 100644 index 00000000..77150364 --- /dev/null +++ b/docs/html/onionr/onionrutils/blockmetadata/process.html @@ -0,0 +1,187 @@ + + + + + + +onionr.onionrutils.blockmetadata.process API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.blockmetadata.process

+
+
+

Onionr - Private P2P Communication

+

Process block metadata with relevant actions

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Process block metadata with relevant actions
+'''
+'''
+    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 etc import onionrvalues
+from onionrblocks import onionrblockapi
+from .. import epoch, bytesconverter
+from coredb import blockmetadb
+import logger
+from onionrplugins import onionrevents
+import onionrexceptions
+from onionrusers import onionrusers
+
+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)
+
+        onionrevents.event('processblocks', data = {'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid})
+
+
+
+
+
+
+
+

Functions

+
+
+def process_block_metadata(blockHash) +
+
+

Read metadata from a block and cache it to the block database

+

blockHash -> sha3_256 hex formatted hash of Onionr block

+
+Source code +
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)
+
+        onionrevents.event('processblocks', data = {'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid})
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/bytesconverter.html b/docs/html/onionr/onionrutils/bytesconverter.html new file mode 100644 index 00000000..54be16e6 --- /dev/null +++ b/docs/html/onionr/onionrutils/bytesconverter.html @@ -0,0 +1,110 @@ + + + + + + +onionr.onionrutils.bytesconverter API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.bytesconverter

+
+
+
+Source code +
def str_to_bytes(data):
+    '''Converts a string to bytes with .encode()'''
+    try:
+        data = data.encode('UTF-8')
+    except AttributeError:
+        pass
+    return data
+
+def bytes_to_str(data):
+    try:
+        data = data.decode('UTF-8')
+    except AttributeError:
+        pass
+    return data
+
+
+
+
+
+
+
+

Functions

+
+
+def bytes_to_str(data) +
+
+
+
+Source code +
def bytes_to_str(data):
+    try:
+        data = data.decode('UTF-8')
+    except AttributeError:
+        pass
+    return data
+
+
+
+def str_to_bytes(data) +
+
+

Converts a string to bytes with .encode()

+
+Source code +
def str_to_bytes(data):
+    '''Converts a string to bytes with .encode()'''
+    try:
+        data = data.encode('UTF-8')
+    except AttributeError:
+        pass
+    return data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/checkcommunicator.html b/docs/html/onionr/onionrutils/checkcommunicator.html new file mode 100644 index 00000000..80dda1a0 --- /dev/null +++ b/docs/html/onionr/onionrutils/checkcommunicator.html @@ -0,0 +1,132 @@ + + + + + + +onionr.onionrutils.checkcommunicator API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.checkcommunicator

+
+
+

Onionr - Private P2P Communication

+

Check if the communicator is running

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Check if the communicator is running
+'''
+'''
+    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/>.
+'''
+import time, os
+import filepaths
+def is_communicator_running(timeout = 5, interval = 0.1):
+    try:
+        runcheck_file = filepaths.run_check_file
+
+        if not os.path.isfile(runcheck_file):
+            open(runcheck_file, 'w+').close()
+
+        starttime = time.time()
+
+        while True:
+            time.sleep(interval)
+
+            if not os.path.isfile(runcheck_file):
+                return True
+            elif time.time() - starttime >= timeout:
+                return False
+    except:
+        return False
+
+
+
+
+
+
+
+

Functions

+
+
+def is_communicator_running(timeout=5, interval=0.1) +
+
+
+
+Source code +
def is_communicator_running(timeout = 5, interval = 0.1):
+    try:
+        runcheck_file = filepaths.run_check_file
+
+        if not os.path.isfile(runcheck_file):
+            open(runcheck_file, 'w+').close()
+
+        starttime = time.time()
+
+        while True:
+            time.sleep(interval)
+
+            if not os.path.isfile(runcheck_file):
+                return True
+            elif time.time() - starttime >= timeout:
+                return False
+    except:
+        return False
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/epoch.html b/docs/html/onionr/onionrutils/epoch.html new file mode 100644 index 00000000..7a687fc8 --- /dev/null +++ b/docs/html/onionr/onionrutils/epoch.html @@ -0,0 +1,124 @@ + + + + + + +onionr.onionrutils.epoch API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.epoch

+
+
+

Onionr - Private P2P Communication

+

Get floored epoch, or rounded epoch

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Get floored epoch, or rounded 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/>.
+'''
+import math, time
+def get_rounded_epoch(roundS=60):
+    '''
+        Returns the epoch, rounded down to given seconds (Default 60)
+    '''
+    epoch = get_epoch()
+    return epoch - (epoch % roundS)
+
+def get_epoch():
+    '''returns epoch'''
+    return math.floor(time.time())
+
+
+
+
+
+
+
+

Functions

+
+
+def get_epoch() +
+
+

returns epoch

+
+Source code +
def get_epoch():
+    '''returns epoch'''
+    return math.floor(time.time())
+
+
+
+def get_rounded_epoch(roundS=60) +
+
+

Returns the epoch, rounded down to given seconds (Default 60)

+
+Source code +
def get_rounded_epoch(roundS=60):
+    '''
+        Returns the epoch, rounded down to given seconds (Default 60)
+    '''
+    epoch = get_epoch()
+    return epoch - (epoch % roundS)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/escapeansi.html b/docs/html/onionr/onionrutils/escapeansi.html new file mode 100644 index 00000000..720ac31d --- /dev/null +++ b/docs/html/onionr/onionrutils/escapeansi.html @@ -0,0 +1,94 @@ + + + + + + +onionr.onionrutils.escapeansi API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.escapeansi

+
+
+
+Source code +
import re
+def escape_ANSI(line):
+    '''
+        Remove ANSI escape codes from a string with regex
+
+        adapted from: https://stackoverflow.com/a/38662876 by user https://stackoverflow.com/users/802365/%c3%89douard-lopez
+        cc-by-sa-3 license https://creativecommons.org/licenses/by-sa/3.0/
+    '''
+    ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
+    return ansi_escape.sub('', line)
+
+
+
+
+
+
+
+

Functions

+
+
+def escape_ANSI(line) +
+
+

Remove ANSI escape codes from a string with regex

+

adapted from: https://stackoverflow.com/a/38662876 by user https://stackoverflow.com/users/802365/%c3%89douard-lopez +cc-by-sa-3 license https://creativecommons.org/licenses/by-sa/3.0/

+
+Source code +
def escape_ANSI(line):
+    '''
+        Remove ANSI escape codes from a string with regex
+
+        adapted from: https://stackoverflow.com/a/38662876 by user https://stackoverflow.com/users/802365/%c3%89douard-lopez
+        cc-by-sa-3 license https://creativecommons.org/licenses/by-sa/3.0/
+    '''
+    ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
+    return ansi_escape.sub('', line)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/getclientapiserver.html b/docs/html/onionr/onionrutils/getclientapiserver.html new file mode 100644 index 00000000..7e51e0c3 --- /dev/null +++ b/docs/html/onionr/onionrutils/getclientapiserver.html @@ -0,0 +1,128 @@ + + + + + + +onionr.onionrutils.getclientapiserver API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.getclientapiserver

+
+
+

Onionr - Private P2P Communication

+

Return the client api server address and port, which is usually random

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Return the client api server address and port, which is usually random
+'''
+'''
+    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/>.
+'''
+import filepaths
+import config
+def get_client_API_server():
+    config.reload()
+    retData = ''
+    getconf = lambda: config.get('client.client.port')
+    port = getconf()
+    if port is None:
+        config.reload()
+        port = getconf()
+    try:
+        with open(filepaths.private_API_host_file, 'r') as host:
+            hostname = host.read()
+    except FileNotFoundError:
+        raise FileNotFoundError
+    else:
+        retData += '%s:%s' % (hostname, port)
+    return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def get_client_API_server() +
+
+
+
+Source code +
def get_client_API_server():
+    config.reload()
+    retData = ''
+    getconf = lambda: config.get('client.client.port')
+    port = getconf()
+    if port is None:
+        config.reload()
+        port = getconf()
+    try:
+        with open(filepaths.private_API_host_file, 'r') as host:
+            hostname = host.read()
+    except FileNotFoundError:
+        raise FileNotFoundError
+    else:
+        retData += '%s:%s' % (hostname, port)
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/importnewblocks.html b/docs/html/onionr/onionrutils/importnewblocks.html new file mode 100644 index 00000000..28bb6fb5 --- /dev/null +++ b/docs/html/onionr/onionrutils/importnewblocks.html @@ -0,0 +1,150 @@ + + + + + + +onionr.onionrutils.importnewblocks API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.importnewblocks

+
+
+

Onionr - Private P2P Communication

+

import new blocks from disk, providing transport agnosticism

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    import new blocks from disk, providing transport agnosticism
+'''
+'''
+    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/>.
+'''
+import glob
+import logger
+from onionrutils import blockmetadata
+from coredb import blockmetadb
+import filepaths
+import onionrcrypto as crypto
+def import_new_blocks(scanDir=''):
+    '''
+        This function is intended to 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 + "*.dat"):
+        if block.replace(scanDir, '').replace('.dat', '') 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('.dat', '')
+                if crypto.hashers.sha3_hash(newBlock.read()) == block.replace('.dat', ''):
+                    blockmetadb.add_to_block_DB(block.replace('.dat', ''), 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"Scans the Onionr data directory under {filepaths.block_data_location} for new block files (.dat, .db not supported) to import"
+
+
+
+
+
+
+
+

Functions

+
+
+def import_new_blocks(scanDir='') +
+
+

This function is intended to scan for new blocks ON THE DISK and import them

+
+Source code +
def import_new_blocks(scanDir=''):
+    '''
+        This function is intended to 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 + "*.dat"):
+        if block.replace(scanDir, '').replace('.dat', '') 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('.dat', '')
+                if crypto.hashers.sha3_hash(newBlock.read()) == block.replace('.dat', ''):
+                    blockmetadb.add_to_block_DB(block.replace('.dat', ''), 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)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/index.html b/docs/html/onionr/onionrutils/index.html new file mode 100644 index 00000000..d389bc96 --- /dev/null +++ b/docs/html/onionr/onionrutils/index.html @@ -0,0 +1,120 @@ + + + + + + +onionr.onionrutils API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils

+
+
+
+
+

Sub-modules

+
+
onionr.onionrutils.basicrequests
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.blockmetadata
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.bytesconverter
+
+
+
+
onionr.onionrutils.checkcommunicator
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.epoch
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.escapeansi
+
+
+
+
onionr.onionrutils.getclientapiserver
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.importnewblocks
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.localcommand
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.mnemonickeys
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.stringvalidators
+
+

Onionr - Private P2P Communication …

+
+
onionr.onionrutils.validatemetadata
+
+

Onionr - Private P2P Communication …

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/localcommand.html b/docs/html/onionr/onionrutils/localcommand.html new file mode 100644 index 00000000..c4c4e360 --- /dev/null +++ b/docs/html/onionr/onionrutils/localcommand.html @@ -0,0 +1,198 @@ + + + + + + +onionr.onionrutils.localcommand API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.localcommand

+
+
+

Onionr - Private P2P Communication

+

send a command to the local API server

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    send a command to the local API server
+'''
+'''
+    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/>.
+'''
+import urllib, requests, time
+import logger, config, deadsimplekv
+from . import getclientapiserver
+import filepaths
+config.reload()
+
+cache = deadsimplekv.DeadSimpleKV(filepaths.cached_storage, refresh_seconds=1000)
+def get_hostname():
+    hostname = ''
+    waited = 0
+    maxWait = 3
+    while True:
+        if cache.get('client_api') is None:
+            try:
+                hostname = getclientapiserver.get_client_API_server()
+            except FileNotFoundError:
+                hostname = False
+            else:
+                cache.put('hostname', hostname)
+                cache.flush()
+        else:
+            hostname = cache.get('hostname')
+        if hostname == '' or hostname is None:
+            time.sleep(1)
+            if waited == maxWait:
+                return False
+        else:
+            return hostname
+
+def local_command(command, data='', silent = True, post=False, postData = {}, maxWait=20):
+    '''
+        Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers.
+    '''
+    # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless.
+    hostname = get_hostname()
+    if hostname == False: return False
+    if data != '':
+        data = '&data=' + urllib.parse.quote_plus(data)
+    payload = 'http://%s/%s%s' % (hostname, command, data)
+
+    try:
+        if post:
+            ret_data = requests.post(payload, data=postData, headers={'token': config.get('client.webpassword'), 'Connection':'close'}, timeout=(maxWait, maxWait)).text
+        else:
+            ret_data = requests.get(payload, headers={'token': config.get('client.webpassword'), 'Connection':'close'}, timeout=(maxWait, maxWait)).text
+    except Exception as error:
+        if not silent:
+            logger.error('Failed to make local request (command: %s):%s' % (command, error), terminal=True)
+        ret_data = False
+
+    return ret_data
+
+
+
+
+
+
+
+

Functions

+
+
+def get_hostname() +
+
+
+
+Source code +
def get_hostname():
+    hostname = ''
+    waited = 0
+    maxWait = 3
+    while True:
+        if cache.get('client_api') is None:
+            try:
+                hostname = getclientapiserver.get_client_API_server()
+            except FileNotFoundError:
+                hostname = False
+            else:
+                cache.put('hostname', hostname)
+                cache.flush()
+        else:
+            hostname = cache.get('hostname')
+        if hostname == '' or hostname is None:
+            time.sleep(1)
+            if waited == maxWait:
+                return False
+        else:
+            return hostname
+
+
+
+def local_command(command, data='', silent=True, post=False, postData={}, maxWait=20) +
+
+

Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers.

+
+Source code +
def local_command(command, data='', silent = True, post=False, postData = {}, maxWait=20):
+    '''
+        Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers.
+    '''
+    # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless.
+    hostname = get_hostname()
+    if hostname == False: return False
+    if data != '':
+        data = '&data=' + urllib.parse.quote_plus(data)
+    payload = 'http://%s/%s%s' % (hostname, command, data)
+
+    try:
+        if post:
+            ret_data = requests.post(payload, data=postData, headers={'token': config.get('client.webpassword'), 'Connection':'close'}, timeout=(maxWait, maxWait)).text
+        else:
+            ret_data = requests.get(payload, headers={'token': config.get('client.webpassword'), 'Connection':'close'}, timeout=(maxWait, maxWait)).text
+    except Exception as error:
+        if not silent:
+            logger.error('Failed to make local request (command: %s):%s' % (command, error), terminal=True)
+        ret_data = False
+
+    return ret_data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/mnemonickeys.html b/docs/html/onionr/onionrutils/mnemonickeys.html new file mode 100644 index 00000000..149314ad --- /dev/null +++ b/docs/html/onionr/onionrutils/mnemonickeys.html @@ -0,0 +1,139 @@ + + + + + + +onionr.onionrutils.mnemonickeys API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.mnemonickeys

+
+
+

Onionr - Private P2P Communication

+

convert a base32 string (intended for ed25519 user ids) to pgp word list

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    convert a base32 string (intended for ed25519 user ids) to pgp word list
+'''
+'''
+    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/>.
+'''
+import base64
+
+import mnemonic
+import unpaddedbase32
+
+import onionrcrypto
+from etc import onionrvalues
+
+m = mnemonic.Mnemonic('english')
+
+def get_human_readable_ID(pub=''):
+    '''gets a human readable ID from a public key'''
+    if pub == '':
+        pub = onionrcrypto.pub_key
+    
+    if not len(pub) == onionrvalues.MAIN_PUBLIC_KEY_SIZE:
+        pub = base64.b32decode(pub)
+    
+    return m.to_mnemonic(pub).replace(' ', '-')
+
+def get_base32(words):
+    '''converts mnemonic to base32'''
+    return unpaddedbase32.b32encode(m.to_entropy(words.replace('-', ' ')))
+
+
+
+
+
+
+
+

Functions

+
+
+def get_base32(words) +
+
+

converts mnemonic to base32

+
+Source code +
def get_base32(words):
+    '''converts mnemonic to base32'''
+    return unpaddedbase32.b32encode(m.to_entropy(words.replace('-', ' ')))
+
+
+
+def get_human_readable_ID(pub='') +
+
+

gets a human readable ID from a public key

+
+Source code +
def get_human_readable_ID(pub=''):
+    '''gets a human readable ID from a public key'''
+    if pub == '':
+        pub = onionrcrypto.pub_key
+    
+    if not len(pub) == onionrvalues.MAIN_PUBLIC_KEY_SIZE:
+        pub = base64.b32decode(pub)
+    
+    return m.to_mnemonic(pub).replace(' ', '-')
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/stringvalidators.html b/docs/html/onionr/onionrutils/stringvalidators.html new file mode 100644 index 00000000..d0b95b9a --- /dev/null +++ b/docs/html/onionr/onionrutils/stringvalidators.html @@ -0,0 +1,319 @@ + + + + + + +onionr.onionrutils.stringvalidators API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.stringvalidators

+
+
+

Onionr - Private P2P Communication

+

validate various string data types

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    validate various string data types
+'''
+'''
+    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/>.
+'''
+import base64, string
+import unpaddedbase32, nacl.signing, nacl.encoding
+from onionrutils import bytesconverter
+def validate_hash(data, length=64):
+    '''
+        Validate if a string is a valid hash hex digest (does not compare, just checks length and charset)
+
+        Length is only invalid if its *more* than the specified
+    '''
+    retVal = True
+    if data == False or data == True:
+        return False
+    data = data.strip()
+    if len(data) > length:
+        retVal = False
+    else:
+        try:
+            int(data, 16)
+        except ValueError:
+            retVal = False
+
+    return retVal
+
+def validate_pub_key(key):
+    '''
+        Validate if a string is a valid base32 encoded Ed25519 key
+    '''
+    if type(key) is type(None):
+        return False
+    # Accept keys that have no = padding
+    key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key))
+
+    retVal = False
+    try:
+        nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder)
+    except nacl.exceptions.ValueError:
+        pass
+    except base64.binascii.Error as err:
+        pass
+    else:
+        retVal = True
+    return retVal
+
+def validate_transport(id):
+    try:
+        idLength = len(id)
+        retVal = True
+        idNoDomain = ''
+        peerType = ''
+        # i2p b32 addresses are 60 characters long (including .b32.i2p)
+        if idLength == 60:
+            peerType = 'i2p'
+            if not id.endswith('.b32.i2p'):
+                retVal = False
+            else:
+                idNoDomain = id.split('.b32.i2p')[0]
+        # Onion v2's are 22 (including .onion), v3's are 62 with .onion
+        elif idLength == 22 or idLength == 62:
+            peerType = 'onion'
+            if not id.endswith('.onion'):
+                retVal = False
+            else:
+                idNoDomain = id.split('.onion')[0]
+        else:
+            retVal = False
+        if retVal:
+            if peerType == 'i2p':
+                try:
+                    id.split('.b32.i2p')[2]
+                except IndexError:
+                    pass
+                else:
+                    retVal = False
+            elif peerType == 'onion':
+                try:
+                    id.split('.onion')[2]
+                except IndexError:
+                    pass
+                else:
+                    retVal = False
+            if not idNoDomain.isalnum():
+                retVal = False
+
+            # Validate address is valid base32 (when capitalized and minus extension); v2/v3 onions and .b32.i2p use base32
+            for x in idNoDomain.upper():
+                if x not in string.ascii_uppercase and x not in '234567':
+                    retVal = False
+
+        return retVal
+    except Exception as e:
+        return False
+
+def is_integer_string(data):
+    '''Check if a string is a valid base10 integer (also returns true if already an int)'''
+    try:
+        int(data)
+    except (ValueError, TypeError) as e:
+        return False
+    else:
+        return True
+
+
+
+
+
+
+
+

Functions

+
+
+def is_integer_string(data) +
+
+

Check if a string is a valid base10 integer (also returns true if already an int)

+
+Source code +
def is_integer_string(data):
+    '''Check if a string is a valid base10 integer (also returns true if already an int)'''
+    try:
+        int(data)
+    except (ValueError, TypeError) as e:
+        return False
+    else:
+        return True
+
+
+
+def validate_hash(data, length=64) +
+
+

Validate if a string is a valid hash hex digest (does not compare, just checks length and charset)

+

Length is only invalid if its more than the specified

+
+Source code +
def validate_hash(data, length=64):
+    '''
+        Validate if a string is a valid hash hex digest (does not compare, just checks length and charset)
+
+        Length is only invalid if its *more* than the specified
+    '''
+    retVal = True
+    if data == False or data == True:
+        return False
+    data = data.strip()
+    if len(data) > length:
+        retVal = False
+    else:
+        try:
+            int(data, 16)
+        except ValueError:
+            retVal = False
+
+    return retVal
+
+
+
+def validate_pub_key(key) +
+
+

Validate if a string is a valid base32 encoded Ed25519 key

+
+Source code +
def validate_pub_key(key):
+    '''
+        Validate if a string is a valid base32 encoded Ed25519 key
+    '''
+    if type(key) is type(None):
+        return False
+    # Accept keys that have no = padding
+    key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key))
+
+    retVal = False
+    try:
+        nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder)
+    except nacl.exceptions.ValueError:
+        pass
+    except base64.binascii.Error as err:
+        pass
+    else:
+        retVal = True
+    return retVal
+
+
+
+def validate_transport(id) +
+
+
+
+Source code +
def validate_transport(id):
+    try:
+        idLength = len(id)
+        retVal = True
+        idNoDomain = ''
+        peerType = ''
+        # i2p b32 addresses are 60 characters long (including .b32.i2p)
+        if idLength == 60:
+            peerType = 'i2p'
+            if not id.endswith('.b32.i2p'):
+                retVal = False
+            else:
+                idNoDomain = id.split('.b32.i2p')[0]
+        # Onion v2's are 22 (including .onion), v3's are 62 with .onion
+        elif idLength == 22 or idLength == 62:
+            peerType = 'onion'
+            if not id.endswith('.onion'):
+                retVal = False
+            else:
+                idNoDomain = id.split('.onion')[0]
+        else:
+            retVal = False
+        if retVal:
+            if peerType == 'i2p':
+                try:
+                    id.split('.b32.i2p')[2]
+                except IndexError:
+                    pass
+                else:
+                    retVal = False
+            elif peerType == 'onion':
+                try:
+                    id.split('.onion')[2]
+                except IndexError:
+                    pass
+                else:
+                    retVal = False
+            if not idNoDomain.isalnum():
+                retVal = False
+
+            # Validate address is valid base32 (when capitalized and minus extension); v2/v3 onions and .b32.i2p use base32
+            for x in idNoDomain.upper():
+                if x not in string.ascii_uppercase and x not in '234567':
+                    retVal = False
+
+        return retVal
+    except Exception as e:
+        return False
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/onionrutils/validatemetadata.html b/docs/html/onionr/onionrutils/validatemetadata.html new file mode 100644 index 00000000..934d9523 --- /dev/null +++ b/docs/html/onionr/onionrutils/validatemetadata.html @@ -0,0 +1,283 @@ + + + + + + +onionr.onionrutils.validatemetadata API documentation + + + + + + + + + +
+
+
+

Module onionr.onionrutils.validatemetadata

+
+
+

Onionr - Private P2P Communication

+

validate new block's metadata

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    validate new block's 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/>.
+'''
+import json
+import logger, onionrexceptions
+from etc import onionrvalues
+from onionrutils import stringvalidators, epoch, bytesconverter
+import config, filepaths, onionrcrypto
+def validate_metadata(metadata, block_data) -> bool:
+    '''Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string'''
+
+    ret_data = False
+    max_clock_difference = onionrvalues.MAX_BLOCK_CLOCK_SKEW
+
+    # convert to dict if it is json string
+    if type(metadata) is str:
+        try:
+            metadata = json.loads(metadata)
+        except json.JSONDecodeError:
+            pass
+
+    # Validate metadata dict for invalid keys to sizes that are too large
+    maxAge = min(config.get("general.max_block_age", onionrvalues.DEFAULT_EXPIRE), onionrvalues.DEFAULT_EXPIRE)
+    if type(metadata) is dict:
+        for i in metadata:
+            try:
+                onionrvalues.BLOCK_METADATA_LENGTHS[i]
+            except KeyError:
+                logger.warn('Block has invalid metadata key ' + i)
+                break
+            else:
+                testData = metadata[i]
+                try:
+                    testData = len(testData)
+                except (TypeError, AttributeError) as e:
+                    testData = len(str(testData))
+                if onionrvalues.BLOCK_METADATA_LENGTHS[i] < testData:
+                    logger.warn('Block metadata key ' + i + ' exceeded maximum size')
+                    break
+            if i == 'time':
+                if not stringvalidators.is_integer_string(metadata[i]):
+                    logger.warn('Block metadata time stamp is not integer string or int')
+                    break
+                isFuture = (metadata[i] - epoch.get_epoch())
+                if isFuture > max_clock_difference:
+                    logger.warn('Block timestamp is skewed to the future over the max %s: %s' (max_clock_difference, isFuture))
+                    break
+                if (epoch.get_epoch() - metadata[i]) > maxAge:
+                    logger.warn('Block is outdated: %s' % (metadata[i],))
+                    break
+            elif i == 'expire':
+                try:
+                    if not int(metadata[i]) > epoch.get_epoch(): raise ValueError
+                except ValueError:
+                    logger.warn('Block is expired: %s less than %s' % (metadata[i], epoch.get_epoch()))
+                    break
+            elif i == 'encryptType':
+                try:
+                    if not metadata[i] in ('asym', 'sym', ''): raise ValueError
+                except ValueError:
+                    logger.warn('Invalid encryption mode')
+                    break
+            elif i == 'sig':
+                try:
+                    metadata['encryptType']
+                except KeyError:
+                    signer = metadata['signer']
+                    sig = metadata['sig']
+                    encodedMeta = bytesconverter.str_to_bytes(metadata['meta'])
+                    encodedBlock = bytesconverter.str_to_bytes(block_data)
+                    if not onionrcrypto.signing.ed_verify(encodedMeta + encodedBlock[1:], signer, sig):
+                        break
+        else:
+            # if metadata loop gets no errors, it does not break, therefore metadata is valid
+            # make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
+
+            # Make sure time is set (validity was checked above if it is)
+            try:
+                metadata['time']
+            except KeyError:
+                return False
+
+            nonce = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(block_data))
+            try:
+                with open(filepaths.data_nonce_file, 'r') as nonceFile:
+                    if nonce in nonceFile.read():
+                        # we've seen that nonce before, so we can't pass metadata
+                        raise onionrexceptions.DataExists
+            except FileNotFoundError:
+                ret_data = True
+            except onionrexceptions.DataExists:
+                # do not set ret_data to True, because data has been seen before
+                logger.warn(f'{nonce} seen before')
+                raise onionrexceptions.DataExists
+            else:
+                ret_data = True
+    else:
+        logger.warn('In call to utils.validateMetadata, metadata must be JSON string or a dictionary object')
+
+    return ret_data
+
+
+
+
+
+
+
+

Functions

+
+
+def validate_metadata(metadata, block_data) +
+
+

Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string

+
+Source code +
def validate_metadata(metadata, block_data) -> bool:
+    '''Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string'''
+
+    ret_data = False
+    max_clock_difference = onionrvalues.MAX_BLOCK_CLOCK_SKEW
+
+    # convert to dict if it is json string
+    if type(metadata) is str:
+        try:
+            metadata = json.loads(metadata)
+        except json.JSONDecodeError:
+            pass
+
+    # Validate metadata dict for invalid keys to sizes that are too large
+    maxAge = min(config.get("general.max_block_age", onionrvalues.DEFAULT_EXPIRE), onionrvalues.DEFAULT_EXPIRE)
+    if type(metadata) is dict:
+        for i in metadata:
+            try:
+                onionrvalues.BLOCK_METADATA_LENGTHS[i]
+            except KeyError:
+                logger.warn('Block has invalid metadata key ' + i)
+                break
+            else:
+                testData = metadata[i]
+                try:
+                    testData = len(testData)
+                except (TypeError, AttributeError) as e:
+                    testData = len(str(testData))
+                if onionrvalues.BLOCK_METADATA_LENGTHS[i] < testData:
+                    logger.warn('Block metadata key ' + i + ' exceeded maximum size')
+                    break
+            if i == 'time':
+                if not stringvalidators.is_integer_string(metadata[i]):
+                    logger.warn('Block metadata time stamp is not integer string or int')
+                    break
+                isFuture = (metadata[i] - epoch.get_epoch())
+                if isFuture > max_clock_difference:
+                    logger.warn('Block timestamp is skewed to the future over the max %s: %s' (max_clock_difference, isFuture))
+                    break
+                if (epoch.get_epoch() - metadata[i]) > maxAge:
+                    logger.warn('Block is outdated: %s' % (metadata[i],))
+                    break
+            elif i == 'expire':
+                try:
+                    if not int(metadata[i]) > epoch.get_epoch(): raise ValueError
+                except ValueError:
+                    logger.warn('Block is expired: %s less than %s' % (metadata[i], epoch.get_epoch()))
+                    break
+            elif i == 'encryptType':
+                try:
+                    if not metadata[i] in ('asym', 'sym', ''): raise ValueError
+                except ValueError:
+                    logger.warn('Invalid encryption mode')
+                    break
+            elif i == 'sig':
+                try:
+                    metadata['encryptType']
+                except KeyError:
+                    signer = metadata['signer']
+                    sig = metadata['sig']
+                    encodedMeta = bytesconverter.str_to_bytes(metadata['meta'])
+                    encodedBlock = bytesconverter.str_to_bytes(block_data)
+                    if not onionrcrypto.signing.ed_verify(encodedMeta + encodedBlock[1:], signer, sig):
+                        break
+        else:
+            # if metadata loop gets no errors, it does not break, therefore metadata is valid
+            # make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
+
+            # Make sure time is set (validity was checked above if it is)
+            try:
+                metadata['time']
+            except KeyError:
+                return False
+
+            nonce = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(block_data))
+            try:
+                with open(filepaths.data_nonce_file, 'r') as nonceFile:
+                    if nonce in nonceFile.read():
+                        # we've seen that nonce before, so we can't pass metadata
+                        raise onionrexceptions.DataExists
+            except FileNotFoundError:
+                ret_data = True
+            except onionrexceptions.DataExists:
+                # do not set ret_data to True, because data has been seen before
+                logger.warn(f'{nonce} seen before')
+                raise onionrexceptions.DataExists
+            else:
+                ret_data = True
+    else:
+        logger.warn('In call to utils.validateMetadata, metadata must be JSON string or a dictionary object')
+
+    return ret_data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/serializeddata.html b/docs/html/onionr/serializeddata.html new file mode 100644 index 00000000..1eafea5a --- /dev/null +++ b/docs/html/onionr/serializeddata.html @@ -0,0 +1,181 @@ + + + + + + +onionr.serializeddata API documentation + + + + + + + + + +
+
+
+

Module onionr.serializeddata

+
+
+

Onionr - Private P2P Communication

+

This module serializes various data pieces for use in other modules, in particular the web api

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    This module serializes various data pieces for use in other modules, in particular the web api
+'''
+'''
+    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/>.
+'''
+
+import json, time
+from coredb import blockmetadb
+import communicator
+class SerializedData:
+    def __init__(self):
+        '''
+        Serialized data is in JSON format:
+        {
+            'success': bool,
+            'foo': 'bar',
+            etc
+        }
+        '''
+    
+    def get_stats(self):
+        '''Return statistics about our node'''
+        stats = {}
+        try:
+            self._too_many
+        except AttributeError:
+            time.sleep(1)
+        comm_inst = self._too_many.get(communicator.OnionrCommunicatorDaemon, args=(self._too_many,))
+        stats['uptime'] = comm_inst.getUptime()
+        stats['connectedNodes'] = '\n'.join(comm_inst.onlinePeers)
+        stats['blockCount'] = len(blockmetadb.get_block_list())
+        stats['blockQueueCount'] = len(comm_inst.blockQueue)
+        return json.dumps(stats)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class SerializedData +
+
+

Serialized data is in JSON format: +{ +'success': bool, +'foo': 'bar', +etc +}

+
+Source code +
class SerializedData:
+    def __init__(self):
+        '''
+        Serialized data is in JSON format:
+        {
+            'success': bool,
+            'foo': 'bar',
+            etc
+        }
+        '''
+    
+    def get_stats(self):
+        '''Return statistics about our node'''
+        stats = {}
+        try:
+            self._too_many
+        except AttributeError:
+            time.sleep(1)
+        comm_inst = self._too_many.get(communicator.OnionrCommunicatorDaemon, args=(self._too_many,))
+        stats['uptime'] = comm_inst.getUptime()
+        stats['connectedNodes'] = '\n'.join(comm_inst.onlinePeers)
+        stats['blockCount'] = len(blockmetadb.get_block_list())
+        stats['blockQueueCount'] = len(comm_inst.blockQueue)
+        return json.dumps(stats)
+
+

Methods

+
+
+def get_stats(self) +
+
+

Return statistics about our node

+
+Source code +
def get_stats(self):
+    '''Return statistics about our node'''
+    stats = {}
+    try:
+        self._too_many
+    except AttributeError:
+        time.sleep(1)
+    comm_inst = self._too_many.get(communicator.OnionrCommunicatorDaemon, args=(self._too_many,))
+    stats['uptime'] = comm_inst.getUptime()
+    stats['connectedNodes'] = '\n'.join(comm_inst.onlinePeers)
+    stats['blockCount'] = len(blockmetadb.get_block_list())
+    stats['blockQueueCount'] = len(comm_inst.blockQueue)
+    return json.dumps(stats)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/cachedconfig/index.html b/docs/html/onionr/utils/cachedconfig/index.html new file mode 100644 index 00000000..a9fe6773 --- /dev/null +++ b/docs/html/onionr/utils/cachedconfig/index.html @@ -0,0 +1,101 @@ + + + + + + +onionr.utils.cachedconfig API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.cachedconfig

+
+
+
+Source code +
from onionrutils import localcommand
+import config
+config.reload()
+running_detected = False # if we know the api server is running
+first_get = True
+
+def config_get(key):
+    ret_data = False
+    if running_detected or first_get:
+        first_get = False
+        ret_data = localcommand.local_command('/config/get/' + key)
+    if ret_data == False:
+        running_detected = False
+        ret_data = config.get(key)
+    else:
+        running_detected = False
+    return ret_data
+
+
+
+
+
+
+
+

Functions

+
+
+def config_get(key) +
+
+
+
+Source code +
def config_get(key):
+    ret_data = False
+    if running_detected or first_get:
+        first_get = False
+        ret_data = localcommand.local_command('/config/get/' + key)
+    if ret_data == False:
+        running_detected = False
+        ret_data = config.get(key)
+    else:
+        running_detected = False
+    return ret_data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/createdirs.html b/docs/html/onionr/utils/createdirs.html new file mode 100644 index 00000000..901c512f --- /dev/null +++ b/docs/html/onionr/utils/createdirs.html @@ -0,0 +1,127 @@ + + + + + + +onionr.utils.createdirs API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.createdirs

+
+
+

Onionr - Private P2P Communication

+

Create required Onionr directories

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Create required Onionr directories
+'''
+'''
+    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/>.
+'''
+import os
+from . import identifyhome
+from onionrsetup import dbcreator
+import filepaths
+home = identifyhome.identify_home()
+
+def create_dirs():
+    """Creates onionr data-related directories in order of the hardcoded list below,
+    then trigger creation of DBs"""
+    gen_dirs = [home, filepaths.block_data_location, filepaths.contacts_location, filepaths.export_location]
+    for path in gen_dirs:
+        if not os.path.exists(path):
+            os.mkdir(path)
+
+    for db in dbcreator.create_funcs:
+        try:
+            db()
+        except FileExistsError:
+            pass
+
+
+
+
+
+
+
+

Functions

+
+
+def create_dirs() +
+
+

Creates onionr data-related directories in order of the hardcoded list below, +then trigger creation of DBs

+
+Source code +
def create_dirs():
+    """Creates onionr data-related directories in order of the hardcoded list below,
+    then trigger creation of DBs"""
+    gen_dirs = [home, filepaths.block_data_location, filepaths.contacts_location, filepaths.export_location]
+    for path in gen_dirs:
+        if not os.path.exists(path):
+            os.mkdir(path)
+
+    for db in dbcreator.create_funcs:
+        try:
+            db()
+        except FileExistsError:
+            pass
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/definewait.html b/docs/html/onionr/utils/definewait.html new file mode 100644 index 00000000..db677f31 --- /dev/null +++ b/docs/html/onionr/utils/definewait.html @@ -0,0 +1,111 @@ + + + + + + +onionr.utils.definewait API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.definewait

+
+
+

Onionr - Private P2P Communication

+

Waits for a key to become set in a dictionary, even to None, 0 or empty string

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Waits for a key to become set in a dictionary, even to None, 0 or empty string
+'''
+'''
+    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/>.
+'''
+import time
+def define_wait(dictionary, key, delay: int = 0):
+    while True:
+        try:
+            return dictionary[key]
+        except KeyError:
+            pass
+        if delay > 0:
+            time.sleep(delay)
+
+
+
+
+
+
+
+

Functions

+
+
+def define_wait(dictionary, key, delay=0) +
+
+
+
+Source code +
def define_wait(dictionary, key, delay: int = 0):
+    while True:
+        try:
+            return dictionary[key]
+        except KeyError:
+            pass
+        if delay > 0:
+            time.sleep(delay)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/getconsolewidth.html b/docs/html/onionr/utils/getconsolewidth.html new file mode 100644 index 00000000..cfc55903 --- /dev/null +++ b/docs/html/onionr/utils/getconsolewidth.html @@ -0,0 +1,123 @@ + + + + + + +onionr.utils.getconsolewidth API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.getconsolewidth

+
+
+

Onionr - Private P2P Communication

+

Get the width of the terminal screen

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Get the width of the terminal screen
+'''
+'''
+    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/>.
+'''
+import os
+def get_console_width():
+    '''
+        Returns an integer, the width of the terminal/cmd window
+    '''
+
+    columns = 80
+
+    try:
+        columns = int(os.popen('stty size', 'r').read().split()[1])
+    except:
+        # if it errors, it's probably windows, so default to 80.
+        pass
+
+    return columns
+
+
+
+
+
+
+
+

Functions

+
+
+def get_console_width() +
+
+

Returns an integer, the width of the terminal/cmd window

+
+Source code +
def get_console_width():
+    '''
+        Returns an integer, the width of the terminal/cmd window
+    '''
+
+    columns = 80
+
+    try:
+        columns = int(os.popen('stty size', 'r').read().split()[1])
+    except:
+        # if it errors, it's probably windows, so default to 80.
+        pass
+
+    return columns
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/gethostname.html b/docs/html/onionr/utils/gethostname.html new file mode 100644 index 00000000..ace64e2d --- /dev/null +++ b/docs/html/onionr/utils/gethostname.html @@ -0,0 +1,112 @@ + + + + + + +onionr.utils.gethostname API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.gethostname

+
+
+

Onionr - Private P2P Communication

+

Get the node's Tor hostname

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Get the node's Tor hostname
+'''
+'''
+    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 identifyhome
+import filepaths
+def get_hostname():
+    try:
+        with open(identifyhome.identify_home() + '/hs/hostname', 'r') as hostname:
+            return hostname.read().strip()
+    except FileNotFoundError:
+        return "Not Generated"
+    except Exception:
+        return None
+
+
+
+
+
+
+
+

Functions

+
+
+def get_hostname() +
+
+
+
+Source code +
def get_hostname():
+    try:
+        with open(identifyhome.identify_home() + '/hs/hostname', 'r') as hostname:
+            return hostname.read().strip()
+    except FileNotFoundError:
+        return "Not Generated"
+    except Exception:
+        return None
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/gettransports.html b/docs/html/onionr/utils/gettransports.html new file mode 100644 index 00000000..b404061c --- /dev/null +++ b/docs/html/onionr/utils/gettransports.html @@ -0,0 +1,103 @@ + + + + + + +onionr.utils.gettransports API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.gettransports

+
+
+
+Source code +
import filepaths, time
+
+files = [filepaths.tor_hs_address_file]
+
+def get():
+        transports = []
+        for file in files:
+                try:
+                        with open(file, 'r') as transport_file:
+                                transports.append(transport_file.read().strip())
+                except FileNotFoundError:
+                        pass
+                else:
+                        break
+        else:
+                time.sleep(1)
+        return list(transports)
+
+
+
+
+
+
+
+

Functions

+
+
+def get() +
+
+
+
+Source code +
def get():
+        transports = []
+        for file in files:
+                try:
+                        with open(file, 'r') as transport_file:
+                                transports.append(transport_file.read().strip())
+                except FileNotFoundError:
+                        pass
+                else:
+                        break
+        else:
+                time.sleep(1)
+        return list(transports)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/hastor.html b/docs/html/onionr/utils/hastor.html new file mode 100644 index 00000000..a8196205 --- /dev/null +++ b/docs/html/onionr/utils/hastor.html @@ -0,0 +1,79 @@ + + + + + + +onionr.utils.hastor API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.hastor

+
+
+
+Source code +
import netcontroller
+
+def has_tor():
+    return netcontroller.tor_binary() is not None
+
+
+
+
+
+
+
+

Functions

+
+
+def has_tor() +
+
+
+
+Source code +
def has_tor():
+    return netcontroller.tor_binary() is not None
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/identifyhome.html b/docs/html/onionr/utils/identifyhome.html new file mode 100644 index 00000000..aa864b20 --- /dev/null +++ b/docs/html/onionr/utils/identifyhome.html @@ -0,0 +1,136 @@ + + + + + + +onionr.utils.identifyhome API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.identifyhome

+
+
+

Onionr - Private P2P Communication

+

Identify a data directory for Onionr

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    Identify a data directory for Onionr
+'''
+'''
+    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/>.
+'''
+import os, platform
+
+def identify_home():
+
+    path = os.environ.get('ONIONR_HOME', None)
+
+    if path is None:
+        system = platform.system()
+        if system == 'Linux':
+            path = os.path.expanduser('~') + '/.local/share/onionr/'
+        elif system == 'Windows':
+            path = os.path.expanduser('~') + '\\AppData\\Local\\onionr\\'
+        elif system == 'Darwin':
+            path = os.path.expanduser('~' + '/Library/Application Support/onionr/')
+        else:
+            path = 'data/'
+    else:
+        path = os.path.abspath(path)
+    if not path.endswith('/'):
+        path += '/'
+
+    return path
+
+
+
+
+
+
+
+

Functions

+
+
+def identify_home() +
+
+
+
+Source code +
def identify_home():
+
+    path = os.environ.get('ONIONR_HOME', None)
+
+    if path is None:
+        system = platform.system()
+        if system == 'Linux':
+            path = os.path.expanduser('~') + '/.local/share/onionr/'
+        elif system == 'Windows':
+            path = os.path.expanduser('~') + '\\AppData\\Local\\onionr\\'
+        elif system == 'Darwin':
+            path = os.path.expanduser('~' + '/Library/Application Support/onionr/')
+        else:
+            path = 'data/'
+    else:
+        path = os.path.abspath(path)
+    if not path.endswith('/'):
+        path += '/'
+
+    return path
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/index.html b/docs/html/onionr/utils/index.html new file mode 100644 index 00000000..c4080eec --- /dev/null +++ b/docs/html/onionr/utils/index.html @@ -0,0 +1,130 @@ + + + + + + +onionr.utils API documentation + + + + + + + + + +
+
+
+

Module onionr.utils

+
+
+
+
+

Sub-modules

+
+
onionr.utils.cachedconfig
+
+
+
+
onionr.utils.createdirs
+
+

Onionr - Private P2P Communication …

+
+
onionr.utils.definewait
+
+

Onionr - Private P2P Communication …

+
+
onionr.utils.getconsolewidth
+
+

Onionr - Private P2P Communication …

+
+
onionr.utils.gethostname
+
+

Onionr - Private P2P Communication …

+
+
onionr.utils.gettransports
+
+
+
+
onionr.utils.hastor
+
+
+
+
onionr.utils.identifyhome
+
+

Onionr - Private P2P Communication …

+
+
onionr.utils.logoheader
+
+
+
+
onionr.utils.netutils
+
+

Onionr - P2P Microblogging Platform & Social network …

+
+
onionr.utils.networkmerger
+
+

Onionr - P2P Microblogging Platform & Social network …

+
+
onionr.utils.readstatic
+
+

Onionr - Private P2P Communication …

+
+
onionr.utils.reconstructhash
+
+

Onionr - Private P2P Communication …

+
+
onionr.utils.sizeutils
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/logoheader.html b/docs/html/onionr/utils/logoheader.html new file mode 100644 index 00000000..63d62b41 --- /dev/null +++ b/docs/html/onionr/utils/logoheader.html @@ -0,0 +1,99 @@ + + + + + + +onionr.utils.logoheader API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.logoheader

+
+
+
+Source code +
import sys, os
+from . import readstatic
+import logger
+from etc import onionrvalues
+def header(message = logger.colors.fg.pink + logger.colors.bold + 'Onionr' + logger.colors.reset + logger.colors.fg.pink + ' has started.'):
+    if onionrvalues.DEVELOPMENT_MODE:
+        return
+    header_path = readstatic.get_static_dir() + 'header.txt'
+    if os.path.exists(header_path) and logger.settings.get_level() <= logger.settings.LEVEL_INFO:
+        with open(header_path, 'rb') as file:
+            # only to stdout, not file or log or anything
+            sys.stderr.write(file.read().decode().replace('P', logger.colors.fg.pink).replace('W', logger.colors.reset + logger.colors.bold).replace('G', logger.colors.fg.green).replace('\n', logger.colors.reset + '\n').replace('B', logger.colors.bold))
+
+            if not message is None:
+                logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n', terminal=True)
+
+
+
+
+
+
+
+

Functions

+
+
+def header(message='\x1b[95m\x1b[01mOnionr\x1b[0m\x1b[95m has started.') +
+
+
+
+Source code +
def header(message = logger.colors.fg.pink + logger.colors.bold + 'Onionr' + logger.colors.reset + logger.colors.fg.pink + ' has started.'):
+    if onionrvalues.DEVELOPMENT_MODE:
+        return
+    header_path = readstatic.get_static_dir() + 'header.txt'
+    if os.path.exists(header_path) and logger.settings.get_level() <= logger.settings.LEVEL_INFO:
+        with open(header_path, 'rb') as file:
+            # only to stdout, not file or log or anything
+            sys.stderr.write(file.read().decode().replace('P', logger.colors.fg.pink).replace('W', logger.colors.reset + logger.colors.bold).replace('G', logger.colors.fg.green).replace('\n', logger.colors.reset + '\n').replace('B', logger.colors.bold))
+
+            if not message is None:
+                logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n', terminal=True)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/netutils.html b/docs/html/onionr/utils/netutils.html new file mode 100644 index 00000000..ac991f16 --- /dev/null +++ b/docs/html/onionr/utils/netutils.html @@ -0,0 +1,125 @@ + + + + + + +onionr.utils.netutils API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.netutils

+
+
+

Onionr - P2P Microblogging Platform & Social network

+

OnionrUtils offers various useful functions to Onionr networking.

+
+Source code +
'''
+    Onionr - P2P Microblogging Platform & Social network
+
+    OnionrUtils offers various useful functions to Onionr networking.
+'''
+'''
+    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 onionrutils import basicrequests
+from . import readstatic
+from onionrcrypto import cryptoutils
+def checkNetwork(torPort=0):
+    '''Check if we are connected to the internet (through Tor)'''
+    retData = False
+    connectURLs = []
+    try:
+        connectURLs = cryptoutils.random_shuffle(readstatic.read_static('connect-check.txt').split(','))
+
+        for url in connectURLs:
+            if basicrequests.do_get_request(url, port=torPort, ignoreAPI=True) != False:
+                retData = True
+                break
+    except FileNotFoundError:
+        pass
+    return retData
+
+
+
+
+
+
+
+

Functions

+
+
+def checkNetwork(torPort=0) +
+
+

Check if we are connected to the internet (through Tor)

+
+Source code +
def checkNetwork(torPort=0):
+    '''Check if we are connected to the internet (through Tor)'''
+    retData = False
+    connectURLs = []
+    try:
+        connectURLs = cryptoutils.random_shuffle(readstatic.read_static('connect-check.txt').split(','))
+
+        for url in connectURLs:
+            if basicrequests.do_get_request(url, port=torPort, ignoreAPI=True) != False:
+                retData = True
+                break
+    except FileNotFoundError:
+        pass
+    return retData
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/networkmerger.html b/docs/html/onionr/utils/networkmerger.html new file mode 100644 index 00000000..d7050d6c --- /dev/null +++ b/docs/html/onionr/utils/networkmerger.html @@ -0,0 +1,145 @@ + + + + + + +onionr.utils.networkmerger API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.networkmerger

+
+
+

Onionr - P2P Microblogging Platform & Social network

+

Merges peer and block lists

+
+Source code +
'''
+    Onionr - P2P Microblogging Platform & Social network
+
+    Merges peer and block lists
+'''
+'''
+    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/>.
+'''
+import logger
+from coredb import keydb
+import config
+from onionrblocks import onionrblacklist
+from utils import gettransports
+def mergeAdders(newAdderList):
+    '''
+        Merge peer adders list to our database
+    '''
+    blacklist = onionrblacklist.OnionrBlackList()
+    retVal = False
+    if newAdderList != False:
+        for adder in newAdderList.split(','):
+            adder = adder.strip()
+            if not adder in keydb.listkeys.list_adders(randomOrder = False) and not adder in gettransports.get() and not blacklist.inBlacklist(adder):
+                if not config.get('tor.v3onions', True) and len(adder) == 62:
+                    continue
+                if keydb.addkeys.add_address(adder):
+                    # Check if we have the maximum amount of allowed stored peers
+                    if config.get('peers.max_stored_peers') > len(keydb.listkeys.list_adders()):
+                        logger.info('Added %s to db.' % adder, timestamp = True)
+                        retVal = True
+                    else:
+                        logger.warn('Reached the maximum amount of peers in the net database as allowed by your config.')
+            else:
+                pass
+                #logger.debug('%s is either our address or already in our DB' % adder)
+    return retVal
+
+
+
+
+
+
+
+

Functions

+
+
+def mergeAdders(newAdderList) +
+
+

Merge peer adders list to our database

+
+Source code +
def mergeAdders(newAdderList):
+    '''
+        Merge peer adders list to our database
+    '''
+    blacklist = onionrblacklist.OnionrBlackList()
+    retVal = False
+    if newAdderList != False:
+        for adder in newAdderList.split(','):
+            adder = adder.strip()
+            if not adder in keydb.listkeys.list_adders(randomOrder = False) and not adder in gettransports.get() and not blacklist.inBlacklist(adder):
+                if not config.get('tor.v3onions', True) and len(adder) == 62:
+                    continue
+                if keydb.addkeys.add_address(adder):
+                    # Check if we have the maximum amount of allowed stored peers
+                    if config.get('peers.max_stored_peers') > len(keydb.listkeys.list_adders()):
+                        logger.info('Added %s to db.' % adder, timestamp = True)
+                        retVal = True
+                    else:
+                        logger.warn('Reached the maximum amount of peers in the net database as allowed by your config.')
+            else:
+                pass
+                #logger.debug('%s is either our address or already in our DB' % adder)
+    return retVal
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/readstatic.html b/docs/html/onionr/utils/readstatic.html new file mode 100644 index 00000000..5fe5d6a9 --- /dev/null +++ b/docs/html/onionr/utils/readstatic.html @@ -0,0 +1,132 @@ + + + + + + +onionr.utils.readstatic API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.readstatic

+
+
+

Onionr - Private P2P Communication

+

module to get static directory and read static data files

+
+Source code +
"""
+    Onionr - Private P2P Communication
+
+    module to get static directory and read static data files
+"""
+"""
+    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 typing import Union
+import os
+def get_static_dir()->str:
+    return os.path.dirname(os.path.realpath(__file__)) + '/../../static-data/'
+
+def read_static(file:str, ret_bin:bool=False)->Union[str, bytes]:
+    static_file = get_static_dir() + file
+
+    if ret_bin:
+        mode = 'rb'
+    else:
+        mode = 'r'
+    with open(static_file, mode) as f:
+        data = f.read()
+    return data
+
+
+
+
+
+
+
+

Functions

+
+
+def get_static_dir() +
+
+
+
+Source code +
def get_static_dir()->str:
+    return os.path.dirname(os.path.realpath(__file__)) + '/../../static-data/'
+
+
+
+def read_static(file, ret_bin=False) +
+
+
+
+Source code +
def read_static(file:str, ret_bin:bool=False)->Union[str, bytes]:
+    static_file = get_static_dir() + file
+
+    if ret_bin:
+        mode = 'rb'
+    else:
+        mode = 'r'
+    with open(static_file, mode) as f:
+        data = f.read()
+    return data
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/reconstructhash.html b/docs/html/onionr/utils/reconstructhash.html new file mode 100644 index 00000000..648c5add --- /dev/null +++ b/docs/html/onionr/utils/reconstructhash.html @@ -0,0 +1,151 @@ + + + + + + +onionr.utils.reconstructhash API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.reconstructhash

+
+
+

Onionr - Private P2P Communication

+

z-fill (zero fill) a string to a specific length, intended for reconstructing block hashes

+
+Source code +
'''
+    Onionr - Private P2P Communication
+
+    z-fill (zero fill) a string to a specific length, intended for reconstructing block hashes
+'''
+'''
+    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 reconstruct_hash(hex_hash, length=64):
+    return hex_hash.zfill(length)
+
+def deconstruct_hash(hex_hash):
+    new_hash = ''
+    ret_bytes = False
+    try:
+        hex_hash = hex_hash.decode()
+        ret_bytes = True
+    except AttributeError:
+        pass
+
+    c = 0
+    for x in hex_hash:
+        if x == '0':
+            c += 1
+        else:
+            break
+    new_hash = hex_hash[c:]
+
+    if ret_bytes:
+
+        new_hash = new_hash.encode()
+    return new_hash
+
+
+
+
+
+
+
+

Functions

+
+
+def deconstruct_hash(hex_hash) +
+
+
+
+Source code +
def deconstruct_hash(hex_hash):
+    new_hash = ''
+    ret_bytes = False
+    try:
+        hex_hash = hex_hash.decode()
+        ret_bytes = True
+    except AttributeError:
+        pass
+
+    c = 0
+    for x in hex_hash:
+        if x == '0':
+            c += 1
+        else:
+            break
+    new_hash = hex_hash[c:]
+
+    if ret_bytes:
+
+        new_hash = new_hash.encode()
+    return new_hash
+
+
+
+def reconstruct_hash(hex_hash, length=64) +
+
+
+
+Source code +
def reconstruct_hash(hex_hash, length=64):
+    return hex_hash.zfill(length)
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/html/onionr/utils/sizeutils.html b/docs/html/onionr/utils/sizeutils.html new file mode 100644 index 00000000..fa8c73d0 --- /dev/null +++ b/docs/html/onionr/utils/sizeutils.html @@ -0,0 +1,134 @@ + + + + + + +onionr.utils.sizeutils API documentation + + + + + + + + + +
+
+
+

Module onionr.utils.sizeutils

+
+
+
+Source code +
import sqlite3, os
+from onionrutils import stringvalidators
+def human_size(num, suffix='B'):
+    '''
+        Converts from bytes to a human readable format.
+    '''
+    for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
+        if abs(num) < 1024.0:
+            return "%.1f %s%s" % (num, unit, suffix)
+        num /= 1024.0
+    return "%.1f %s%s" % (num, 'Yi', suffix)
+
+def size(path='.'):
+    '''
+        Returns the size of a folder's contents in bytes
+    '''
+    total = 0
+    if os.path.exists(path):
+        if os.path.isfile(path):
+            total = os.path.getsize(path)
+        else:
+            for entry in os.scandir(path):
+                if entry.is_file():
+                    total += entry.stat().st_size
+                elif entry.is_dir():
+                    total += size(entry.path)
+    return total
+
+
+
+
+
+
+
+

Functions

+
+
+def human_size(num, suffix='B') +
+
+

Converts from bytes to a human readable format.

+
+Source code +
def human_size(num, suffix='B'):
+    '''
+        Converts from bytes to a human readable format.
+    '''
+    for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
+        if abs(num) < 1024.0:
+            return "%.1f %s%s" % (num, unit, suffix)
+        num /= 1024.0
+    return "%.1f %s%s" % (num, 'Yi', suffix)
+
+
+
+def size(path='.') +
+
+

Returns the size of a folder's contents in bytes

+
+Source code +
def size(path='.'):
+    '''
+        Returns the size of a folder's contents in bytes
+    '''
+    total = 0
+    if os.path.exists(path):
+        if os.path.isfile(path):
+            total = os.path.getsize(path)
+        else:
+            for entry in os.scandir(path):
+                if entry.is_file():
+                    total += entry.stat().st_size
+                elif entry.is_dir():
+                    total += size(entry.path)
+    return total
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file