mirror of https://github.com/evilhero/mylar
IMP: Added encrypt_passwords (True/False) to ini as controller, IMP: If encrypt_passwords is enabled, will encrypt all passwords, and apikeys in the ini so as to obfuscate the items
This commit is contained in:
parent
a89bf56b08
commit
5805ed3877
|
@ -27,7 +27,7 @@ from cgi import escape
|
||||||
import urllib
|
import urllib
|
||||||
import re
|
import re
|
||||||
import mylar
|
import mylar
|
||||||
from mylar import logger
|
from mylar import logger, encrypted
|
||||||
|
|
||||||
SESSION_KEY = '_cp_username'
|
SESSION_KEY = '_cp_username'
|
||||||
|
|
||||||
|
@ -37,10 +37,18 @@ def check_credentials(username, password):
|
||||||
# Adapt to your needs
|
# Adapt to your needs
|
||||||
forms_user = cherrypy.request.config['auth.forms_username']
|
forms_user = cherrypy.request.config['auth.forms_username']
|
||||||
forms_pass = cherrypy.request.config['auth.forms_password']
|
forms_pass = cherrypy.request.config['auth.forms_password']
|
||||||
if username == forms_user and password == forms_pass:
|
edc = encrypted.Encryptor(forms_pass)
|
||||||
return None
|
ed_chk = edc.decrypt_it()
|
||||||
|
if mylar.CONFIG.ENCRYPT_PASSWORDS is True:
|
||||||
|
if username == forms_user and all([ed_chk['status'] is True, ed_chk['password'] == password]):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return u"Incorrect username or password."
|
||||||
else:
|
else:
|
||||||
return u"Incorrect username or password."
|
if username == forms_user and password == forms_pass:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return u"Incorrect username or password."
|
||||||
|
|
||||||
def check_auth(*args, **kwargs):
|
def check_auth(*args, **kwargs):
|
||||||
"""A tool that looks in config for 'auth.require'. If found and it
|
"""A tool that looks in config for 'auth.require'. If found and it
|
||||||
|
|
|
@ -10,7 +10,7 @@ import threading
|
||||||
import re
|
import re
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
import mylar
|
import mylar
|
||||||
from mylar import logger, helpers
|
from mylar import logger, helpers, encrypted
|
||||||
|
|
||||||
config = ConfigParser.SafeConfigParser()
|
config = ConfigParser.SafeConfigParser()
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ _CONFIG_DEFINITIONS = OrderedDict({
|
||||||
'FORMAT_BOOKTYPE': (bool, 'General', False),
|
'FORMAT_BOOKTYPE': (bool, 'General', False),
|
||||||
'CLEANUP_CACHE': (bool, 'General', False),
|
'CLEANUP_CACHE': (bool, 'General', False),
|
||||||
'SECURE_DIR': (str, 'General', None),
|
'SECURE_DIR': (str, 'General', None),
|
||||||
|
'ENCRYPT_PASSWORDS': (bool, 'General', False),
|
||||||
|
|
||||||
'RSS_CHECKINTERVAL': (int, 'Scheduler', 20),
|
'RSS_CHECKINTERVAL': (int, 'Scheduler', 20),
|
||||||
'SEARCH_INTERVAL': (int, 'Scheduler', 360),
|
'SEARCH_INTERVAL': (int, 'Scheduler', 360),
|
||||||
|
@ -385,7 +386,7 @@ class Config(object):
|
||||||
count = sum(1 for line in open(self._config_file))
|
count = sum(1 for line in open(self._config_file))
|
||||||
else:
|
else:
|
||||||
count = 0
|
count = 0
|
||||||
self.newconfig = 9
|
self.newconfig = 10
|
||||||
if count == 0:
|
if count == 0:
|
||||||
CONFIG_VERSION = 0
|
CONFIG_VERSION = 0
|
||||||
MINIMALINI = False
|
MINIMALINI = False
|
||||||
|
@ -505,9 +506,11 @@ class Config(object):
|
||||||
shutil.move(self._config_file, os.path.join(mylar.DATA_DIR, 'config.ini.backup'))
|
shutil.move(self._config_file, os.path.join(mylar.DATA_DIR, 'config.ini.backup'))
|
||||||
except:
|
except:
|
||||||
print('Unable to make proper backup of config file in %s' % os.path.join(mylar.DATA_DIR, 'config.ini.backup'))
|
print('Unable to make proper backup of config file in %s' % os.path.join(mylar.DATA_DIR, 'config.ini.backup'))
|
||||||
if self.CONFIG_VERSION < 9:
|
if self.CONFIG_VERSION < 10:
|
||||||
print('Attempting to update configuration..')
|
print('Attempting to update configuration..')
|
||||||
#torznab multiple entries merged into extra_torznabs value
|
#8-torznab multiple entries merged into extra_torznabs value
|
||||||
|
#9-remote rtorrent ssl option
|
||||||
|
#10-encryption of all keys/passwords.
|
||||||
self.config_update()
|
self.config_update()
|
||||||
setattr(self, 'CONFIG_VERSION', str(self.newconfig))
|
setattr(self, 'CONFIG_VERSION', str(self.newconfig))
|
||||||
config.set('General', 'CONFIG_VERSION', str(self.newconfig))
|
config.set('General', 'CONFIG_VERSION', str(self.newconfig))
|
||||||
|
@ -555,7 +558,7 @@ class Config(object):
|
||||||
config.remove_option('Torznab', 'torznab_category')
|
config.remove_option('Torznab', 'torznab_category')
|
||||||
config.remove_option('Torznab', 'torznab_verify')
|
config.remove_option('Torznab', 'torznab_verify')
|
||||||
print('Successfully removed outdated config entries.')
|
print('Successfully removed outdated config entries.')
|
||||||
if self.newconfig == 9:
|
if self.newconfig < 9:
|
||||||
#rejig rtorrent settings due to change.
|
#rejig rtorrent settings due to change.
|
||||||
try:
|
try:
|
||||||
if all([self.RTORRENT_SSL is True, not self.RTORRENT_HOST.startswith('http')]):
|
if all([self.RTORRENT_SSL is True, not self.RTORRENT_HOST.startswith('http')]):
|
||||||
|
@ -565,6 +568,15 @@ class Config(object):
|
||||||
pass
|
pass
|
||||||
config.remove_option('Rtorrent', 'rtorrent_ssl')
|
config.remove_option('Rtorrent', 'rtorrent_ssl')
|
||||||
print('Successfully removed oudated config entries.')
|
print('Successfully removed oudated config entries.')
|
||||||
|
if self.newconfig < 10:
|
||||||
|
#encrypt all passwords / apikeys / usernames in ini file.
|
||||||
|
#leave non-ini items (ie. memory) as un-encrypted items.
|
||||||
|
try:
|
||||||
|
if self.ENCRYPT_PASSWORDS is True:
|
||||||
|
self.encrypt_items(mode='encrypt', updateconfig=True)
|
||||||
|
except Exception as e:
|
||||||
|
print('Error: %s' % e)
|
||||||
|
print('Successfully updated config to version 10 ( password / apikey - .ini encryption )')
|
||||||
print('Configuration upgraded to version %s' % self.newconfig)
|
print('Configuration upgraded to version %s' % self.newconfig)
|
||||||
|
|
||||||
def check_section(self, section, key):
|
def check_section(self, section, key):
|
||||||
|
@ -713,6 +725,10 @@ class Config(object):
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if self.ENCRYPT_PASSWORDS is True:
|
||||||
|
self.encrypt_items(mode='encrypt')
|
||||||
|
|
||||||
|
|
||||||
def writeconfig(self, values=None):
|
def writeconfig(self, values=None):
|
||||||
logger.fdebug("Writing configuration to file")
|
logger.fdebug("Writing configuration to file")
|
||||||
self.provider_sequence()
|
self.provider_sequence()
|
||||||
|
@ -741,6 +757,74 @@ class Config(object):
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.warn("Error writing configuration file: %s", e)
|
logger.warn("Error writing configuration file: %s", e)
|
||||||
|
|
||||||
|
def encrypt_items(self, mode='encrypt', updateconfig=False):
|
||||||
|
encryption_list = OrderedDict({
|
||||||
|
#key section key value
|
||||||
|
'HTTP_PASSWORD': ('Interface', 'http_password', self.HTTP_PASSWORD),
|
||||||
|
'SAB_PASSWORD': ('SABnzbd', 'sab_password', self.SAB_PASSWORD),
|
||||||
|
'SAB_APIKEY': ('SABnzbd', 'sab_apikey', self.SAB_APIKEY),
|
||||||
|
'NZBGET_PASSWORD': ('NZBGet', 'nzbget_password', self.NZBGET_PASSWORD),
|
||||||
|
'NZBSU_APIKEY': ('NZBsu', 'nzbsu_apikey', self.NZBSU_APIKEY),
|
||||||
|
'DOGNZB_APIKEY': ('DOGnzb', 'dognzb_apikey', self.DOGNZB_APIKEY),
|
||||||
|
'UTORRENT_PASSWORD': ('uTorrent', 'utorrent_password', self.UTORRENT_PASSWORD),
|
||||||
|
'TRANSMISSION_PASSWORD': ('Transmission', 'transmission_password', self.TRANSMISSION_PASSWORD),
|
||||||
|
'DELUGE_PASSWORD': ('Deluge', 'deluge_password', self.DELUGE_PASSWORD),
|
||||||
|
'QBITTORRENT_PASSWORD': ('qBittorrent', 'qbittorrent_password', self.QBITTORRENT_PASSWORD),
|
||||||
|
'RTORRENT_PASSWORD': ('Rtorrent', 'rtorrent_password', self.RTORRENT_PASSWORD),
|
||||||
|
'PROWL_KEYS': ('Prowl', 'prowl_keys', self.PROWL_KEYS),
|
||||||
|
'PUSHOVER_APIKEY': ('PUSHOVER', 'pushover_apikey', self.PUSHOVER_APIKEY),
|
||||||
|
'PUSHOVER_USERKEY': ('PUSHOVER', 'pushover_userkey', self.PUSHOVER_USERKEY),
|
||||||
|
'BOXCAR_TOKEN': ('BOXCAR', 'boxcar_token', self.BOXCAR_TOKEN),
|
||||||
|
'PUSHBULLET_APIKEY': ('PUSHBULLET', 'pushbullet_apikey', self.PUSHBULLET_APIKEY),
|
||||||
|
'TELEGRAM_TOKEN': ('TELEGRAM', 'telegram_token', self.TELEGRAM_TOKEN),
|
||||||
|
'COMICVINE_API': ('CV', 'comicvine_api', self.COMICVINE_API),
|
||||||
|
'PASSWORD_32P': ('32P', 'password_32p', self.PASSWORD_32P),
|
||||||
|
'PASSKEY_32P': ('32P', 'passkey_32p', self.PASSKEY_32P),
|
||||||
|
'USERNAME_32P': ('32P', 'username_32p', self.USERNAME_32P),
|
||||||
|
'SEEDBOX_PASS': ('Seedbox', 'seedbox_pass', self.SEEDBOX_PASS),
|
||||||
|
'TAB_PASS': ('Tablet', 'tab_pass', self.TAB_PASS),
|
||||||
|
'API_KEY': ('API', 'api_key', self.API_KEY),
|
||||||
|
'OPDS_PASSWORD': ('OPDS', 'opds_password', self.OPDS_PASSWORD),
|
||||||
|
'PP_SSHPASSWD': ('AutoSnatch', 'pp_sshpasswd', self.PP_SSHPASSWD),
|
||||||
|
})
|
||||||
|
|
||||||
|
new_encrypted = 0
|
||||||
|
for k,v in encryption_list.iteritems():
|
||||||
|
value = []
|
||||||
|
for x in v:
|
||||||
|
value.append(x)
|
||||||
|
|
||||||
|
if value[2] is not None:
|
||||||
|
if value[2][:5] == '^~$z$':
|
||||||
|
if mode == 'decrypt':
|
||||||
|
hp = encrypted.Encryptor(value[2])
|
||||||
|
decrypted_password = hp.decrypt_it()
|
||||||
|
if decrypted_password['status'] is False:
|
||||||
|
logger.warn('Password unable to decrypt - you might have to manually edit the ini for %s to reset the value' % value[1])
|
||||||
|
else:
|
||||||
|
if k != 'HTTP_PASSWORD':
|
||||||
|
setattr(self, k, decrypted_password['password'])
|
||||||
|
config.set(value[0], value[1], decrypted_password['password'])
|
||||||
|
else:
|
||||||
|
if k == 'HTTP_PASSWORD':
|
||||||
|
hp = encrypted.Encryptor(value[2])
|
||||||
|
decrypted_password = hp.decrypt_it()
|
||||||
|
if decrypted_password['status'] is False:
|
||||||
|
logger.warn('Password unable to decrypt - you might have to manually edit the ini for %s to reset the value' % value[1])
|
||||||
|
else:
|
||||||
|
setattr(self, k, decrypted_password['password'])
|
||||||
|
else:
|
||||||
|
hp = encrypted.Encryptor(value[2])
|
||||||
|
encrypted_password = hp.encrypt_it()
|
||||||
|
if encrypted_password['status'] is False:
|
||||||
|
logger.warn('Unable to encrypt password for %s - it has not been encrypted. Keeping it as it is.' % value[1])
|
||||||
|
else:
|
||||||
|
if k == 'HTTP_PASSWORD':
|
||||||
|
#make sure we set the http_password for signon to the encrypted value otherwise won't match
|
||||||
|
setattr(self, k, encrypted_password['password'])
|
||||||
|
config.set(value[0], value[1], encrypted_password['password'])
|
||||||
|
new_encrypted+=1
|
||||||
|
|
||||||
def configure(self, update=False, startup=False):
|
def configure(self, update=False, startup=False):
|
||||||
|
|
||||||
#force alt_pull = 2 on restarts regardless of settings
|
#force alt_pull = 2 on restarts regardless of settings
|
||||||
|
@ -880,6 +964,9 @@ class Config(object):
|
||||||
elif all([self.HTTP_USERNAME is None, self.HTTP_PASSWORD is None]):
|
elif all([self.HTTP_USERNAME is None, self.HTTP_PASSWORD is None]):
|
||||||
self.AUTHENTICATION = 0
|
self.AUTHENTICATION = 0
|
||||||
|
|
||||||
|
if self.ENCRYPT_PASSWORDS is True:
|
||||||
|
self.encrypt_items(mode='decrypt')
|
||||||
|
|
||||||
if all([self.IGNORE_TOTAL is True, self.IGNORE_HAVETOTAL is True]):
|
if all([self.IGNORE_TOTAL is True, self.IGNORE_HAVETOTAL is True]):
|
||||||
self.IGNORE_TOTAL = False
|
self.IGNORE_TOTAL = False
|
||||||
self.IGNORE_HAVETOTAL = False
|
self.IGNORE_HAVETOTAL = False
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of Mylar.
|
||||||
|
#
|
||||||
|
# Mylar 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.
|
||||||
|
#
|
||||||
|
# Mylar 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 Mylar. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import random
|
||||||
|
import base64
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
import mylar
|
||||||
|
from mylar import logger
|
||||||
|
|
||||||
|
class Encryptor(object):
|
||||||
|
def __init__(self, password, chk_password=None):
|
||||||
|
self.password = password.encode('utf-8')
|
||||||
|
|
||||||
|
def encrypt_it(self):
|
||||||
|
try:
|
||||||
|
salt = os.urandom(8)
|
||||||
|
saltedhash = [salt[i] for i in range (0, len(salt))]
|
||||||
|
salted_pass = base64.b64encode('%s%s' % (self.password,salt))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warn('Error when encrypting: %s' % e)
|
||||||
|
return {'status': False}
|
||||||
|
else:
|
||||||
|
return {'status': True, 'password': '^~$z$' + salted_pass}
|
||||||
|
|
||||||
|
def decrypt_it(self):
|
||||||
|
try:
|
||||||
|
if not self.password.startswith('^~$z$'):
|
||||||
|
logger.warn('Error not an encryption that I recognize.')
|
||||||
|
return {'status': False}
|
||||||
|
passd = base64.b64decode(self.password[5:]) #(base64.decodestring(self.password))
|
||||||
|
saltedhash = [bytes(passd[-8:])]
|
||||||
|
except Exception as e:
|
||||||
|
logger.warn('Error when decrypting password: %s' % e)
|
||||||
|
return {'status': False}
|
||||||
|
else:
|
||||||
|
return {'status': True, 'password': passd[:-8]}
|
||||||
|
|
Loading…
Reference in New Issue