work on adding peers, improved documentation, improved the way the daemon is run, daemon can now be killed from background
parent
a69e1d2d72
commit
7131902a74
35
api.py
35
api.py
|
@ -1,5 +1,9 @@
|
||||||
'''
|
'''
|
||||||
Onionr - P2P Microblogging Platform & Social network
|
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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
@ -15,20 +19,27 @@
|
||||||
'''
|
'''
|
||||||
import flask
|
import flask
|
||||||
from flask import request, Response, abort
|
from flask import request, Response, abort
|
||||||
|
from multiprocessing import Process
|
||||||
import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os
|
import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os
|
||||||
|
|
||||||
from core import Core
|
from core import Core
|
||||||
'''
|
|
||||||
Main API
|
|
||||||
'''
|
|
||||||
class API:
|
class API:
|
||||||
|
''' Main http api (flask)'''
|
||||||
def validateToken(self, token):
|
def validateToken(self, token):
|
||||||
|
'''
|
||||||
|
Validate if the client token (hmac) matches the given token
|
||||||
|
'''
|
||||||
if self.clientToken != token:
|
if self.clientToken != token:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __init__(self, config, debug):
|
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'):
|
if os.path.exists('dev-enabled'):
|
||||||
print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
|
print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
|
||||||
self._developmentMode = True
|
self._developmentMode = True
|
||||||
|
@ -44,14 +55,20 @@ class API:
|
||||||
self.clientToken = self.config['CLIENT']['CLIENT HMAC']
|
self.clientToken = self.config['CLIENT']['CLIENT HMAC']
|
||||||
print(self.clientToken)
|
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)]
|
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])
|
self.host = '127.' + str(hostNums[0]) + '.' + str(hostNums[1]) + '.' + str(hostNums[2])
|
||||||
else:
|
else:
|
||||||
self.host = '127.0.0.1'
|
self.host = '127.0.0.1'
|
||||||
|
hostFile = open('data/host.txt', 'w')
|
||||||
|
hostFile.write(self.host)
|
||||||
|
hostFile.close()
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def beforeReq():
|
def beforeReq():
|
||||||
|
'''
|
||||||
|
Simply define the request as not having yet failed, before every request.
|
||||||
|
'''
|
||||||
self.requestFailed = False
|
self.requestFailed = False
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -78,6 +95,9 @@ class API:
|
||||||
self.validateHost('private')
|
self.validateHost('private')
|
||||||
if action == 'hello':
|
if action == 'hello':
|
||||||
resp = Response('Hello, World! ' + request.host)
|
resp = Response('Hello, World! ' + request.host)
|
||||||
|
elif action == 'shutdown':
|
||||||
|
request.environ.get('werkzeug.server.shutdown')()
|
||||||
|
resp = Response('Goodbye')
|
||||||
elif action == 'stats':
|
elif action == 'stats':
|
||||||
resp = Response('something')
|
resp = Response('something')
|
||||||
elif action == 'init':
|
elif action == 'init':
|
||||||
|
@ -100,7 +120,6 @@ class API:
|
||||||
requestingPeer = request.args.get('myID')
|
requestingPeer = request.args.get('myID')
|
||||||
|
|
||||||
if action == 'firstConnect':
|
if action == 'firstConnect':
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
|
@ -121,6 +140,12 @@ class API:
|
||||||
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
|
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
|
||||||
|
|
||||||
def validateHost(self, hostType):
|
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:
|
if self.debug:
|
||||||
return
|
return
|
||||||
# Validate host header, to protect against DNS rebinding attacks
|
# Validate host header, to protect against DNS rebinding attacks
|
||||||
|
|
|
@ -28,6 +28,14 @@ class OnionrCommunicate:
|
||||||
This class handles communication with nodes in the Onionr network.
|
This class handles communication with nodes in the Onionr network.
|
||||||
'''
|
'''
|
||||||
self._core = core.Core()
|
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
|
return
|
||||||
def getRemotePeerKey(self, peerID):
|
def getRemotePeerKey(self, peerID):
|
||||||
'''This function contacts a peer and gets their main PGP key.
|
'''This function contacts a peer and gets their main PGP key.
|
||||||
|
|
26
core.py
26
core.py
|
@ -1,5 +1,9 @@
|
||||||
'''
|
'''
|
||||||
Onionr - P2P Microblogging Platform & Social network
|
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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
@ -20,6 +24,9 @@ from Crypto import Random
|
||||||
|
|
||||||
class Core:
|
class Core:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
'''
|
||||||
|
Initialize Core Onionr library
|
||||||
|
'''
|
||||||
self.queueDB = 'data/queue.db'
|
self.queueDB = 'data/queue.db'
|
||||||
self.peerDB = 'data/peers.db'
|
self.peerDB = 'data/peers.db'
|
||||||
|
|
||||||
|
@ -27,6 +34,8 @@ class Core:
|
||||||
return
|
return
|
||||||
|
|
||||||
def generateMainPGP(self):
|
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
|
# Generate main pgp key
|
||||||
gpg = gnupg.GPG(gnupghome='data/pgp/')
|
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')
|
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
|
return
|
||||||
|
|
||||||
def addPeer(self, peerID, name=''):
|
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
|
# This function simply adds a peer to the DB
|
||||||
conn = sqlite3.connect(self.peerDB)
|
conn = sqlite3.connect(self.peerDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
@ -44,6 +55,9 @@ class Core:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def createPeerDB(self):
|
def createPeerDB(self):
|
||||||
|
'''
|
||||||
|
Generate the peer sqlite3 database and populate it with the peers table.
|
||||||
|
'''
|
||||||
# generate the peer database
|
# generate the peer database
|
||||||
conn = sqlite3.connect(self.peerDB)
|
conn = sqlite3.connect(self.peerDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
@ -61,6 +75,9 @@ class Core:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def dataDirEncrypt(self, password):
|
def dataDirEncrypt(self, password):
|
||||||
|
'''
|
||||||
|
Encrypt the data directory on Onionr shutdown
|
||||||
|
'''
|
||||||
# Encrypt data directory (don't delete it in this function)
|
# Encrypt data directory (don't delete it in this function)
|
||||||
if os.path.exists('data.tar'):
|
if os.path.exists('data.tar'):
|
||||||
os.remove('data.tar')
|
os.remove('data.tar')
|
||||||
|
@ -74,6 +91,9 @@ class Core:
|
||||||
os.remove('data.tar')
|
os.remove('data.tar')
|
||||||
return
|
return
|
||||||
def dataDirDecrypt(self, password):
|
def dataDirDecrypt(self, password):
|
||||||
|
'''
|
||||||
|
Decrypt the data directory on startup
|
||||||
|
'''
|
||||||
# Decrypt data directory
|
# Decrypt data directory
|
||||||
if not os.path.exists('data-encrypted.dat'):
|
if not os.path.exists('data-encrypted.dat'):
|
||||||
return (False, 'encrypted archive does not exist')
|
return (False, 'encrypted archive does not exist')
|
||||||
|
@ -89,6 +109,9 @@ class Core:
|
||||||
tar.close()
|
tar.close()
|
||||||
return (True, '')
|
return (True, '')
|
||||||
def daemonQueue(self):
|
def daemonQueue(self):
|
||||||
|
'''
|
||||||
|
Gives commands to the communication proccess/daemon by reading an sqlite3 database
|
||||||
|
'''
|
||||||
# This function intended to be used by the client
|
# This function intended to be used by the client
|
||||||
# Queue to exchange data between "client" and server.
|
# Queue to exchange data between "client" and server.
|
||||||
retData = False
|
retData = False
|
||||||
|
@ -113,6 +136,9 @@ class Core:
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def daemonQueueAdd(self, command, data=''):
|
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
|
# Intended to be used by the web server
|
||||||
date = math.floor(time.time())
|
date = math.floor(time.time())
|
||||||
conn = sqlite3.connect(self.queueDB)
|
conn = sqlite3.connect(self.queueDB)
|
||||||
|
|
37
onionr.py
37
onionr.py
|
@ -20,7 +20,7 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
import sys, os, configparser, base64, random, getpass, shutil, subprocess
|
import sys, os, configparser, base64, random, getpass, shutil, subprocess, requests
|
||||||
import gui, api, colors, core
|
import gui, api, colors, core
|
||||||
from onionrutils import OnionrUtils
|
from onionrutils import OnionrUtils
|
||||||
from colors import Colors
|
from colors import Colors
|
||||||
|
@ -31,7 +31,6 @@ class Onionr:
|
||||||
In general, external programs and plugins should not use this class.
|
In general, external programs and plugins should not use this class.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
self.runningDaemon = False
|
|
||||||
if os.path.exists('dev-enabled'):
|
if os.path.exists('dev-enabled'):
|
||||||
print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
|
print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
|
||||||
self._developmentMode = True
|
self._developmentMode = True
|
||||||
|
@ -40,8 +39,8 @@ class Onionr:
|
||||||
|
|
||||||
colors = Colors()
|
colors = Colors()
|
||||||
|
|
||||||
onionrCore = core.Core()
|
self.onionrCore = core.Core()
|
||||||
onionrUtils = OnionrUtils()
|
self.onionrUtils = OnionrUtils()
|
||||||
|
|
||||||
# Get configuration and Handle commands
|
# Get configuration and Handle commands
|
||||||
|
|
||||||
|
@ -55,7 +54,7 @@ class Onionr:
|
||||||
while True:
|
while True:
|
||||||
print('Enter password to decrypt:')
|
print('Enter password to decrypt:')
|
||||||
password = getpass.getpass()
|
password = getpass.getpass()
|
||||||
result = onionrCore.dataDirDecrypt(password)
|
result = self.onionrCore.dataDirDecrypt(password)
|
||||||
if os.path.exists('data/'):
|
if os.path.exists('data/'):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -65,7 +64,7 @@ class Onionr:
|
||||||
os.mkdir('data/')
|
os.mkdir('data/')
|
||||||
|
|
||||||
if not os.path.exists('data/peers.db'):
|
if not os.path.exists('data/peers.db'):
|
||||||
onionrCore.createPeerDB()
|
self.onionrCore.createPeerDB()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Get configuration
|
# Get configuration
|
||||||
|
@ -89,7 +88,16 @@ class Onionr:
|
||||||
command = ''
|
command = ''
|
||||||
finally:
|
finally:
|
||||||
if command == 'start':
|
if command == 'start':
|
||||||
|
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()
|
self.daemon()
|
||||||
|
if not self.debug and not self._developmentMode:
|
||||||
|
os.remove('.onionr-lock')
|
||||||
elif command == 'stop':
|
elif command == 'stop':
|
||||||
self.killDaemon()
|
self.killDaemon()
|
||||||
elif command == 'stats':
|
elif command == 'stats':
|
||||||
|
@ -102,8 +110,8 @@ class Onionr:
|
||||||
print(colors.RED, 'Invalid Command', colors.RESET)
|
print(colors.RED, 'Invalid Command', colors.RESET)
|
||||||
|
|
||||||
if not self._developmentMode:
|
if not self._developmentMode:
|
||||||
encryptionPassword = onionrUtils.getPassword('Enter password to encrypt directory.')
|
encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory.')
|
||||||
onionrCore.dataDirEncrypt(encryptionPassword)
|
self.onionrCore.dataDirEncrypt(encryptionPassword)
|
||||||
shutil.rmtree('data/')
|
shutil.rmtree('data/')
|
||||||
return
|
return
|
||||||
def daemon(self):
|
def daemon(self):
|
||||||
|
@ -115,13 +123,20 @@ class Onionr:
|
||||||
api.API(self.config, self.debug)
|
api.API(self.config, self.debug)
|
||||||
return
|
return
|
||||||
def killDaemon(self):
|
def killDaemon(self):
|
||||||
if self.runningDaemon == False:
|
'''Shutdown the Onionr Daemon'''
|
||||||
onionrUtils.printErr('No known daemon is running')
|
print('Killing the running daemon')
|
||||||
sys.exit(1)
|
try:
|
||||||
|
self.onionrUtils.localCommand('shutdown')
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.onionrCore.daemonQueueAdd('shutdown')
|
||||||
return
|
return
|
||||||
def showStats(self):
|
def showStats(self):
|
||||||
|
'''Display statistics and exit'''
|
||||||
return
|
return
|
||||||
def showHelp(self):
|
def showHelp(self):
|
||||||
|
'''Show help for Onionr'''
|
||||||
return
|
return
|
||||||
|
|
||||||
Onionr()
|
Onionr()
|
|
@ -1,5 +1,9 @@
|
||||||
'''
|
'''
|
||||||
Onionr - P2P Microblogging Platform & Social network
|
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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
@ -14,13 +18,24 @@
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
# Misc functions that do not fit in the main api, but are useful
|
# Misc functions that do not fit in the main api, but are useful
|
||||||
import getpass, sys
|
import getpass, sys, requests, configparser, os
|
||||||
class OnionrUtils():
|
class OnionrUtils():
|
||||||
|
'''Various useful functions'''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
return
|
return
|
||||||
def printErr(self, text='an error occured'):
|
def printErr(self, text='an error occured'):
|
||||||
|
'''Print an error message to stderr with a new line'''
|
||||||
sys.stderr.write(text + '\n')
|
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: '):
|
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
|
# Get a password safely with confirmation and return it
|
||||||
while True:
|
while True:
|
||||||
print(message)
|
print(message)
|
||||||
|
|
Loading…
Reference in New Issue