diff --git a/api.py b/api.py
index fa2b2fa3..ba3c05b5 100755
--- a/api.py
+++ b/api.py
@@ -1,5 +1,9 @@
'''
Onionr - P2P Microblogging Platform & Social network
+
+ 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
@@ -15,20 +19,27 @@
'''
import flask
from flask import request, Response, abort
+from multiprocessing import Process
import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os
from core import Core
-'''
-Main API
-'''
class API:
+ ''' Main http api (flask)'''
def validateToken(self, token):
+ '''
+ Validate if the client token (hmac) matches the given token
+ '''
if self.clientToken != token:
return False
else:
return True
def __init__(self, config, debug):
+ ''' Initialize the api server, preping variables for later use
+ This initilization 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
+ '''
if os.path.exists('dev-enabled'):
print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
self._developmentMode = True
@@ -44,14 +55,20 @@ class API:
self.clientToken = self.config['CLIENT']['CLIENT HMAC']
print(self.clientToken)
- if not debug:
+ if not debug and not self._developmentMode:
hostNums = [random.randint(1, 255), random.randint(1, 255), random.randint(1, 255)]
self.host = '127.' + str(hostNums[0]) + '.' + str(hostNums[1]) + '.' + str(hostNums[2])
else:
self.host = '127.0.0.1'
+ hostFile = open('data/host.txt', 'w')
+ hostFile.write(self.host)
+ hostFile.close()
@app.before_request
def beforeReq():
+ '''
+ Simply define the request as not having yet failed, before every request.
+ '''
self.requestFailed = False
return
@@ -78,6 +95,9 @@ class API:
self.validateHost('private')
if action == 'hello':
resp = Response('Hello, World! ' + request.host)
+ elif action == 'shutdown':
+ request.environ.get('werkzeug.server.shutdown')()
+ resp = Response('Goodbye')
elif action == 'stats':
resp = Response('something')
elif action == 'init':
@@ -100,7 +120,6 @@ class API:
requestingPeer = request.args.get('myID')
if action == 'firstConnect':
-
pass
@app.errorhandler(404)
@@ -121,6 +140,12 @@ class API:
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
def validateHost(self, hostType):
+ ''' Validate various features of the request including:
+ If private (/client/), is the host header local?
+ If public (/public/), is the host header onion or i2p?
+
+ Was x-request-with used?
+ '''
if self.debug:
return
# Validate host header, to protect against DNS rebinding attacks
diff --git a/communicator.py b/communicator.py
index 0b9e582a..13178458 100755
--- a/communicator.py
+++ b/communicator.py
@@ -28,6 +28,14 @@ class OnionrCommunicate:
This class handles communication with nodes in the Onionr network.
'''
self._core = core.Core()
+ while True:
+ command = self._core.daemonQueue()
+ print('Daemon heartbeat')
+ if command != False:
+ if command[0] == 'shutdown':
+ print('Daemon recieved exit command.')
+ break
+ time.sleep(1)
return
def getRemotePeerKey(self, peerID):
'''This function contacts a peer and gets their main PGP key.
diff --git a/core.py b/core.py
index ad37b877..82cfb6d5 100644
--- a/core.py
+++ b/core.py
@@ -1,5 +1,9 @@
'''
Onionr - P2P Microblogging Platform & Social network
+
+ Core Onionr library, useful for external programs. Handles peer processing and 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
@@ -20,6 +24,9 @@ from Crypto import Random
class Core:
def __init__(self):
+ '''
+ Initialize Core Onionr library
+ '''
self.queueDB = 'data/queue.db'
self.peerDB = 'data/peers.db'
@@ -27,6 +34,8 @@ class Core:
return
def generateMainPGP(self):
+ ''' Generate the main PGP key for our client. Should not be done often.
+ Uses own PGP home folder in the data/ directory. '''
# Generate main pgp key
gpg = gnupg.GPG(gnupghome='data/pgp/')
input_data = gpg.gen_key_input(key_type="RSA", key_length=2048, name_real='anon', name_comment='Onionr key', name_email='anon@onionr')
@@ -34,6 +43,8 @@ class Core:
return
def addPeer(self, peerID, name=''):
+ ''' Add a peer by their ID, with an optional name, to the peer database.'''
+ ''' DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion. '''
# This function simply adds a peer to the DB
conn = sqlite3.connect(self.peerDB)
c = conn.cursor()
@@ -44,6 +55,9 @@ class Core:
return True
def createPeerDB(self):
+ '''
+ Generate the peer sqlite3 database and populate it with the peers table.
+ '''
# generate the peer database
conn = sqlite3.connect(self.peerDB)
c = conn.cursor()
@@ -61,6 +75,9 @@ class Core:
conn.close()
def dataDirEncrypt(self, password):
+ '''
+ Encrypt the data directory on Onionr shutdown
+ '''
# Encrypt data directory (don't delete it in this function)
if os.path.exists('data.tar'):
os.remove('data.tar')
@@ -74,6 +91,9 @@ class Core:
os.remove('data.tar')
return
def dataDirDecrypt(self, password):
+ '''
+ Decrypt the data directory on startup
+ '''
# Decrypt data directory
if not os.path.exists('data-encrypted.dat'):
return (False, 'encrypted archive does not exist')
@@ -89,6 +109,9 @@ class Core:
tar.close()
return (True, '')
def daemonQueue(self):
+ '''
+ 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
@@ -113,6 +136,9 @@ class Core:
return retData
def daemonQueueAdd(self, command, data=''):
+ '''
+ Add a command to the daemon queue, used by the communication daemon (communicator.py)
+ '''
# Intended to be used by the web server
date = math.floor(time.time())
conn = sqlite3.connect(self.queueDB)
diff --git a/onionr.py b/onionr.py
index c9201186..9e92b302 100755
--- a/onionr.py
+++ b/onionr.py
@@ -20,7 +20,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
'''
-import sys, os, configparser, base64, random, getpass, shutil, subprocess
+import sys, os, configparser, base64, random, getpass, shutil, subprocess, requests
import gui, api, colors, core
from onionrutils import OnionrUtils
from colors import Colors
@@ -31,7 +31,6 @@ class Onionr:
In general, external programs and plugins should not use this class.
'''
- self.runningDaemon = False
if os.path.exists('dev-enabled'):
print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
self._developmentMode = True
@@ -40,8 +39,8 @@ class Onionr:
colors = Colors()
- onionrCore = core.Core()
- onionrUtils = OnionrUtils()
+ self.onionrCore = core.Core()
+ self.onionrUtils = OnionrUtils()
# Get configuration and Handle commands
@@ -55,7 +54,7 @@ class Onionr:
while True:
print('Enter password to decrypt:')
password = getpass.getpass()
- result = onionrCore.dataDirDecrypt(password)
+ result = self.onionrCore.dataDirDecrypt(password)
if os.path.exists('data/'):
break
else:
@@ -65,7 +64,7 @@ class Onionr:
os.mkdir('data/')
if not os.path.exists('data/peers.db'):
- onionrCore.createPeerDB()
+ self.onionrCore.createPeerDB()
pass
# Get configuration
@@ -89,7 +88,16 @@ class Onionr:
command = ''
finally:
if command == 'start':
- self.daemon()
+ if os.path.exists('.onionr-lock'):
+ self.onionrUtils.printErr('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).')
+ else:
+ if not self.debug and not self._developmentMode:
+ lockFile = open('.onionr-lock', 'w')
+ lockFile.write('')
+ lockFile.close()
+ self.daemon()
+ if not self.debug and not self._developmentMode:
+ os.remove('.onionr-lock')
elif command == 'stop':
self.killDaemon()
elif command == 'stats':
@@ -102,8 +110,8 @@ class Onionr:
print(colors.RED, 'Invalid Command', colors.RESET)
if not self._developmentMode:
- encryptionPassword = onionrUtils.getPassword('Enter password to encrypt directory.')
- onionrCore.dataDirEncrypt(encryptionPassword)
+ encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory.')
+ self.onionrCore.dataDirEncrypt(encryptionPassword)
shutil.rmtree('data/')
return
def daemon(self):
@@ -115,13 +123,20 @@ class Onionr:
api.API(self.config, self.debug)
return
def killDaemon(self):
- if self.runningDaemon == False:
- onionrUtils.printErr('No known daemon is running')
- sys.exit(1)
+ '''Shutdown the Onionr Daemon'''
+ print('Killing the running daemon')
+ try:
+ self.onionrUtils.localCommand('shutdown')
+ except requests.exceptions.ConnectionError:
+ pass
+ else:
+ self.onionrCore.daemonQueueAdd('shutdown')
return
def showStats(self):
+ '''Display statistics and exit'''
return
def showHelp(self):
+ '''Show help for Onionr'''
return
Onionr()
\ No newline at end of file
diff --git a/onionrutils.py b/onionrutils.py
index 49e98e1c..57c7e2d3 100644
--- a/onionrutils.py
+++ b/onionrutils.py
@@ -1,5 +1,9 @@
'''
Onionr - P2P Microblogging Platform & Social network
+
+ OnionrUtils offers various useful functions to Onionr. Relatively misc.
+'''
+'''
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
@@ -14,13 +18,24 @@
along with this program. If not, see .
'''
# Misc functions that do not fit in the main api, but are useful
-import getpass, sys
+import getpass, sys, requests, configparser, os
class OnionrUtils():
+ '''Various useful functions'''
def __init__(self):
return
def printErr(self, text='an error occured'):
+ '''Print an error message to stderr with a new line'''
sys.stderr.write(text + '\n')
+ def localCommand(self, command):
+ '''Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers.'''
+ config = configparser.ConfigParser()
+ if os.path.exists('data/config.ini'):
+ config.read('data/config.ini')
+ else:
+ return
+ requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config['CLIENT']['PORT']) + '/client/?action=' + command + '&token=' + config['CLIENT']['CLIENT HMAC'])
def getPassword(self, message='Enter password: '):
+ '''Get a password without showing the users typing and confirm the input'''
# Get a password safely with confirmation and return it
while True:
print(message)