From 197d47eb7ddcc42719bfa4d686cfcf66113a0bc5 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Thu, 25 Jan 2018 23:22:48 -0800 Subject: [PATCH] Add logger --- .gitignore | 2 + onionr/api.py | 34 +++++---- onionr/communicator.py | 18 +++-- onionr/core.py | 7 +- onionr/logger.py | 153 ++++++++++++++++++++++++++++++++++++++++ onionr/netcontroller.py | 8 +-- onionr/onionr.py | 42 ++++++----- onionr/onionrutils.py | 10 +-- onionr/tests.py | 36 +++++----- 9 files changed, 232 insertions(+), 78 deletions(-) create mode 100644 onionr/logger.py diff --git a/.gitignore b/.gitignore index 724137bc..4f406e73 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ __pycache__/ onionr/data/config.ini onionr/data/*.db onionr/data-old/* +onionr/*.pyc +onionr/*.log diff --git a/onionr/api.py b/onionr/api.py index 37721490..bc3c069e 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, gnupg, os +import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os, logging from core import Core import onionrutils @@ -42,10 +42,13 @@ class API: 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 + 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 @@ -55,13 +58,13 @@ class API: bindPort = int(self.config['CLIENT']['PORT']) self.bindPort = bindPort self.clientToken = self.config['CLIENT']['CLIENT HMAC'] - print(self.clientToken) + logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken) 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' + self.host = '127.0.0.1' hostFile = open('data/host.txt', 'w') hostFile.write(self.host) hostFile.close() @@ -80,11 +83,11 @@ class API: resp.headers['Access-Control-Allow-Origin'] = '*' else: resp.headers['server'] = 'Onionr' - resp.headers['content-type'] = 'text/plain' + resp.headers['Content-Type'] = 'text/plain' resp.headers["Content-Security-Policy"] = "default-src 'none'" - resp.headers['x-frame-options'] = 'deny' + resp.headers['X-Frame-Options'] = 'deny' return resp - + @app.route('/client/') def private_handler(): startTime = math.floor(time.time()) @@ -101,7 +104,7 @@ class API: request.environ.get('werkzeug.server.shutdown')() resp = Response('Goodbye') elif action == 'stats': - resp = Response('something') + resp = Response('me_irl') elif action == 'init': # generate PGP key self._core.generateMainPGP() @@ -156,17 +159,17 @@ class API: resp = Response("Invalid request") return resp - print('Starting client on ' + self.host + ':' + str(bindPort)) - print('Client token:', self.clientToken) + logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...') + logger.debug('Client token: ' + logger.colors.underline + self.clientToken) 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? + + Was X-Request-With used? ''' if self.debug: return @@ -181,7 +184,8 @@ class API: # Validate x-requested-with, to protect against CSRF/metadata leaks if not self._developmentMode: try: - request.headers['x-requested-with'] + request.headers['X-Requested-With'] except: # we exit rather than abort to avoid fingerprinting - sys.exit(1) \ No newline at end of file + logger.debug('Avoiding fingerprinting, exiting...') + sys.exit(1) diff --git a/onionr/communicator.py b/onionr/communicator.py index ae808067..febbd103 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -19,7 +19,7 @@ and code to operate as a daemon, getting commands from the command queue databas You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sqlite3, requests, hmac, hashlib, time, sys, os +import sqlite3, requests, hmac, hashlib, time, sys, os, logger import core class OnionrCommunicate: def __init__(self, debug, developmentMode): @@ -30,8 +30,7 @@ class OnionrCommunicate: self._core = core.Core() blockProcessTimer = 0 blockProcessAmount = 5 - if debug: - print('Communicator debugging enabled') + logger.debug('Communicator debugging enabled.') torID = open('data/hs/hostname').read() # get our own PGP fingerprint @@ -40,7 +39,7 @@ class OnionrCommunicate: self._core.generateMainPGP(torID) with open(fingerprintFile,'r') as f: self.pgpOwnFingerprint = f.read() - print('My PGP fingerprint is ' + self.pgpOwnFingerprint) + logger.info('My PGP fingerprint is ' + logger.colors.underline + self.pgpOwnFingerprint + logger.colors.reset + logger.colors.fg.green + '.') while True: command = self._core.daemonQueue() @@ -51,11 +50,10 @@ class OnionrCommunicate: self.lookupBlocks() self._core.processBlocks() blockProcessTimer = 0 - if debug: - print('Communicator daemon heartbeat') + logger.debug('Communicator daemon heartbeat') if command != False: if command[0] == 'shutdown': - print('Daemon recieved exit command.') + logger.warn('Daemon recieved exit command.') break time.sleep(1) return @@ -94,11 +92,11 @@ class OnionrCommunicate: # skip hash if it isn't valid continue else: - print('adding', i, 'to hash database') + logger.debug('Adding ' + i + ' to hash database...') self._core.addToBlockDB(i) return - + def performGet(self, action, peer, data=None, type='tor'): '''performs a request to a peer through Tor or i2p (currently only tor)''' if not peer.endswith('.onion') and not peer.endswith('.onion/'): @@ -114,7 +112,7 @@ class OnionrCommunicate: except requests.exceptions.RequestException: return False return r.text - + shouldRun = False debug = False diff --git a/onionr/core.py b/onionr/core.py index b5507511..4943b5da 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sqlite3, os, sys, time, math, gnupg, base64, tarfile, getpass, simplecrypt, hashlib, nacl +import sqlite3, os, sys, time, math, gnupg, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger from Crypto.Cipher import AES from Crypto import Random import netcontroller @@ -26,7 +26,7 @@ if sys.version_info < (3, 6): try: import sha3 except ModuleNotFoundError: - sys.stderr.write('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) class Core: @@ -66,7 +66,7 @@ class Core: conn = sqlite3.connect(self.peerDB) c = conn.cursor() t = (peerID, name, 'unknown') - c.execute('Insert into peers (id, name, dateSeen) values(?, ?, ?);', t) + c.execute('insert into peers (id, name, dateSeen) values(?, ?, ?);', t) conn.commit() conn.close() return True @@ -295,4 +295,3 @@ class Core: for i in row: retData += i return retData - \ No newline at end of file diff --git a/onionr/logger.py b/onionr/logger.py new file mode 100644 index 00000000..fad5e95e --- /dev/null +++ b/onionr/logger.py @@ -0,0 +1,153 @@ +''' + Onionr - P2P Microblogging Platform & Social network + + This file handles all operations involving logging +''' +''' + 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 re + +class colors: + ''' + This class allows you to set the color if ANSI codes are supported + ''' + reset='\033[0m' + bold='\033[01m' + disable='\033[02m' + underline='\033[04m' + reverse='\033[07m' + strikethrough='\033[09m' + invisible='\033[08m' + italics='\033[3m' + class fg: + black='\033[30m' + red='\033[31m' + green='\033[32m' + orange='\033[33m' + blue='\033[34m' + purple='\033[35m' + cyan='\033[36m' + lightgrey='\033[37m' + darkgrey='\033[90m' + lightred='\033[91m' + lightgreen='\033[92m' + yellow='\033[93m' + lightblue='\033[94m' + pink='\033[95m' + lightcyan='\033[96m' + class bg: + black='\033[40m' + red='\033[41m' + green='\033[42m' + orange='\033[43m' + blue='\033[44m' + purple='\033[45m' + cyan='\033[46m' + lightgrey='\033[47m' + @staticmethod + def filter(data): + return re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]').sub('', str(data)) + +''' + Use the bitwise operators to merge these settings +''' +USE_ANSI = 0b100 +OUTPUT_TO_CONSOLE = 0b010 +OUTPUT_TO_FILE = 0b001 + +LEVEL_DEBUG = 1 +LEVEL_INFO = 2 +LEVEL_WARN = 3 +LEVEL_ERROR = 4 +LEVEL_FATAL = 5 + +_type = OUTPUT_TO_CONSOLE | USE_ANSI # the default settings for logging +_level = LEVEL_DEBUG # the lowest level to log +_outputfile = './output.log' # the file to log to + +''' + Set the settings for the logger using bitwise operators +''' +def set_settings(type): + global _type + _type = type + +''' + Get settings from the logger +''' +def get_settings(): + return _type + +''' + Set the lowest log level to output +''' +def set_level(level): + global _level + _level = level + +''' + Get the lowest log level currently being outputted +''' +def get_level(): + return _level + +''' + Outputs raw data to console without formatting +''' +def raw(data): + if get_settings() & OUTPUT_TO_CONSOLE: + print(data) + if get_settings() & OUTPUT_TO_FILE: + with open(_outputfile, "a+") as f: + f.write(colors.filter(data) + '\n') + +''' + Logs the data + prefix : The prefix to the output + data : The actual data to output + color : The color to output before the data +''' +def log(prefix, data, color = ''): + output = colors.reset + str(color) + '[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' + str(data) + colors.reset + if not get_settings() & USE_ANSI: + output = colors.filter(output) + + raw(output) + +# debug: when there is info that could be useful for debugging purposes only +def debug(data): + if get_level() <= LEVEL_DEBUG: + log('/', data) + +# info: when there is something to notify the user of, such as the success of a process +def info(data): + if get_level() <= LEVEL_INFO: + log('+', data, colors.fg.green) + +# warn: when there is a potential for something bad to happen +def warn(data): + if get_level() <= LEVEL_WARN: + log('!', data, colors.fg.orange) + +# error: when only one function, module, or process of the program encountered a problem and must stop +def error(data): + if get_level() <= LEVEL_ERROR: + log('-', data, colors.fg.red) + +# fatal: when the something so bad has happened that the prorgam must stop +def fatal(data): + if get_level() <= LEVEL_FATAL: + log('#', data, colors.bg.red + colors.fg.black) diff --git a/onionr/netcontroller.py b/onionr/netcontroller.py index 7607bcba..ca229826 100644 --- a/onionr/netcontroller.py +++ b/onionr/netcontroller.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import subprocess, os, random, sys +import subprocess, os, random, sys, logger class NetController: '''NetController This class handles hidden service setup on Tor and I2P @@ -56,10 +56,10 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + ''' if 'Bootstrapped 100%: Done' in line.decode(): break elif 'Opening Socks listener' in line.decode(): - print(line.decode()) - print('Finished starting Tor') + logger.debug(line.decode()) + logger.info('Finished starting Tor') self.readyState = True myID = open('data/hs/hostname', 'r') self.myID = myID.read() myID.close() - return \ No newline at end of file + return diff --git a/onionr/onionr.py b/onionr/onionr.py index 51dfd24e..af4f975c 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 ''' - Onionr - P2P Microblogging Platform & Social network. + Onionr - P2P Microblogging Platform & Social network. Onionr is the name for both the protocol and the original/reference software. @@ -20,10 +20,9 @@ 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 -import gui, api, colors, core +import sys, os, configparser, base64, random, getpass, shutil, subprocess, requests, time, logger +import gui, api, core from onionrutils import OnionrUtils -from colors import Colors from netcontroller import NetController class Onionr: @@ -37,18 +36,18 @@ class Onionr: except FileNotFoundError: pass if os.path.exists('dev-enabled'): - print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') self._developmentMode = True + logger.set_level(logger.LEVEL_DEBUG) + logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)') else: self._developmentMode = False - - colors = Colors() + logger.set_level(logger.LEVEL_INFO) self.onionrCore = core.Core() self.onionrUtils = OnionrUtils(self.onionrCore) # Get configuration and Handle commands - + self.debug = False # Whole application debugging if os.path.exists('data-encrypted.dat'): @@ -59,12 +58,12 @@ class Onionr: if os.path.exists('data/'): break else: - print('Failed to decrypt: ' + result[1]) + logger.error('Failed to decrypt: ' + result[1]) else: if not os.path.exists('data/'): os.mkdir('data/') os.mkdir('data/blocks/') - + if not os.path.exists('data/peers.db'): self.onionrCore.createPeerDB() pass @@ -94,7 +93,7 @@ class Onionr: 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).') + logger.fatal('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') @@ -110,32 +109,31 @@ class Onionr: elif command == 'help' or command == '--help': self.showHelp() elif command == '': - print('Do', sys.argv[0], ' --help for Onionr help.') + logger.info('Do ' + logger.colors.bold + sys.argv[0] + ' --help' + logger.colors.reset + logger.colors.fg.green + ' for Onionr help.') else: - print(colors.RED, 'Invalid Command', colors.RESET) - + logger.error('Invalid command.') + if not self._developmentMode: - encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory.') + encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory: ') self.onionrCore.dataDirEncrypt(encryptionPassword) shutil.rmtree('data/') return def daemon(self): ''' Start the Onionr communication daemon ''' - colors = Colors() if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": net = NetController(self.config['CLIENT']['PORT']) - print('Tor is starting...') + logger.info('Tor is starting...') net.startTor() - print(colors.GREEN + 'Started Tor .onion service: ' + colors.UNDERLINE + net.myID + colors.RESET) + logger.info('Started Tor .onion service: ' + logger.colors.underline + net.myID) time.sleep(1) subprocess.Popen(["./communicator.py", "run", str(net.socksPort)]) - print('Started communicator') + logger.debug('Started communicator') api.API(self.config, self.debug) return def killDaemon(self): '''Shutdown the Onionr Daemon''' - print('Killing the running daemon') + logger.warn('Killing the running daemon') try: self.onionrUtils.localCommand('shutdown') except requests.exceptions.ConnectionError: @@ -149,6 +147,6 @@ class Onionr: def showHelp(self): '''Show help for Onionr''' return - -Onionr() \ No newline at end of file + +Onionr() diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 85e33780..a1213341 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -18,12 +18,12 @@ 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, gnupg, hashlib +import getpass, sys, requests, configparser, os, socket, gnupg, hashlib, logger if sys.version_info < (3, 6): try: import sha3 except ModuleNotFoundError: - sys.stderr.write('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) class OnionrUtils: '''Various useful functions''' @@ -33,7 +33,7 @@ class OnionrUtils: return def printErr(self, text='an error occured'): '''Print an error message to stderr with a new line''' - sys.stderr.write(text + '\n') + logger.error(text) 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() @@ -51,7 +51,7 @@ class OnionrUtils: print('Confirm password: ') pass2 = getpass.getpass() if pass1 != pass2: - print("Passwords do not match.") + logger.error("Passwords do not match.") input() else: break @@ -101,4 +101,4 @@ class OnionrUtils: int(data, 16) except ValueError: retVal = False - return retVal \ No newline at end of file + return retVal diff --git a/onionr/tests.py b/onionr/tests.py index 60c28de3..93c5ba38 100755 --- a/onionr/tests.py +++ b/onionr/tests.py @@ -14,18 +14,18 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import unittest, sys, os, base64, tarfile, shutil, simplecrypt +import unittest, sys, os, base64, tarfile, shutil, simplecrypt, logger class OnionrTests(unittest.TestCase): def testPython3(self): if sys.version_info.major != 3: - print(sys.version_info.major) + logger.debug('Python version: ' + sys.version_info.major) self.assertTrue(False) else: self.assertTrue(True) def testNone(self): - print('--------------------------') - print('Running simple program run test') + logger.debug('--------------------------') + logger.info('Running simple program run test...') # Test just running ./onionr with no arguments blank = os.system('./onionr.py') if blank != 0: @@ -33,8 +33,8 @@ class OnionrTests(unittest.TestCase): else: self.assertTrue(True) def testPeer_a_DBCreation(self): - print('--------------------------') - print('Running peer db creation test') + logger.debug('--------------------------') + logger.info('Running peer db creation test...') if os.path.exists('data/peers.db'): os.remove('data/peers.db') import core @@ -45,8 +45,8 @@ class OnionrTests(unittest.TestCase): else: self.assertTrue(False) def testPeer_b_addPeerToDB(self): - print('--------------------------') - print('Running peer db insertion test') + logger.debug('--------------------------') + logger.info('Running peer db insertion test...') import core myCore = core.Core() if not os.path.exists('data/peers.db'): @@ -58,8 +58,8 @@ class OnionrTests(unittest.TestCase): def testData_b_Encrypt(self): self.assertTrue(True) return - print('--------------------------') - print('Running data dir encrypt test') + logger.debug('--------------------------') + logger.info('Running data dir encrypt test...') import core myCore = core.Core() myCore.dataDirEncrypt('password') @@ -70,8 +70,8 @@ class OnionrTests(unittest.TestCase): def testData_a_Decrypt(self): self.assertTrue(True) return - print('--------------------------') - print('Running data dir decrypt test') + logger.debug('--------------------------') + logger.info('Running data dir decrypt test...') import core myCore = core.Core() myCore.dataDirDecrypt('password') @@ -80,8 +80,8 @@ class OnionrTests(unittest.TestCase): else: self.assertTrue(False) def testPGPGen(self): - print('--------------------------') - print('Testing PGP key generation') + logger.debug('--------------------------') + logger.info('Running PGP key generation test...') if os.path.exists('data/pgp/'): self.assertTrue(True) else: @@ -94,8 +94,8 @@ class OnionrTests(unittest.TestCase): if os.path.exists('data/pgp/'): self.assertTrue(True) def testHMACGen(self): - print('--------------------------') - print('running daemon queue test') + logger.debug('--------------------------') + logger.info('Running HMAC generation test...') # Test if hmac key generation is working import core myCore = core.Core() @@ -105,8 +105,8 @@ class OnionrTests(unittest.TestCase): else: self.assertTrue(False) def testQueue(self): - print('--------------------------') - print('running daemon queue test') + logger.debug('--------------------------') + logger.info('Running daemon queue test...') # test if the daemon queue can read/write data import core myCore = core.Core()