work on adding peers, improved documentation, improved the way the daemon is run, daemon can now be killed from background

This commit is contained in:
Kevin Froman 2018-01-14 02:48:23 -06:00
parent a69e1d2d72
commit 7131902a74
No known key found for this signature in database
GPG Key ID: 0D414D0FE405B63B
5 changed files with 107 additions and 18 deletions

35
api.py
View File

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

View File

@ -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
View File

@ -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)

View File

@ -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':
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()

View File

@ -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)