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
|
||||
|
||||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
26
core.py
26
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)
|
||||
|
|
37
onionr.py
37
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 <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
|
||||
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':
|
||||
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()
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
# 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)
|
||||
|
|
Loading…
Reference in New Issue