From cd5a8b2be546fe490c16dc353d17f2b5ad5a70d0 Mon Sep 17 00:00:00 2001 From: evilhero Date: Sat, 24 Jan 2015 21:26:22 -0500 Subject: [PATCH] IMP: Added HTTPS support, with certificate/key generation, FIX: If a series had a ', search results would miss some key results when not hitting rss feeds, IMP: Better handling of series' that have non-alphanumeric characters when it comes to requesting search results, FIX: Removed some unnecessary spamming when file-checking/manual post-processing in relation to # signs --- Mylar.py | 77 +++++++++++++++++++++++---- data/interfaces/default/config.html | 51 ++++++++++++++++-- lib/certgen.py | 82 +++++++++++++++++++++++++++++ mylar/__init__.py | 24 ++++++++- mylar/filechecker.py | 2 - mylar/helpers.py | 36 +++++++++++++ mylar/rsscheck.py | 5 +- mylar/search.py | 16 ++++-- mylar/webserve.py | 34 ++++++++---- mylar/webstart.py | 63 ++++++++++++++++++---- 10 files changed, 346 insertions(+), 44 deletions(-) create mode 100644 lib/certgen.py diff --git a/Mylar.py b/Mylar.py index da385146..1e10a0e0 100644 --- a/Mylar.py +++ b/Mylar.py @@ -15,6 +15,8 @@ # along with Mylar. If not, see . import os, sys, locale +import errno +import shutil import time import threading import signal @@ -64,6 +66,7 @@ def main(): parser.add_argument('-q', '--quiet', action='store_true', help='Turn off console logging') parser.add_argument('-d', '--daemon', action='store_true', help='Run as a daemon') parser.add_argument('-p', '--port', type=int, help='Force mylar to run on a specified port') + parser.add_argument('-b', '--backup', action='store_true', help='Will automatically backup & keep the last 2 copies of the .db & ini files prior to startup') parser.add_argument('--datadir', help='Specify a directory where to store your data files') parser.add_argument('--config', help='Specify a config file to use') parser.add_argument('--nolaunch', action='store_true', help='Prevent browser from launching on startup') @@ -85,7 +88,6 @@ def main(): # except Exception, e: # sys.exit('Mylar failed to update.') - if args.daemon: if sys.platform == 'win32': print "Daemonize not supported under Windows, starting normally" @@ -142,6 +144,47 @@ def main(): # Put the database in the DATA_DIR mylar.DB_FILE = os.path.join(mylar.DATA_DIR, 'mylar.db') + # backup the db and configs before they load. + if args.backup: + print '[AUTO-BACKUP] Backing up .db and config.ini files for safety.' + backupdir = os.path.join(mylar.DATA_DIR, 'backup') + + try: + os.makedirs(backupdir) + print '[AUTO-BACKUP] Directory does not exist for backup - creating : ' + backupdir + except OSError as exception: + if exception.errno != errno.EEXIST: + print '[AUTO-BACKUP] Directory already exists.' + raise + + i = 0 + while (i < 2): + if i == 0: + ogfile = mylar.DB_FILE + back = os.path.join(backupdir, 'mylar.db') + back_1 = os.path.join(backupdir, 'mylar.db.1') + else: + ogfile = mylar.CONFIG_FILE + back = os.path.join(backupdir, 'config.ini') + back_1 = os.path.join(backupdir, 'config.ini.1') + + try: + print '[AUTO-BACKUP] Now Backing up mylar.db file' + if os.path.isfile(back_1): + print '[AUTO-BACKUP] ' + back_1 + ' exists. Deleting and keeping new.' + os.remove(back_1) + if os.path.isfile(back): + print '[AUTO-BACKUP] Now renaming ' + back + ' to ' + back_1 + shutil.move(back, back_1) + print '[AUTO-BACKUP] Now copying db file to ' + back + shutil.copy(ogfile, back) + + except OSError as exception: + if exception.errno != errno.EXIST: + raise + + i+=1 + mylar.CFG = ConfigObj(mylar.CONFIG_FILE, encoding='utf-8') # Rename the main thread @@ -160,16 +203,32 @@ def main(): else: http_port = int(mylar.HTTP_PORT) + # Check if pyOpenSSL is installed. It is required for certificate generation + # and for CherryPy. + if mylar.ENABLE_HTTPS: + try: + import OpenSSL + except ImportError: + logger.warn("The pyOpenSSL module is missing. Install this " \ + "module to enable HTTPS. HTTPS will be disabled.") + mylar.ENABLE_HTTPS = False + + # Try to start the server. Will exit here is address is already in use. + web_config = { + 'http_port': http_port, + 'http_host': mylar.HTTP_HOST, + 'http_root': mylar.HTTP_ROOT, + 'enable_https': mylar.ENABLE_HTTPS, + 'https_cert': mylar.HTTPS_CERT, + 'https_key': mylar.HTTPS_KEY, + 'http_username': mylar.HTTP_USERNAME, + 'http_password': mylar.HTTP_PASSWORD, + } + # Try to start the server. - webstart.initialize({ - 'http_port': http_port, - 'http_host': mylar.HTTP_HOST, - 'http_root': mylar.HTTP_ROOT, - 'http_username': mylar.HTTP_USERNAME, - 'http_password': mylar.HTTP_PASSWORD, - }) + webstart.initialize(web_config) - logger.info('Starting Mylar on port: %i' % http_port) + #logger.info('Starting Mylar on port: %i' % http_port) if mylar.LAUNCH_BROWSER and not args.nolaunch: mylar.launch_browser(mylar.HTTP_HOST, http_port, mylar.HTTP_ROOT) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index c53cda41..fa01e4e2 100755 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -54,7 +54,7 @@


+ by buying me a coffee (or several)

@@ -120,9 +120,27 @@ e.g. localhost or 0.0.0.0
+
+ + +
- - +
+ + +
+
+
+ + +
+
+ + +
+
@@ -136,6 +154,7 @@
+
Annual Handling @@ -1149,7 +1174,24 @@ $('#api_key').val(data); }); }); - + if ($("#enable_https").is(":checked")) + { + $("#https_options").show(); + } + else + { + $("#https_options").hide(); + } + $("#enable_https").click(function(){ + if ($("#enable_https").is(":checked")) + { + $("#https_options").slideDown(); + } + else + { + $("#https_options").slideUp(); + } + }); $('#sab_apikey').click(function(){ $('#sab_apikey').select() }); $("#find_sabapi").click(function(){ $.get('findsabAPI', @@ -1185,6 +1227,7 @@ }); initActions(); initConfigCheckbox("#launch_browser"); + initConfigCheckbox("#enable_https"); initConfigCheckbox("#enable_api"); initConfigCheckbox("#usenewznab"); initConfigCheckbox("#usenzbsu"); diff --git a/lib/certgen.py b/lib/certgen.py new file mode 100644 index 00000000..1b941161 --- /dev/null +++ b/lib/certgen.py @@ -0,0 +1,82 @@ +# -*- coding: latin-1 -*- +# +# Copyright (C) Martin Sjögren and AB Strakt 2001, All rights reserved +# Copyright (C) Jean-Paul Calderone 2008, All rights reserved +# This file is licenced under the GNU LESSER GENERAL PUBLIC LICENSE Version 2.1 or later (aka LGPL v2.1) +# Please see LGPL2.1.txt for more information +""" +Certificate generation module. +""" + +from OpenSSL import crypto +import time + +TYPE_RSA = crypto.TYPE_RSA +TYPE_DSA = crypto.TYPE_DSA + +serial = int(time.time()) + + +def createKeyPair(type, bits): + """ + Create a public/private key pair. + + Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA + bits - Number of bits to use in the key + Returns: The public/private key pair in a PKey object + """ + pkey = crypto.PKey() + pkey.generate_key(type, bits) + return pkey + +def createCertRequest(pkey, digest="md5", **name): + """ + Create a certificate request. + + Arguments: pkey - The key to associate with the request + digest - Digestion method to use for signing, default is md5 + **name - The name of the subject of the request, possible + arguments are: + C - Country name + ST - State or province name + L - Locality name + O - Organization name + OU - Organizational unit name + CN - Common name + emailAddress - E-mail address + Returns: The certificate request in an X509Req object + """ + req = crypto.X509Req() + subj = req.get_subject() + + for (key,value) in name.items(): + setattr(subj, key, value) + + req.set_pubkey(pkey) + req.sign(pkey, digest) + return req + +def createCertificate(req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest="md5"): + """ + Generate a certificate given a certificate request. + + Arguments: req - Certificate reqeust to use + issuerCert - The certificate of the issuer + issuerKey - The private key of the issuer + serial - Serial number for the certificate + notBefore - Timestamp (relative to now) when the certificate + starts being valid + notAfter - Timestamp (relative to now) when the certificate + stops being valid + digest - Digest method to use for signing, default is md5 + Returns: The signed certificate in an X509 object + """ + cert = crypto.X509() + cert.set_serial_number(serial) + cert.gmtime_adj_notBefore(notBefore) + cert.gmtime_adj_notAfter(notAfter) + cert.set_issuer(issuerCert.get_subject()) + cert.set_subject(req.get_subject()) + cert.set_pubkey(req.get_pubkey()) + cert.sign(issuerKey, digest) + return cert diff --git a/mylar/__init__.py b/mylar/__init__.py index ee085406..966a3d43 100755 --- a/mylar/__init__.py +++ b/mylar/__init__.py @@ -51,6 +51,7 @@ DAEMON = False PIDFILE= None CREATEPID = False SAFESTART = False +AUTO_UPDATE = False SCHED = Scheduler() @@ -104,6 +105,9 @@ HTTP_HOST = None HTTP_USERNAME = None HTTP_PASSWORD = None HTTP_ROOT = None +ENABLE_HTTPS = False +HTTPS_CERT = None +HTTPS_KEY = None HTTPS_FORCE_ON = False API_ENABLED = False API_KEY = None @@ -373,7 +377,7 @@ def initialize(): with INIT_LOCK: global __INITIALIZED__, DBCHOICE, DBUSER, DBPASS, DBNAME, COMICVINE_API, DEFAULT_CVAPI, CVAPI_COUNT, CVAPI_TIME, CVAPI_MAX, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, COMICSORT, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, MAX_LOGSIZE, LOGVERBOSE, OLDCONFIG_VERSION, OS_DETECT, OS_LANG, OS_ENCODING, \ - queue, HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTPS_FORCE_ON, API_ENABLED, API_KEY, LAUNCH_BROWSER, GIT_PATH, SAFESTART, \ + queue, HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, HTTPS_FORCE_ON, API_ENABLED, API_KEY, LAUNCH_BROWSER, GIT_PATH, SAFESTART, AUTO_UPDATE, \ CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, USER_AGENT, DESTINATION_DIR, MULTIPLE_DEST_DIRS, CREATE_FOLDERS, \ DOWNLOAD_DIR, USENET_RETENTION, SEARCH_INTERVAL, NZB_STARTUP_SEARCH, INTERFACE, DUPECONSTRAINT, AUTOWANT_ALL, AUTOWANT_UPCOMING, ZERO_LEVEL, ZERO_LEVEL_N, COMIC_COVER_LOCAL, HIGHCOUNT, \ LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, NZB_DOWNLOADER, USE_SABNZBD, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_PRIORITY, SAB_DIRECTORY, USE_BLACKHOLE, BLACKHOLE_DIR, ADD_COMICS, COMIC_DIR, IMP_MOVE, IMP_RENAME, IMP_METADATA, \ @@ -410,6 +414,11 @@ def initialize(): if HTTP_PORT < 21 or HTTP_PORT > 65535: HTTP_PORT = 8090 + + if HTTPS_CERT == '': + HTTPS_CERT = os.path.join(DATA_DIR, 'server.crt') + if HTTPS_KEY == '': + HTTPS_KEY = os.path.join(DATA_DIR, 'server.key') CONFIG_VERSION = check_setting_str(CFG, 'General', 'config_version', '') DBCHOICE = check_setting_str(CFG, 'General', 'dbchoice', 'sqlite3') @@ -427,10 +436,14 @@ def initialize(): HTTP_USERNAME = check_setting_str(CFG, 'General', 'http_username', '') HTTP_PASSWORD = check_setting_str(CFG, 'General', 'http_password', '') HTTP_ROOT = check_setting_str(CFG, 'General', 'http_root', '/') + ENABLE_HTTPS = bool(check_setting_int(CFG, 'General', 'enable_https', 0)) + HTTPS_CERT = check_setting_str(CFG, 'General', 'https_cert', '') + HTTPS_KEY = check_setting_str(CFG, 'General', 'https_key', '') HTTPS_FORCE_ON = bool(check_setting_int(CFG, 'General', 'https_force_on', 0)) API_ENABLED = bool(check_setting_int(CFG, 'General', 'api_enabled', 0)) API_KEY = check_setting_str(CFG, 'General', 'api_key', '') LAUNCH_BROWSER = bool(check_setting_int(CFG, 'General', 'launch_browser', 1)) + AUTO_UPDATE = bool(check_setting_int(CFG, 'General', 'auto_update', 0)) LOGVERBOSE = bool(check_setting_int(CFG, 'General', 'logverbose', 0)) if LOGVERBOSE: VERBOSE = 2 @@ -924,6 +937,11 @@ def initialize(): else: LATEST_VERSION = CURRENT_VERSION +# if AUTO_UPDATE: +# if CURRENT_VERSION != LATEST_VERSION and INSTALL_TYPE != 'win' and COMMITS_BEHIND > 0: +# logger.info('Auto-updating has been enabled. Attempting to auto-update.') +# SIGNAL = 'update' + #check for syno_fix here if SYNO_FIX: parsepath = os.path.join(DATA_DIR, 'bs4', 'builder', '_lxml.py') @@ -1100,10 +1118,14 @@ def config_write(): new_config['General']['http_username'] = HTTP_USERNAME new_config['General']['http_password'] = HTTP_PASSWORD new_config['General']['http_root'] = HTTP_ROOT + new_config['General']['enable_https'] = int(ENABLE_HTTPS) + new_config['General']['https_cert'] = HTTPS_CERT + new_config['General']['https_key'] = HTTPS_KEY new_config['General']['https_force_on'] = int(HTTPS_FORCE_ON) new_config['General']['api_enabled'] = int(API_ENABLED) new_config['General']['api_key'] = API_KEY new_config['General']['launch_browser'] = int(LAUNCH_BROWSER) + new_config['General']['auto_update'] = int(AUTO_UPDATE) new_config['General']['log_dir'] = LOG_DIR new_config['General']['max_logsize'] = MAX_LOGSIZE new_config['General']['logverbose'] = int(LOGVERBOSE) diff --git a/mylar/filechecker.py b/mylar/filechecker.py index 592b9fe7..4e8ce34b 100755 --- a/mylar/filechecker.py +++ b/mylar/filechecker.py @@ -447,10 +447,8 @@ def listFiles(dir,watchcomic,Publisher,AlternateSearch=None,manual=None,sarc=Non fndit = 0 blspc = 0 if nono == '#': - logger.info('# detected.') fndit = subname.find(nono) if subname[fndit+1].isdigit(): - logger.info('# detected before digits - assuming issue number.') subname = re.sub('#','',subname) continue while x < subcnt: diff --git a/mylar/helpers.py b/mylar/helpers.py index ae552ec3..7471276a 100755 --- a/mylar/helpers.py +++ b/mylar/helpers.py @@ -1649,6 +1649,42 @@ def duplicate_filecheck(filename, ComicID=None, IssueID=None, StoryArcID=None): rtnval = "write" return rtnval +def create_https_certificates(ssl_cert, ssl_key): + """ + Create a pair of self-signed HTTPS certificares and store in them in + 'ssl_cert' and 'ssl_key'. Method assumes pyOpenSSL is installed. + + This code is stolen from SickBeard (http://github.com/midgetspy/Sick-Beard). + """ + + from mylar import logger + + from OpenSSL import crypto + from lib.certgen import createKeyPair, createCertRequest, createCertificate, \ + TYPE_RSA, serial + + # Create the CA Certificate + cakey = createKeyPair(TYPE_RSA, 2048) + careq = createCertRequest(cakey, CN="Certificate Authority") + cacert = createCertificate(careq, (careq, cakey), serial, (0, 60 * 60 * 24 * 365 * 10)) # ten years + + pkey = createKeyPair(TYPE_RSA, 2048) + req = createCertRequest(pkey, CN="Mylar") + cert = createCertificate(req, (cacert, cakey), serial, (0, 60 * 60 * 24 * 365 * 10)) # ten years + + # Save the key and certificate to disk + try: + with open(ssl_key, "w") as fp: + fp.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) + with open(ssl_cert, "w") as fp: + fp.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) + except IOError as e: + logger.error("Error creating SSL key and certificate: %s", e) + return False + + return True + + from threading import Thread class ThreadWithReturnValue(Thread): diff --git a/mylar/rsscheck.py b/mylar/rsscheck.py index c1a764f9..473a4a88 100755 --- a/mylar/rsscheck.py +++ b/mylar/rsscheck.py @@ -789,8 +789,9 @@ def nzbdbsearch(seriesname,issue,comicid=None,nzbprov=None,searchYear=None,Comic return nzbinfo def torsend2client(seriesname, issue, seriesyear, linkit, site): - logger.info('matched on ' + str(seriesname)) - filename = re.sub('[\'\!\@\#\$\%\:\;\/\\=\?\.]', '',seriesname) + logger.info('matched on ' + seriesname) + filename = helpers.filesafe(seriesname) + #filename = re.sub('[\'\!\@\#\$\%\:\;\/\\=\?\.]', '',seriesname) filename = re.sub(' ', '_', filename) filename += "_" + str(issue) + "_" + str(seriesyear) if site == 'CBT': diff --git a/mylar/search.py b/mylar/search.py index 13cb741a..10a23eb1 100755 --- a/mylar/search.py +++ b/mylar/search.py @@ -37,9 +37,14 @@ def search_init(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueD unaltered_ComicName = None if filesafe: if filesafe != ComicName and mode != 'want_ann': - logger.info('[SEARCH] altering ComicName to search-safe Name : ' + filesafe) + logger.info('[SEARCH] Special Characters exist within Series Title. Enabling search-safe Name : ' + filesafe) + if AlternateSearch is None or AlternateSearch == 'None': + AlternateSearch = filesafe + else: + AlternateSearch += '##' + filesafe unaltered_ComicName = ComicName - ComicName = filesafe + #ComicName = filesafe + logger.info('AlternateSearch is : ' + AlternateSearch) if ComicYear == None: ComicYear = '2014' else: ComicYear = str(ComicYear)[:4] if Publisher == 'IDW Publishing': Publisher = 'IDW' @@ -464,7 +469,7 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa elif nzbprov == 'KAT': cmname = re.sub("%20", " ", str(comsrc)) logger.fdebug("Sending request to [KAT] for " + str(cmname) + " : " + str(mod_isssearch)) - bb = rsscheck.torrents(pickfeed='KAT',seriesname=cmname,issue=mod_isssearch) + bb = rsscheck.torrents(pickfeed='KAT',seriesname=cmname,issue=mod_isssearch)#cmname,issue=mod_isssearch) rss = "no" #if bb is not None: logger.fdebug("results: " + str(bb)) elif nzbprov != 'experimental': @@ -1018,6 +1023,7 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa watchcomic_split = helpers.cleanName(str(findcomic)) if '&' in watchcomic_split: watchcomic_split = re.sub('[/&]','and', watchcomic_split) watchcomic_nonsplit = re.sub('[\-\:\,\.\?]', ' ', watchcomic_split) + watchcomic_nonsplit = re.sub('\'', '', watchcomic_nonsplit) watchcomic_split = watchcomic_nonsplit.split(None) logger.fdebug(str(splitit) + " nzb series word count: " + str(splitst)) @@ -1288,7 +1294,7 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa if '[RSS]' in tmpprov : tmpprov = re.sub('\[RSS\]','', tmpprov).strip() updater.nzblog(IssueID, nzbname, ComicName, SARC, IssueArcID, nzbid, tmpprov) #send out the notifications for the snatch. - notify_snatch(nzbname, sent_to, modcomicname, comyear, IssueNumber, nzbprov) + notify_snatch(nzbname, sent_to, helpers.filesafe(modcomicname), comyear, IssueNumber, nzbprov) prov_count == 0 #break return foundc @@ -1787,7 +1793,7 @@ def searcher(nzbprov, nzbname, comicinfo, link, IssueID, ComicID, tmpprov, direc if '[RSS]' in tmpprov : tmpprov = re.sub('\[RSS\]','', tmpprov).strip() updater.nzblog(IssueID, nzbname, ComicName, SARC=None, IssueArcID=None, id=nzbid, prov=tmpprov) #send out notifications for on snatch after the updater incase notification fails (it would bugger up the updater/pp scripts) - notify_snatch(nzbname, sent_to, modcomicname, comyear, IssueNumber, nzbprov) + notify_snatch(nzbname, sent_to, helpers.filesafe(modcomicname), comyear, IssueNumber, nzbprov) return def notify_snatch(nzbname, sent_to, modcomicname, comyear, IssueNumber, nzbprov): diff --git a/mylar/webserve.py b/mylar/webserve.py index 6a8bda00..e3f0c443 100755 --- a/mylar/webserve.py +++ b/mylar/webserve.py @@ -1232,8 +1232,14 @@ class WebInterface(object): raise cherrypy.HTTPRedirect("pullist") #return elif mode == 'want' or mode == 'want_ann' or manualsearch: - cdname = myDB.selectone("SELECT ComicName, ComicName_Filesafe from comics where ComicID=?", [ComicID]).fetchone() - ComicName = cdname['ComicName_Filesafe'] + cdname = myDB.selectone("SELECT * from comics where ComicID=?", [ComicID]).fetchone() + ComicName_Filesafe = cdname['ComicName_Filesafe'] + SeriesYear = cdname['ComicYear'] + AlternateSearch = cdname['AlternateSearch'] + Publisher = cdname['ComicPublisher'] + UseAFuzzy = cdname['UseFuzzy'] + ComicVersion = cdname['ComicVersion'] + ComicName = cdname['ComicName'] controlValueDict = {"IssueID": IssueID} newStatus = {"Status": "Wanted"} if mode == 'want': @@ -1273,13 +1279,13 @@ class WebInterface(object): storedate = issues['IssueDate'] else: storedate = issues['ReleaseDate'] - miy = myDB.selectone("SELECT * FROM comics WHERE ComicID=?", [ComicID]).fetchone() - SeriesYear = miy['ComicYear'] - AlternateSearch = miy['AlternateSearch'] - Publisher = miy['ComicPublisher'] - UseAFuzzy = miy['UseFuzzy'] - ComicVersion = miy['ComicVersion'] - foundcom, prov = search.search_init(ComicName, ComicIssue, ComicYear, SeriesYear, Publisher, issues['IssueDate'], storedate, IssueID, AlternateSearch, UseAFuzzy, ComicVersion, mode=mode, ComicID=ComicID, manualsearch=manualsearch, filesafe=miy['ComicName_Filesafe']) + #miy = myDB.selectone("SELECT * FROM comics WHERE ComicID=?", [ComicID]).fetchone() + #SeriesYear = miy['ComicYear'] + #AlternateSearch = miy['AlternateSearch'] + #Publisher = miy['ComicPublisher'] + #UseAFuzzy = miy['UseFuzzy'] + #ComicVersion = miy['ComicVersion'] + foundcom, prov = search.search_init(ComicName, ComicIssue, ComicYear, SeriesYear, Publisher, issues['IssueDate'], storedate, IssueID, AlternateSearch, UseAFuzzy, ComicVersion, mode=mode, ComicID=ComicID, manualsearch=manualsearch, filesafe=ComicName_Filesafe) if foundcom == "yes": # file check to see if issue exists and update 'have' count if IssueID is not None: @@ -3120,9 +3126,13 @@ class WebInterface(object): "http_user" : mylar.HTTP_USERNAME, "http_port" : mylar.HTTP_PORT, "http_pass" : mylar.HTTP_PASSWORD, + "enable_https" : helpers.checked(mylar.ENABLE_HTTPS), + "https_cert" : mylar.HTTPS_CERT, + "https_key" : mylar.HTTPS_KEY, "api_enabled" : helpers.checked(mylar.API_ENABLED), "api_key" : mylar.API_KEY, "launch_browser" : helpers.checked(mylar.LAUNCH_BROWSER), + "auto_update" : helpers.checked(mylar.AUTO_UPDATE), "logverbose" : helpers.checked(mylar.LOGVERBOSE), "max_logsize" : mylar.MAX_LOGSIZE, "annuals_on" : helpers.checked(mylar.ANNUALS_ON), @@ -3438,7 +3448,7 @@ class WebInterface(object): readOptions.exposed = True - def configUpdate(self, comicvine_api=None, http_host='0.0.0.0', http_username=None, http_port=8090, http_password=None, api_enabled=0, api_key=None, launch_browser=0, logverbose=0, annuals_on=0, max_logsize=None, download_scan_interval=None, nzb_search_interval=None, nzb_startup_search=0, libraryscan_interval=None, + def configUpdate(self, comicvine_api=None, http_host='0.0.0.0', http_username=None, http_port=8090, http_password=None, enable_https=0, https_cert=None, https_key=None, api_enabled=0, api_key=None, launch_browser=0, auto_update=0, logverbose=0, annuals_on=0, max_logsize=None, download_scan_interval=None, nzb_search_interval=None, nzb_startup_search=0, libraryscan_interval=None, nzb_downloader=0, sab_host=None, sab_username=None, sab_apikey=None, sab_password=None, sab_category=None, sab_priority=None, sab_directory=None, log_dir=None, log_level=0, blackhole_dir=None, nzbget_host=None, nzbget_port=None, nzbget_username=None, nzbget_password=None, nzbget_category=None, nzbget_priority=None, nzbget_directory=None, usenet_retention=None, nzbsu=0, nzbsu_uid=None, nzbsu_apikey=None, dognzb=0, dognzb_uid=None, dognzb_apikey=None, newznab=0, newznab_host=None, newznab_name=None, newznab_apikey=None, newznab_uid=None, newznab_enabled=0, @@ -3454,9 +3464,13 @@ class WebInterface(object): mylar.HTTP_PORT = http_port mylar.HTTP_USERNAME = http_username mylar.HTTP_PASSWORD = http_password + mylar.ENABLE_HTTPS = enable_https + mylar.HTTPS_CERT = https_cert + mylar.HTTPS_KEY = https_key mylar.API_ENABLED = api_enabled mylar.API_KEY = api_key mylar.LAUNCH_BROWSER = launch_browser + mylar.AUTO_UPDATE = auto_update mylar.LOGVERBOSE = logverbose mylar.ANNUALS_ON = int(annuals_on) mylar.MAX_LOGSIZE = max_logsize diff --git a/mylar/webstart.py b/mylar/webstart.py index 12119ed5..a86a2316 100755 --- a/mylar/webstart.py +++ b/mylar/webstart.py @@ -19,19 +19,60 @@ import sys import cherrypy import mylar - +from mylar import logger from mylar.webserve import WebInterface +from mylar.helpers import create_https_certificates -def initialize(options={}): +def initialize(options): + # HTTPS stuff stolen from sickbeard + enable_https = options['enable_https'] + https_cert = options['https_cert'] + https_key = options['https_key'] - cherrypy.config.update({ - 'log.screen': False, - 'server.thread_pool': 10, - 'server.socket_port': options['http_port'], - 'server.socket_host': options['http_host'], - 'engine.autoreload_on': False, - }) + if enable_https: + # If either the HTTPS certificate or key do not exist, try to make + # self-signed ones. + if not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key)): + if not create_https_certificates(https_cert, https_key): + logger.warn("Unable to create certificate and key. Disabling " \ + "HTTPS") + enable_https = False + + if not (os.path.exists(https_cert) and os.path.exists(https_key)): + logger.warn("Disabled HTTPS because of missing certificate and " \ + "key.") + enable_https = False + + options_dict = { + 'server.socket_port': options['http_port'], + 'server.socket_host': options['http_host'], + 'server.thread_pool': 10, + 'tools.encode.on': True, + 'tools.encode.encoding': 'utf-8', + 'tools.decode.on': True, + 'log.screen': False, + 'engine.autoreload.on': False, + } + + if enable_https: + options_dict['server.ssl_certificate'] = https_cert + options_dict['server.ssl_private_key'] = https_key + protocol = "https" + else: + protocol = "http" + + logger.info("Starting Mylar on %s://%s:%d/", protocol, + options['http_host'], options['http_port']) + cherrypy.config.update(options_dict) + +# cherrypy.config.update({ +# 'log.screen': False, +# 'server.thread_pool': 10, +# 'server.socket_port': options['http_port'], +# 'server.socket_host': options['http_host'], +# 'engine.autoreload_on': False, +# }) conf = { '/': { @@ -55,7 +96,7 @@ def initialize(options={}): }, '/favicon.ico':{ 'tools.staticfile.on': True, - 'tools.staticfile.filename': "images/favicon.ico" + 'tools.staticfile.filename': os.path.join(os.path.abspath(os.curdir), 'images' + os.sep + 'favicon.ico') }, '/cache':{ 'tools.staticdir.on': True, @@ -70,7 +111,7 @@ def initialize(options={}): 'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict( {options['http_username']:options['http_password']}) }) - + conf['/api'] = {'tools.auth_basic.on': False} # Prevent time-outs cherrypy.engine.timeout_monitor.unsubscribe()