From 1a1317a7b623c02b118c8321976e6e5e99748e0b Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 22 Feb 2018 17:58:36 -0800 Subject: [PATCH 1/4] Refactor configuration management code --- onionr/api.py | 15 +++--- onionr/config.py | 107 ++++++++++++++++++++++++++++++++++++++++++ onionr/onionr.py | 31 ++++++------ onionr/onionrutils.py | 26 +++++----- requirements.txt | 1 + 5 files changed, 148 insertions(+), 32 deletions(-) create mode 100644 onionr/config.py diff --git a/onionr/api.py b/onionr/api.py index df5d2236..4175ecdf 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -20,7 +20,7 @@ import flask from flask import request, Response, abort 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 import onionrutils, onionrcrypto @@ -37,31 +37,32 @@ class API: else: return True - def __init__(self, config, debug): + def __init__(self, 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'): + + config.reload() + + if config.get('devmode', True): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) - #logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') else: self._developmentMode = False logger.set_level(logger.LEVEL_INFO) - self.config = config self.debug = debug self._privateDelayTime = 3 self._core = Core() self._crypto = onionrcrypto.OnionrCrypto(self._core) self._utils = onionrutils.OnionrUtils(self._core) app = flask.Flask(__name__) - bindPort = int(self.config['CLIENT']['PORT']) + bindPort = int(config.get('CLIENT')['PORT']) 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": logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken) diff --git a/onionr/config.py b/onionr/config.py new file mode 100644 index 00000000..81e8bbb0 --- /dev/null +++ b/onionr/config.py @@ -0,0 +1,107 @@ +''' + 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 . +''' + +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 key in get_config(): + 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 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) + 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) diff --git a/onionr/onionr.py b/onionr/onionr.py index 3098fe82..ed260450 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -20,8 +20,8 @@ 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, requests, time, logger, platform -import api, core, gui +import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform +import api, core, gui, config, logger from onionrutils import OnionrUtils from netcontroller import NetController @@ -39,12 +39,19 @@ class Onionr: 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. ''' + try: os.chdir(sys.path[0]) except FileNotFoundError: pass - if os.path.exists('dev-enabled'): + # Load global configuration data + + exists = os.path.exists(config.get_config_file()) + config.set_config({'devmode': 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 + + if config.get('devmode', True): self._developmentMode = True logger.set_level(logger.LEVEL_DEBUG) else: @@ -54,7 +61,7 @@ class Onionr: self.onionrCore = core.Core() self.onionrUtils = OnionrUtils(self.onionrCore) - # Get configuration and Handle commands + # Handle commands self.debug = False # Whole application debugging @@ -79,10 +86,8 @@ class Onionr: self.onionrCore.createAddressDB() # Get configuration - self.config = configparser.ConfigParser() - if os.path.exists('data/config.ini'): - self.config.read('data/config.ini') - else: + + if not exists: # Generate default config # Hostname should only be set if different from 127.x.x.x. Important for DNS rebinding attack prevention. if self.debug: @@ -92,9 +97,7 @@ class Onionr: randomPort = random.randint(1024, 65535) if self.onionrUtils.checkPort(randomPort): break - self.config['CLIENT'] = {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION} - with open('data/config.ini', 'w') as configfile: - self.config.write(configfile) + config.set('CLIENT', {'participate': 'true', 'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': API_VERSION}, True) command = '' try: @@ -271,7 +274,7 @@ class Onionr: if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": if self._developmentMode: 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...') if not net.startTor(): sys.exit(1) @@ -280,7 +283,7 @@ class Onionr: time.sleep(1) subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) logger.debug('Started communicator') - api.API(self.config, self.debug) + api.API(self.debug) return @@ -290,7 +293,7 @@ class Onionr: ''' logger.warn('Killing the running daemon') - net = NetController(self.config['CLIENT']['PORT']) + net = NetController(config.get('CLIENT')['PORT']) try: self.onionrUtils.localCommand('shutdown') except requests.exceptions.ConnectionError: diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index b000e799..35ba38e3 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -18,30 +18,34 @@ along with this program. If not, see . ''' # 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 + if sys.version_info < (3, 6): try: import sha3 except ModuleNotFoundError: logger.fatal('On Python 3 versions prior to 3.6.x, you need the sha3 module') sys.exit(1) + class OnionrUtils: - '''Various useful functions''' + ''' + Various useful function + ''' def __init__(self, coreInstance): self.fingerprintFile = 'data/own-fingerprint.txt' self._core = coreInstance return + 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']) + + config.reload() + + # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless. + requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('CLIENT')['PORT']) + '/client/?action=' + command + '&token=' + config.get('CLIENT')['CLIENT HMAC']) return @@ -141,7 +145,7 @@ class OnionrUtils: retVal = False return retVal - + def validatePubKey(self, key): '''Validate if a string is a valid base32 encoded Ed25519 key''' retVal = False @@ -195,5 +199,5 @@ class OnionrUtils: retVal = False if not idNoDomain.isalnum(): retVal = False - - return retVal \ No newline at end of file + + return retVal diff --git a/requirements.txt b/requirements.txt index 77e25537..7338d372 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ simple_crypt==4.1.7 urllib3==1.19.1 sha3==0.2.1 PySocks==1.6.8 +urllib3 From 9998cf6a791af38e9cb67d0985b0d55e205e1487 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 22 Feb 2018 18:01:28 -0800 Subject: [PATCH 2/4] Fix requirements --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7338d372..77e25537 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,3 @@ simple_crypt==4.1.7 urllib3==1.19.1 sha3==0.2.1 PySocks==1.6.8 -urllib3 From c87bf1514679e363409fcd2a93ebd5a31e4a9e04 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 22 Feb 2018 18:25:05 -0800 Subject: [PATCH 3/4] Add config command --- onionr/config.py | 7 +++++-- onionr/onionr.py | 25 ++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/onionr/config.py b/onionr/config.py index 81e8bbb0..793d8b9b 100644 --- a/onionr/config.py +++ b/onionr/config.py @@ -27,7 +27,7 @@ def get(key, default = None): ''' Gets the key from configuration, or returns `default` ''' - if key in get_config(): + if is_set(key): return get_config()[key] return default @@ -42,6 +42,9 @@ def set(key, value = None, savefile = False): 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 @@ -64,7 +67,7 @@ def save(): check() try: with open(get_config_file(), 'w', encoding="utf8") as configfile: - json.dump(get_config(), configfile) + json.dump(get_config(), configfile, indent=2, sort_keys=True) except: logger.warn('Failed to write to configuration file.') diff --git a/onionr/onionr.py b/onionr/onionr.py index ed260450..f06d1268 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -120,13 +120,14 @@ class Onionr: def getCommands(self): return { + 'help': self.showHelp, + 'version': self.version, + 'config': self.configure, 'start': self.start, 'stop': self.killDaemon, - 'version': self.version, + 'stats': self.showStats, 'listpeers': self.listPeers, 'list-peers': self.listPeers, - 'stats': self.showStats, - 'help': self.showHelp, '': self.showHelpSuggestion, 'addmsg': self.addMessage, 'addmessage': self.addMessage, @@ -142,6 +143,7 @@ class Onionr: return { 'help': 'Displays this Onionr help menu', 'version': 'Displays the Onionr version', + 'config': 'Configures something and adds it to the file', 'start': 'Starts the Onionr daemon', 'stop': 'Stops the Onionr daemon', 'stats': 'Displays node statistics', @@ -152,6 +154,23 @@ class 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] + ' ') + logger.info(logger.colors.bold + 'Set a value: ' + logger.colors.reset + sys.argv[0] + ' ' + sys.argv[1] + ' ') + + def execute(self, argument): ''' Executes a command From 39c81ae7d108ae0fed8a6c34d319d02c9475d4c3 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 22 Feb 2018 18:53:49 -0800 Subject: [PATCH 4/4] Add log configuration --- onionr/logger.py | 15 +++++++++++++++ onionr/onionr.py | 12 +++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/onionr/logger.py b/onionr/logger.py index 96d30ad1..12b61702 100644 --- a/onionr/logger.py +++ b/onionr/logger.py @@ -108,6 +108,21 @@ def get_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): ''' Outputs raw data to console without formatting diff --git a/onionr/onionr.py b/onionr/onionr.py index f06d1268..8591322b 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -48,9 +48,19 @@ class Onionr: # Load global configuration data exists = os.path.exists(config.get_config_file()) - config.set_config({'devmode': True}) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it + 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 logger.set_level(logger.LEVEL_DEBUG)