mirror of
https://github.com/rembo10/headphones.git
synced 2026-03-28 07:39:26 +00:00
Merge branch 'ssl-support' into develop
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -15,6 +15,11 @@
|
||||
logs/*
|
||||
cache/*
|
||||
|
||||
# HTTPS Cert/Key #
|
||||
##################
|
||||
*.crt
|
||||
*.key
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
.DS_Store?
|
||||
|
||||
@@ -142,7 +142,7 @@ def main():
|
||||
# Force the http port if neccessary
|
||||
if args.port:
|
||||
http_port = args.port
|
||||
logger.info('Starting Headphones on forced port: %i' % http_port)
|
||||
logger.info('Using forced port: %i' % http_port)
|
||||
else:
|
||||
http_port = int(headphones.HTTP_PORT)
|
||||
|
||||
@@ -152,12 +152,13 @@ def main():
|
||||
'http_host': headphones.HTTP_HOST,
|
||||
'http_root': headphones.HTTP_ROOT,
|
||||
'http_proxy': headphones.HTTP_PROXY,
|
||||
'enable_https': headphones.ENABLE_HTTPS,
|
||||
'https_cert': headphones.HTTPS_CERT,
|
||||
'https_key': headphones.HTTPS_KEY,
|
||||
'http_username': headphones.HTTP_USERNAME,
|
||||
'http_password': headphones.HTTP_PASSWORD,
|
||||
})
|
||||
|
||||
logger.info('Starting Headphones on port: %i' % http_port)
|
||||
|
||||
if headphones.LAUNCH_BROWSER and not args.nolaunch:
|
||||
headphones.launch_browser(headphones.HTTP_HOST, http_port, headphones.HTTP_ROOT)
|
||||
|
||||
|
||||
@@ -54,6 +54,19 @@
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="launch_browser" value="1" ${config['launch_browser']} /> <label>Launch Browser on Startup</label>
|
||||
</div>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="enable_https" id="enable_https" value="1" ${config['enable_https']} /> <label>Enable HTTPS</label>
|
||||
</div>
|
||||
<div id="https_options">
|
||||
<div class="row">
|
||||
<label>HTTPS Cert</label>
|
||||
<input type="text" name="https_cert" value="${config['https_cert']}" size="30">
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>HTTPS Key</label>
|
||||
<input type="text" name="https_key" value="${config['https_key']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</td>
|
||||
<td>
|
||||
@@ -945,6 +958,25 @@
|
||||
$('#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();
|
||||
}
|
||||
});
|
||||
if ($("#music_encoder").is(":checked"))
|
||||
{
|
||||
$("#encoderoptions").show();
|
||||
@@ -1214,6 +1246,7 @@
|
||||
initConfigCheckbox("#userutracker");
|
||||
initConfigCheckbox("#usewhatcd");
|
||||
initConfigCheckbox("#useapi");
|
||||
initConfigCheckbox("#enable_https");
|
||||
}
|
||||
$(document).ready(function() {
|
||||
initThisPage();
|
||||
|
||||
@@ -73,6 +73,10 @@ HTTP_ROOT = None
|
||||
HTTP_PROXY = False
|
||||
LAUNCH_BROWSER = False
|
||||
|
||||
ENABLE_HTTPS = False
|
||||
HTTPS_CERT = None
|
||||
HTTPS_KEY = None
|
||||
|
||||
API_ENABLED = False
|
||||
API_KEY = None
|
||||
|
||||
@@ -304,7 +308,7 @@ def initialize():
|
||||
PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, PUSHOVER_ENABLED, PUSHOVER_PRIORITY, PUSHOVER_KEYS, PUSHOVER_ONSNATCH, MIRRORLIST, \
|
||||
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
|
||||
XBMC_NOTIFY, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
||||
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, CACHE_SIZEMB, JOURNAL_MODE, UMASK
|
||||
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY
|
||||
|
||||
if __INITIALIZED__:
|
||||
return False
|
||||
@@ -345,6 +349,9 @@ def initialize():
|
||||
HTTP_PASSWORD = check_setting_str(CFG, 'General', 'http_password', '')
|
||||
HTTP_ROOT = check_setting_str(CFG, 'General', 'http_root', '/')
|
||||
HTTP_PROXY = bool(check_setting_int(CFG, 'General', 'http_proxy', 0))
|
||||
ENABLE_HTTPS = bool(check_setting_int(CFG, 'General', 'enable_https', 0))
|
||||
HTTPS_CERT = check_setting_str(CFG, 'General', 'https_cert', os.path.join(DATA_DIR, 'server.crt'))
|
||||
HTTPS_KEY = check_setting_str(CFG, 'General', 'https_key', os.path.join(DATA_DIR, 'server.key'))
|
||||
LAUNCH_BROWSER = bool(check_setting_int(CFG, 'General', 'launch_browser', 1))
|
||||
API_ENABLED = bool(check_setting_int(CFG, 'General', 'api_enabled', 0))
|
||||
API_KEY = check_setting_str(CFG, 'General', 'api_key', '')
|
||||
@@ -679,9 +686,14 @@ def launch_browser(host, port, root):
|
||||
|
||||
if host == '0.0.0.0':
|
||||
host = 'localhost'
|
||||
|
||||
if ENABLE_HTTPS:
|
||||
protocol = 'https'
|
||||
else:
|
||||
protocol = 'http'
|
||||
|
||||
try:
|
||||
webbrowser.open('http://%s:%i%s' % (host, port, root))
|
||||
webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root))
|
||||
except Exception, e:
|
||||
logger.error('Could not launch browser: %s' % e)
|
||||
|
||||
@@ -698,6 +710,9 @@ def config_write():
|
||||
new_config['General']['http_password'] = HTTP_PASSWORD
|
||||
new_config['General']['http_root'] = HTTP_ROOT
|
||||
new_config['General']['http_proxy'] = int(HTTP_PROXY)
|
||||
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']['launch_browser'] = int(LAUNCH_BROWSER)
|
||||
new_config['General']['api_enabled'] = int(API_ENABLED)
|
||||
new_config['General']['api_key'] = API_KEY
|
||||
|
||||
@@ -345,3 +345,37 @@ def split_string(mystring):
|
||||
for each_word in mystring.split(','):
|
||||
mylist.append(each_word.strip())
|
||||
return mylist
|
||||
|
||||
def create_https_certificates(ssl_cert, ssl_key):
|
||||
"""
|
||||
Stolen from SickBeard (http://github.com/midgetspy/Sick-Beard):
|
||||
Create self-signed HTTPS certificares and store in paths 'ssl_cert' and 'ssl_key'
|
||||
"""
|
||||
from headphones import logger
|
||||
|
||||
try:
|
||||
from OpenSSL import crypto #@UnresolvedImport
|
||||
from lib.certgen import createKeyPair, createCertRequest, createCertificate, TYPE_RSA, serial #@UnresolvedImport
|
||||
except:
|
||||
logger.warn(u"pyopenssl module missing, please install for https access")
|
||||
return False
|
||||
|
||||
# Create the CA Certificate
|
||||
cakey = createKeyPair(TYPE_RSA, 1024)
|
||||
careq = createCertRequest(cakey, CN='Certificate Authority')
|
||||
cacert = createCertificate(careq, (careq, cakey), serial, (0, 60*60*24*365*10)) # ten years
|
||||
|
||||
cname = 'Headphones'
|
||||
pkey = createKeyPair(TYPE_RSA, 1024)
|
||||
req = createCertRequest(pkey, CN=cname)
|
||||
cert = createCertificate(req, (cacert, cakey), serial, (0, 60*60*24*365*10)) # ten years
|
||||
|
||||
# Save the key and certificate to disk
|
||||
try:
|
||||
open(ssl_key, 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
open(ssl_cert, 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
except:
|
||||
logger.error(u"Error creating SSL key and certificate")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -565,6 +565,9 @@ class WebInterface(object):
|
||||
"http_port" : headphones.HTTP_PORT,
|
||||
"http_pass" : headphones.HTTP_PASSWORD,
|
||||
"launch_browser" : checked(headphones.LAUNCH_BROWSER),
|
||||
"enable_https" : checked(headphones.ENABLE_HTTPS),
|
||||
"https_cert" : headphones.HTTPS_CERT,
|
||||
"https_key" : headphones.HTTPS_KEY,
|
||||
"api_enabled" : checked(headphones.API_ENABLED),
|
||||
"api_key" : headphones.API_KEY,
|
||||
"download_scan_interval" : headphones.DOWNLOAD_SCAN_INTERVAL,
|
||||
@@ -726,13 +729,17 @@ class WebInterface(object):
|
||||
delete_lossless_files=0, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None,
|
||||
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, synoindex_enabled=False,
|
||||
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, mirror=None, customhost=None, customport=None,
|
||||
customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, **kwargs):
|
||||
customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None,
|
||||
enable_https=0, https_cert=None, https_key=None, **kwargs):
|
||||
|
||||
headphones.HTTP_HOST = http_host
|
||||
headphones.HTTP_PORT = http_port
|
||||
headphones.HTTP_USERNAME = http_username
|
||||
headphones.HTTP_PASSWORD = http_password
|
||||
headphones.LAUNCH_BROWSER = launch_browser
|
||||
headphones.ENABLE_HTTPS = enable_https
|
||||
headphones.HTTPS_CERT = https_cert
|
||||
headphones.HTTPS_KEY = https_key
|
||||
headphones.API_ENABLED = api_enabled
|
||||
headphones.API_KEY = api_key
|
||||
headphones.DOWNLOAD_SCAN_INTERVAL = download_scan_interval
|
||||
|
||||
@@ -20,12 +20,31 @@ import cherrypy
|
||||
|
||||
import headphones
|
||||
|
||||
from headphones import logger
|
||||
from headphones.webserve import WebInterface
|
||||
from headphones.helpers import create_https_certificates
|
||||
|
||||
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({
|
||||
if enable_https:
|
||||
# If either the HTTPS certificate or key do not exist, make some 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(u"Unable to create cert/key files, disabling HTTPS")
|
||||
headphones.ENABLE_HTTPS = False
|
||||
enable_https = False
|
||||
|
||||
if not (os.path.exists(https_cert) and os.path.exists(https_key)):
|
||||
logger.warn(u"Disabled HTTPS because of missing CERT and KEY files")
|
||||
headphones.ENABLE_HTTPS = False
|
||||
enable_https = False
|
||||
|
||||
options_dict = {
|
||||
'log.screen': False,
|
||||
'server.thread_pool': 10,
|
||||
'server.socket_port': options['http_port'],
|
||||
@@ -34,7 +53,17 @@ def initialize(options={}):
|
||||
'tools.encode.on' : True,
|
||||
'tools.encode.encoding' : 'utf-8',
|
||||
'tools.decode.on' : True,
|
||||
})
|
||||
}
|
||||
|
||||
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(u"Starting Headphones on " + protocol + "://" + str(options['http_host']) + ":" + str(options['http_port']) + "/")
|
||||
cherrypy.config.update(options_dict)
|
||||
|
||||
conf = {
|
||||
'/': {
|
||||
|
||||
82
lib/certgen.py
Normal file
82
lib/certgen.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user