Merge pull request #5 from beardog108/crypto3

Refactor configuration management code
master
Kevin Froman 2018-02-22 21:19:12 -06:00 committed by GitHub
commit 4f28141de6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 197 additions and 35 deletions

View File

@ -20,7 +20,7 @@
import flask import flask
from flask import request, Response, abort from flask import request, Response, abort
from multiprocessing import Process from multiprocessing import Process
import configparser, sys, random, threading, hmac, hashlib, base64, time, math, os, logger import sys, random, threading, hmac, hashlib, base64, time, math, os, logger, config
from core import Core from core import Core
import onionrutils, onionrcrypto import onionrutils, onionrcrypto
@ -37,31 +37,32 @@ class API:
else: else:
return True return True
def __init__(self, config, debug): def __init__(self, debug):
''' '''
Initialize the api server, preping variables for later use 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 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 This also saves the used host (random localhost IP address) to the data folder in host.txt
''' '''
if os.path.exists('dev-enabled'):
config.reload()
if config.get('devmode', True):
self._developmentMode = True self._developmentMode = True
logger.set_level(logger.LEVEL_DEBUG) logger.set_level(logger.LEVEL_DEBUG)
#logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
else: else:
self._developmentMode = False self._developmentMode = False
logger.set_level(logger.LEVEL_INFO) logger.set_level(logger.LEVEL_INFO)
self.config = config
self.debug = debug self.debug = debug
self._privateDelayTime = 3 self._privateDelayTime = 3
self._core = Core() self._core = Core()
self._crypto = onionrcrypto.OnionrCrypto(self._core) self._crypto = onionrcrypto.OnionrCrypto(self._core)
self._utils = onionrutils.OnionrUtils(self._core) self._utils = onionrutils.OnionrUtils(self._core)
app = flask.Flask(__name__) app = flask.Flask(__name__)
bindPort = int(self.config['CLIENT']['PORT']) bindPort = int(config.get('CLIENT')['PORT'])
self.bindPort = bindPort self.bindPort = bindPort
self.clientToken = self.config['CLIENT']['CLIENT HMAC'] self.clientToken = config.get('CLIENT')['CLIENT HMAC']
if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": if not os.environ.get("WERKZEUG_RUN_MAIN") == "true":
logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken) logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken)

110
onionr/config.py Normal file
View File

@ -0,0 +1,110 @@
'''
Onionr - P2P Microblogging Platform & Social network
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
_configfile = os.path.abspath('data/config.json')
_config = {}
def get(key, default = None):
'''
Gets the key from configuration, or returns `default`
'''
if is_set(key):
return get_config()[key]
return default
def set(key, value = None, savefile = False):
'''
Sets the key in configuration to `value`
'''
global _config
_config[key] = value
if savefile:
save()
def is_set(key):
return key in get_config() and not get_config()[key] is None
def check():
'''
Checks if the configuration file exists, creates it if not
'''
try:
if not os.path.exists(os.path.dirname(get_config_file())):
os.path.mkdirs(os.path.dirname(get_config_file()))
if not os.path.isfile(get_config_file()):
open(get_config_file(), 'a', encoding="utf8").close()
save()
except:
logger.warn('Failed to check configuration 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, sort_keys=True)
except:
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:
logger.warn('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)

View File

@ -108,6 +108,21 @@ def get_level():
return _level 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
def raw(data): def raw(data):
''' '''
Outputs raw data to console without formatting Outputs raw data to console without formatting

View File

@ -20,8 +20,8 @@
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, requests, time, logger, platform import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform
import api, core, gui import api, core, gui, config, logger
from onionrutils import OnionrUtils from onionrutils import OnionrUtils
from netcontroller import NetController from netcontroller import NetController
@ -39,12 +39,29 @@ class Onionr:
Main Onionr class. This is for the CLI program, and does not handle much of the logic. Main Onionr class. This is for the CLI program, and does not handle much of the logic.
In general, external programs and plugins should not use this class. In general, external programs and plugins should not use this class.
''' '''
try: try:
os.chdir(sys.path[0]) os.chdir(sys.path[0])
except FileNotFoundError: except FileNotFoundError:
pass pass
if os.path.exists('dev-enabled'): # Load global configuration data
exists = os.path.exists(config.get_config_file())
config.set_config({'devmode': True, 'log.file': True, 'log.console': True, 'log.outputfile': 'data/output.log', 'log.color': True}) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it
config.reload() # this will read the configuration file into memory
settings = 0b000
if config.get('log.color', True):
settings = settings | logger.USE_ANSI
if config.get('log.console', True):
settings = settings | logger.OUTPUT_TO_CONSOLE
if config.get('log.file', False):
settings = settings | logger.OUTPUT_TO_FILE
logger.set_file(config.get('log.outputfile', 'data/output.log'))
logger.set_settings(settings)
if config.get('devmode', True):
self._developmentMode = True self._developmentMode = True
logger.set_level(logger.LEVEL_DEBUG) logger.set_level(logger.LEVEL_DEBUG)
else: else:
@ -54,7 +71,7 @@ class Onionr:
self.onionrCore = core.Core() self.onionrCore = core.Core()
self.onionrUtils = OnionrUtils(self.onionrCore) self.onionrUtils = OnionrUtils(self.onionrCore)
# Get configuration and Handle commands # Handle commands
self.debug = False # Whole application debugging self.debug = False # Whole application debugging
@ -79,10 +96,8 @@ class Onionr:
self.onionrCore.createAddressDB() self.onionrCore.createAddressDB()
# Get configuration # Get configuration
self.config = configparser.ConfigParser()
if os.path.exists('data/config.ini'): if not exists:
self.config.read('data/config.ini')
else:
# Generate default config # Generate default config
# Hostname should only be set if different from 127.x.x.x. Important for DNS rebinding attack prevention. # Hostname should only be set if different from 127.x.x.x. Important for DNS rebinding attack prevention.
if self.debug: if self.debug:
@ -92,9 +107,7 @@ class Onionr:
randomPort = random.randint(1024, 65535) randomPort = random.randint(1024, 65535)
if self.onionrUtils.checkPort(randomPort): if self.onionrUtils.checkPort(randomPort):
break break
self.config['CLIENT'] = {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION} config.set('CLIENT', {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION}, True)
with open('data/config.ini', 'w') as configfile:
self.config.write(configfile)
command = '' command = ''
try: try:
@ -117,13 +130,14 @@ class Onionr:
def getCommands(self): def getCommands(self):
return { return {
'help': self.showHelp,
'version': self.version,
'config': self.configure,
'start': self.start, 'start': self.start,
'stop': self.killDaemon, 'stop': self.killDaemon,
'version': self.version, 'stats': self.showStats,
'listpeers': self.listPeers, 'listpeers': self.listPeers,
'list-peers': self.listPeers, 'list-peers': self.listPeers,
'stats': self.showStats,
'help': self.showHelp,
'': self.showHelpSuggestion, '': self.showHelpSuggestion,
'addmsg': self.addMessage, 'addmsg': self.addMessage,
'addmessage': self.addMessage, 'addmessage': self.addMessage,
@ -139,6 +153,7 @@ class Onionr:
return { return {
'help': 'Displays this Onionr help menu', 'help': 'Displays this Onionr help menu',
'version': 'Displays the Onionr version', 'version': 'Displays the Onionr version',
'config': 'Configures something and adds it to the file',
'start': 'Starts the Onionr daemon', 'start': 'Starts the Onionr daemon',
'stop': 'Stops the Onionr daemon', 'stop': 'Stops the Onionr daemon',
'stats': 'Displays node statistics', 'stats': 'Displays node statistics',
@ -149,6 +164,23 @@ class Onionr:
'gui': 'Opens a graphical interface for Onionr' 'gui': 'Opens a graphical interface for Onionr'
} }
def configure(self):
'''
Displays something from the configuration file, or sets it
'''
if len(sys.argv) >= 4:
config.reload()
config.set(sys.argv[2], sys.argv[3], True)
logger.debug('Configuration file updated.')
elif len(sys.argv) >= 3:
config.reload()
logger.info(logger.colors.bold + sys.argv[2] + ': ' + logger.colors.reset + str(config.get(sys.argv[2], logger.colors.fg.red + 'Not set.')))
else:
logger.info(logger.colors.bold + 'Get a value: ' + logger.colors.reset + sys.argv[0] + ' ' + sys.argv[1] + ' <key>')
logger.info(logger.colors.bold + 'Set a value: ' + logger.colors.reset + sys.argv[0] + ' ' + sys.argv[1] + ' <key> <value>')
def execute(self, argument): def execute(self, argument):
''' '''
Executes a command Executes a command
@ -271,7 +303,7 @@ class Onionr:
if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": if not os.environ.get("WERKZEUG_RUN_MAIN") == "true":
if self._developmentMode: if self._developmentMode:
logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
net = NetController(self.config['CLIENT']['PORT']) net = NetController(config.get('CLIENT')['PORT'])
logger.info('Tor is starting...') logger.info('Tor is starting...')
if not net.startTor(): if not net.startTor():
sys.exit(1) sys.exit(1)
@ -280,7 +312,7 @@ class Onionr:
time.sleep(1) time.sleep(1)
subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) subprocess.Popen(["./communicator.py", "run", str(net.socksPort)])
logger.debug('Started communicator') logger.debug('Started communicator')
api.API(self.config, self.debug) api.API(self.debug)
return return
@ -290,7 +322,7 @@ class Onionr:
''' '''
logger.warn('Killing the running daemon') logger.warn('Killing the running daemon')
net = NetController(self.config['CLIENT']['PORT']) net = NetController(config.get('CLIENT')['PORT'])
try: try:
self.onionrUtils.localCommand('shutdown') self.onionrUtils.localCommand('shutdown')
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:

View File

@ -18,30 +18,34 @@
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, requests, configparser, os, socket, hashlib, logger, sqlite3 import getpass, sys, requests, os, socket, hashlib, logger, sqlite3, config
import nacl.signing, nacl.encoding import nacl.signing, nacl.encoding
if sys.version_info < (3, 6): if sys.version_info < (3, 6):
try: try:
import sha3 import sha3
except ModuleNotFoundError: except ModuleNotFoundError:
logger.fatal('On Python 3 versions prior to 3.6.x, you need the sha3 module') logger.fatal('On Python 3 versions prior to 3.6.x, you need the sha3 module')
sys.exit(1) sys.exit(1)
class OnionrUtils: class OnionrUtils:
'''Various useful functions''' '''
Various useful function
'''
def __init__(self, coreInstance): def __init__(self, coreInstance):
self.fingerprintFile = 'data/own-fingerprint.txt' self.fingerprintFile = 'data/own-fingerprint.txt'
self._core = coreInstance self._core = coreInstance
return return
def localCommand(self, command): def localCommand(self, command):
''' '''
Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers. 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.reload()
config.read('data/config.ini')
else: # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless.
return requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('CLIENT')['PORT']) + '/client/?action=' + command + '&token=' + config.get('CLIENT')['CLIENT HMAC'])
requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config['CLIENT']['PORT']) + '/client/?action=' + command + '&token=' + config['CLIENT']['CLIENT HMAC'])
return return
@ -141,7 +145,7 @@ class OnionrUtils:
retVal = False retVal = False
return retVal return retVal
def validatePubKey(self, key): def validatePubKey(self, key):
'''Validate if a string is a valid base32 encoded Ed25519 key''' '''Validate if a string is a valid base32 encoded Ed25519 key'''
retVal = False retVal = False
@ -195,5 +199,5 @@ class OnionrUtils:
retVal = False retVal = False
if not idNoDomain.isalnum(): if not idNoDomain.isalnum():
retVal = False retVal = False
return retVal return retVal