diff --git a/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/NzbDrone.Core/Configuration/ConfigFileProvider.cs index a25e85b7c..4a1e93518 100644 --- a/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -20,6 +20,8 @@ namespace NzbDrone.Core.Configuration void SaveConfigDictionary(Dictionary configValues); int Port { get; } + int SslPort { get; } + bool EnableSsl { get; } bool LaunchBrowser { get; } bool AuthenticationEnabled { get; } string Username { get; } @@ -27,6 +29,7 @@ namespace NzbDrone.Core.Configuration string LogLevel { get; } string Branch { get; } bool Torrent { get; } + string SslCertHash { get; } } public class ConfigFileProvider : IConfigFileProvider @@ -90,6 +93,16 @@ namespace NzbDrone.Core.Configuration get { return GetValueInt("Port", 8989); } } + public int SslPort + { + get { return GetValueInt("SslPort", 9898); } + } + + public bool EnableSsl + { + get { return GetValueBoolean("EnableSsl", false); } + } + public bool LaunchBrowser { get { return GetValueBoolean("LaunchBrowser", true); } @@ -125,6 +138,11 @@ namespace NzbDrone.Core.Configuration get { return GetValue("LogLevel", "Info"); } } + public string SslCertHash + { + get { return GetValue("SslCertHash", ""); } + } + public int GetValueInt(string key, int defaultValue) { return Convert.ToInt32(GetValue(key, defaultValue)); diff --git a/NzbDrone.Host/AccessControl/NetshProvider.cs b/NzbDrone.Host/AccessControl/NetshProvider.cs new file mode 100644 index 000000000..dc9e8b754 --- /dev/null +++ b/NzbDrone.Host/AccessControl/NetshProvider.cs @@ -0,0 +1,39 @@ +using System; +using NLog; +using NzbDrone.Common.Processes; + +namespace NzbDrone.Host.AccessControl +{ + public interface INetshProvider + { + ProcessOutput Run(string arguments); + } + + public class NetshProvider : INetshProvider + { + private readonly IProcessProvider _processProvider; + private readonly Logger _logger; + + public NetshProvider(IProcessProvider processProvider, Logger logger) + { + _processProvider = processProvider; + _logger = logger; + } + + public ProcessOutput Run(string arguments) + { + try + { + var output = _processProvider.StartAndCapture("netsh.exe", arguments); + + return output; + } + catch (Exception ex) + { + _logger.WarnException("Error executing netsh with arguments: " + arguments, ex); + } + + return null; + } + } +} diff --git a/NzbDrone.Host/AccessControl/SslAdapter.cs b/NzbDrone.Host/AccessControl/SslAdapter.cs new file mode 100644 index 000000000..c94e307a3 --- /dev/null +++ b/NzbDrone.Host/AccessControl/SslAdapter.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using NLog; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Host.AccessControl +{ + public interface ISslAdapter + { + void Register(); + } + + public class SslAdapter : ISslAdapter + { + private const string APP_ID = "C2172AF4-F9A6-4D91-BAEE-C2E4EE680613"; + + private readonly INetshProvider _netshProvider; + private readonly IConfigFileProvider _configFileProvider; + private readonly Logger _logger; + + public SslAdapter(INetshProvider netshProvider, IConfigFileProvider configFileProvider, Logger logger) + { + _netshProvider = netshProvider; + _configFileProvider = configFileProvider; + _logger = logger; + } + + public void Register() + { + if (!_configFileProvider.EnableSsl) return; + if (IsRegistered()) return; + + if (String.IsNullOrWhiteSpace(_configFileProvider.SslCertHash)) + { + _logger.Warn("Unable to enable SSL, SSL Cert Hash is required"); + return; + } + + var arguments = String.Format("netsh http add sslcert ipport=0.0.0.0:{0} certhash={1} appid={{{2}}", _configFileProvider.SslPort, _configFileProvider.SslCertHash, APP_ID); + _netshProvider.Run(arguments); + } + + private bool IsRegistered() + { + var ipPort = "0.0.0.0:" + _configFileProvider.SslPort; + var arguments = String.Format("http show sslcert ipport={0}", ipPort); + + var output = _netshProvider.Run(arguments); + + if (output == null || !output.Standard.Any()) return false; + + return output.Standard.Any(line => line.Contains(ipPort)); + } + } +} diff --git a/NzbDrone.Host/AccessControl/UrlAclAdapter.cs b/NzbDrone.Host/AccessControl/UrlAclAdapter.cs index 0fbbd3611..cca29eb2b 100644 --- a/NzbDrone.Host/AccessControl/UrlAclAdapter.cs +++ b/NzbDrone.Host/AccessControl/UrlAclAdapter.cs @@ -1,9 +1,7 @@ using System; using System.Linq; using NLog; -using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Processes; using NzbDrone.Core.Configuration; namespace NzbDrone.Host.AccessControl @@ -11,43 +9,50 @@ namespace NzbDrone.Host.AccessControl public interface IUrlAclAdapter { void ConfigureUrl(); - string UrlAcl { get; } + string Url { get; } + string HttpsUrl { get; } } public class UrlAclAdapter : IUrlAclAdapter { - private const string URL_ACL = "http://{0}:{1}/"; - - private readonly IProcessProvider _processProvider; + private readonly INetshProvider _netshProvider; private readonly IConfigFileProvider _configFileProvider; private readonly IRuntimeInfo _runtimeInfo; private readonly Logger _logger; - public string UrlAcl { get; private set; } + public string Url { get; private set; } + public string HttpsUrl { get; private set; } + private string _localUrl; private string _wildcardUrl; + private string _localHttpsUrl; + private string _wildcardHttpsUrl; - public UrlAclAdapter(IProcessProvider processProvider, + public UrlAclAdapter(INetshProvider netshProvider, IConfigFileProvider configFileProvider, IRuntimeInfo runtimeInfo, Logger logger) { - _processProvider = processProvider; + _netshProvider = netshProvider; _configFileProvider = configFileProvider; _runtimeInfo = runtimeInfo; _logger = logger; - _localUrl = String.Format(URL_ACL, "localhost", _configFileProvider.Port); - _wildcardUrl = String.Format(URL_ACL, "*", _configFileProvider.Port); + _localUrl = String.Format("http://localhost:{0}/", _configFileProvider.Port); + _wildcardUrl = String.Format("http://*:{0}/", _configFileProvider.Port); + _localHttpsUrl = String.Format("https://localhost:{0}/", _configFileProvider.SslPort); + _wildcardHttpsUrl = String.Format("https://*:{0}/", _configFileProvider.SslPort); - UrlAcl = _wildcardUrl; + Url = _wildcardUrl; + HttpsUrl = _wildcardHttpsUrl; } public void ConfigureUrl() { - if (!_runtimeInfo.IsAdmin && !IsRegistered) + if (!_runtimeInfo.IsAdmin) { - UrlAcl = _localUrl; + if (!IsRegistered(_wildcardUrl)) Url = _localUrl; + if (!IsRegistered(_wildcardHttpsUrl)) HttpsUrl = _localHttpsUrl; } if (_runtimeInfo.IsAdmin) @@ -61,42 +66,24 @@ namespace NzbDrone.Host.AccessControl if (OsInfo.Version.Major < 6) return; - RegisterUrl(); + RegisterUrl(Url); + RegisterUrl(HttpsUrl); } - - private bool IsRegistered + + private bool IsRegistered(string urlAcl) { - get - { - var arguments = String.Format("http show urlacl {0}", _wildcardUrl); - var output = RunNetsh(arguments); + var arguments = String.Format("http show urlacl {0}", urlAcl); + var output = _netshProvider.Run(arguments); - if (output == null || !output.Standard.Any()) return false; - - return output.Standard.Any(line => line.Contains(_wildcardUrl)); - } - } + if (output == null || !output.Standard.Any()) return false; - private void RegisterUrl() - { - var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", UrlAcl); - RunNetsh(arguments); + return output.Standard.Any(line => line.Contains(urlAcl)); } - private ProcessOutput RunNetsh(string arguments) + private void RegisterUrl(string urlAcl) { - try - { - var output = _processProvider.StartAndCapture("netsh.exe", arguments); - - return output; - } - catch (Exception ex) - { - _logger.WarnException("Error executing netsh with arguments: " + arguments, ex); - } - - return null; + var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl); + _netshProvider.Run(arguments); } } } \ No newline at end of file diff --git a/NzbDrone.Host/NzbDrone.Host.csproj b/NzbDrone.Host/NzbDrone.Host.csproj index 71a3643d6..ff8922c0c 100644 --- a/NzbDrone.Host/NzbDrone.Host.csproj +++ b/NzbDrone.Host/NzbDrone.Host.csproj @@ -117,6 +117,8 @@ Properties\SharedAssemblyInfo.cs + + Component diff --git a/NzbDrone.Host/Owin/OwinHostController.cs b/NzbDrone.Host/Owin/OwinHostController.cs index 52d2878e5..5af5a96c5 100644 --- a/NzbDrone.Host/Owin/OwinHostController.cs +++ b/NzbDrone.Host/Owin/OwinHostController.cs @@ -21,17 +21,24 @@ namespace NzbDrone.Host.Owin private readonly IRuntimeInfo _runtimeInfo; private readonly IUrlAclAdapter _urlAclAdapter; private readonly IFirewallAdapter _firewallAdapter; + private readonly ISslAdapter _sslAdapter; private readonly Logger _logger; private IDisposable _host; - public OwinHostController(IConfigFileProvider configFileProvider, IEnumerable owinMiddleWares, - IRuntimeInfo runtimeInfo, IUrlAclAdapter urlAclAdapter, IFirewallAdapter firewallAdapter, Logger logger) + public OwinHostController(IConfigFileProvider configFileProvider, + IEnumerable owinMiddleWares, + IRuntimeInfo runtimeInfo, + IUrlAclAdapter urlAclAdapter, + IFirewallAdapter firewallAdapter, + ISslAdapter sslAdapter, + Logger logger) { _configFileProvider = configFileProvider; _owinMiddleWares = owinMiddleWares; _runtimeInfo = runtimeInfo; _urlAclAdapter = urlAclAdapter; _firewallAdapter = firewallAdapter; + _sslAdapter = sslAdapter; _logger = logger; } @@ -44,17 +51,24 @@ namespace NzbDrone.Host.Owin if (_runtimeInfo.IsAdmin) { _firewallAdapter.MakeAccessible(); + _sslAdapter.Register(); } _urlAclAdapter.ConfigureUrl(); } - var options = new StartOptions(_urlAclAdapter.UrlAcl) + var options = new StartOptions(_urlAclAdapter.Url) { ServerFactory = "Microsoft.Owin.Host.HttpListener" }; - _logger.Info("starting server on {0}", _urlAclAdapter.UrlAcl); + if (_configFileProvider.EnableSsl) + { + _logger.Trace("SSL enabled, listening on: {0}", _urlAclAdapter.HttpsUrl); + options.Urls.Add(_urlAclAdapter.HttpsUrl); + } + + _logger.Info("starting server on {0}", _urlAclAdapter.Url); try {