diff --git a/src/NzbDrone.Api/Config/HostConfigModule.cs b/src/NzbDrone.Api/Config/HostConfigModule.cs
index d8dce397f..ee523087c 100644
--- a/src/NzbDrone.Api/Config/HostConfigModule.cs
+++ b/src/NzbDrone.Api/Config/HostConfigModule.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Linq;
+using System.Linq;
using System.Reflection;
using FluentValidation;
using NzbDrone.Common.EnvironmentInfo;
@@ -24,10 +23,8 @@ public HostConfigModule(IConfigFileProvider configFileProvider)
GetResourceById = GetHostConfig;
UpdateResource = SaveHostConfig;
- SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
+ SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
SharedValidator.RuleFor(c => c.Port).ValidPort();
- SharedValidator.RuleFor(c => c.BindAddress).ValidIp4Address().When(c => c.BindAddress != "*");
- SharedValidator.RuleFor(c => c.Port).InclusiveBetween(1, 65535);
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationEnabled);
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationEnabled);
@@ -36,6 +33,11 @@ public HostConfigModule(IConfigFileProvider configFileProvider)
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script);
+
+ SharedValidator.RuleFor(c => c.BindAddress)
+ .ValidIp4Address()
+ .NotListenAllIp4Address()
+ .When(c => c.BindAddress != "*");
}
private HostConfigResource GetHostConfig()
@@ -61,4 +63,4 @@ private void SaveHostConfig(HostConfigResource resource)
_configFileProvider.SaveConfigDictionary(dictionary);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 0c00d49d3..14ec9ed73 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -841,6 +841,7 @@
+
diff --git a/src/NzbDrone.Core/Validation/IpValidation.cs b/src/NzbDrone.Core/Validation/IpValidation.cs
new file mode 100644
index 000000000..b0a674b39
--- /dev/null
+++ b/src/NzbDrone.Core/Validation/IpValidation.cs
@@ -0,0 +1,35 @@
+using System.Net;
+using System.Net.Sockets;
+using FluentValidation;
+using FluentValidation.Validators;
+
+namespace NzbDrone.Core.Validation
+{
+ public static class IpValidation
+ {
+ public static IRuleBuilderOptions ValidIp4Address(this IRuleBuilder ruleBuilder)
+ {
+ return ruleBuilder.Must(x =>
+ {
+ IPAddress parsedAddress;
+
+ if (!IPAddress.TryParse(x, out parsedAddress))
+ {
+ return false;
+ }
+
+ if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
+ {
+ return false;
+ }
+
+ return parsedAddress.AddressFamily == AddressFamily.InterNetwork;
+ }).WithMessage("Must be a valid IPv4 Address");
+ }
+
+ public static IRuleBuilderOptions NotListenAllIp4Address(this IRuleBuilder ruleBuilder)
+ {
+ return ruleBuilder.SetValidator(new RegularExpressionValidator(@"^(?!0\.0\.0\.0)")).WithMessage("Use * instead of 0.0.0.0");
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs
index 3751ee2b7..b0fb71377 100644
--- a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs
+++ b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs
@@ -1,6 +1,4 @@
-using System.Net;
-using System.Net.Sockets;
-using System.Text.RegularExpressions;
+using System.Text.RegularExpressions;
using FluentValidation;
using FluentValidation.Validators;
using NzbDrone.Core.Parser;
@@ -35,21 +33,6 @@ public static IRuleBuilderOptions ValidPort(this IRuleBuilder
return ruleBuilder.SetValidator(new InclusiveBetweenValidator(1, 65535));
}
- public static IRuleBuilderOptions ValidIp4Address(this IRuleBuilder ruleBuilder)
- {
-
- return ruleBuilder.Must(x =>
- {
- IPAddress parsedAddress;
- if (!IPAddress.TryParse(x, out parsedAddress))
- {
- return false;
- }
-
- return parsedAddress.AddressFamily == AddressFamily.InterNetwork;
- });
- }
-
public static IRuleBuilderOptions ValidLanguage(this IRuleBuilder ruleBuilder)
{
return ruleBuilder.SetValidator(new LangaugeValidator());
diff --git a/src/NzbDrone.Host/AccessControl/UrlAcl.cs b/src/NzbDrone.Host/AccessControl/UrlAcl.cs
new file mode 100644
index 000000000..c53ba857b
--- /dev/null
+++ b/src/NzbDrone.Host/AccessControl/UrlAcl.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace NzbDrone.Host.AccessControl
+{
+ public class UrlAcl
+ {
+ public string Scheme { get; set; }
+ public string Address { get; set; }
+ public int Port { get; set; }
+ public string UrlBase { get; set; }
+
+ public string Url
+ {
+ get
+ {
+ return String.Format("{0}://{1}:{2}/{3}", Scheme, Address, Port, UrlBase);
+ }
+ }
+ }
+}
diff --git a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs b/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs
index 3b4bd5437..34ff4619e 100644
--- a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs
+++ b/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs
@@ -1,15 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Text.RegularExpressions;
using NLog;
+using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Host.AccessControl
{
public interface IUrlAclAdapter
{
- void ConfigureUrl();
+ void ConfigureUrls();
List Urls { get; }
}
@@ -20,7 +23,18 @@ public class UrlAclAdapter : IUrlAclAdapter
private readonly IRuntimeInfo _runtimeInfo;
private readonly Logger _logger;
- public List Urls { get; private set; }
+ public List Urls
+ {
+ get
+ {
+ return InternalUrls.Select(c => c.Url).ToList();
+ }
+ }
+
+ private List InternalUrls { get; set; }
+ private List RegisteredUrls { get; set; }
+
+ private static readonly Regex UrlAclRegex = new Regex(@"(?https?)\:\/\/(?.+?)\:(?\d+)/(?.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public UrlAclAdapter(INetshProvider netshProvider,
IConfigFileProvider configFileProvider,
@@ -32,20 +46,21 @@ public UrlAclAdapter(INetshProvider netshProvider,
_runtimeInfo = runtimeInfo;
_logger = logger;
- Urls = new List();
+ InternalUrls = new List();
+ RegisteredUrls = GetRegisteredUrls();
}
- public void ConfigureUrl()
+ public void ConfigureUrls()
{
- var localHostHttpUrls = BuildUrls("http", "localhost", _configFileProvider.Port);
- var interfaceHttpUrls = BuildUrls("http", _configFileProvider.BindAddress, _configFileProvider.Port);
+ var localHostHttpUrls = BuildUrlAcls("http", "localhost", _configFileProvider.Port);
+ var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, _configFileProvider.Port);
- var localHostHttpsUrls = BuildUrls("https", "localhost", _configFileProvider.SslPort);
- var interfaceHttpsUrls = BuildUrls("https", _configFileProvider.BindAddress, _configFileProvider.SslPort);
+ var localHostHttpsUrls = BuildUrlAcls("https", "localhost", _configFileProvider.SslPort);
+ var interfaceHttpsUrls = BuildUrlAcls("https", _configFileProvider.BindAddress, _configFileProvider.SslPort);
if (!_configFileProvider.EnableSsl)
{
- Urls.Clear();
+ localHostHttpsUrls.Clear();
interfaceHttpsUrls.Clear();
}
@@ -54,13 +69,33 @@ public void ConfigureUrl()
var httpUrls = interfaceHttpUrls.All(IsRegistered) ? interfaceHttpUrls : localHostHttpUrls;
var httpsUrls = interfaceHttpsUrls.All(IsRegistered) ? interfaceHttpsUrls : localHostHttpsUrls;
- Urls.AddRange(httpUrls);
- Urls.AddRange(httpsUrls);
+ InternalUrls.AddRange(httpUrls);
+ InternalUrls.AddRange(httpsUrls);
+
+ if (_configFileProvider.BindAddress != "*")
+ {
+ if (httpUrls.None(c => c.Address.Equals("localhost")))
+ {
+ InternalUrls.AddRange(localHostHttpUrls);
+ }
+
+ if (httpsUrls.None(c => c.Address.Equals("localhost")))
+ {
+ InternalUrls.AddRange(localHostHttpsUrls);
+ }
+ }
}
else
{
- Urls.AddRange(interfaceHttpUrls);
- Urls.AddRange(interfaceHttpsUrls);
+ InternalUrls.AddRange(interfaceHttpUrls);
+ InternalUrls.AddRange(interfaceHttpsUrls);
+
+ //Register localhost URLs so the IP Address doesn't need to be used from the local system
+ if (_configFileProvider.BindAddress != "*")
+ {
+ InternalUrls.AddRange(localHostHttpUrls);
+ InternalUrls.AddRange(localHostHttpsUrls);
+ }
if (OsInfo.IsWindows)
{
@@ -71,49 +106,104 @@ public void ConfigureUrl()
private void RefreshRegistration()
{
- if (OsInfo.Version.Major < 6)
- return;
+ if (OsInfo.Version.Major < 6) return;
- Urls.ForEach(RegisterUrl);
+ foreach (var urlAcl in InternalUrls)
+ {
+ if (IsRegistered(urlAcl) || urlAcl.Address.Equals("localhost")) continue;
+
+ RemoveSimilar(urlAcl);
+ RegisterUrl(urlAcl);
+ }
}
- private bool IsRegistered(string urlAcl)
+ private bool IsRegistered(UrlAcl urlAcl)
{
- 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(urlAcl));
+ return RegisteredUrls.Any(c => c.Scheme == urlAcl.Scheme &&
+ c.Address == urlAcl.Address &&
+ c.Port == urlAcl.Port &&
+ c.UrlBase == urlAcl.UrlBase);
}
- private void RegisterUrl(string urlAcl)
+ private List GetRegisteredUrls()
{
- var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl);
+ var arguments = String.Format("http show urlacl");
+ var output = _netshProvider.Run(arguments);
+
+ if (output == null || !output.Standard.Any()) return new List();
+
+ return output.Standard.Select(line =>
+ {
+ var match = UrlAclRegex.Match(line);
+
+ if (match.Success)
+ {
+ return new UrlAcl
+ {
+ Scheme = match.Groups["scheme"].Value,
+ Address = match.Groups["address"].Value,
+ Port = Convert.ToInt32(match.Groups["port"].Value),
+ UrlBase = match.Groups["urlbase"].Value.Trim()
+ };
+ }
+
+ return null;
+
+ }).Where(r => r != null).ToList();
+ }
+
+ private void RegisterUrl(UrlAcl urlAcl)
+ {
+ var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl.Url);
_netshProvider.Run(arguments);
}
- private string BuildUrl(string protocol, string url, int port, string urlBase)
+ private void RemoveSimilar(UrlAcl urlAcl)
{
- var result = protocol + "://" + url + ":" + port;
- result += String.IsNullOrEmpty(urlBase) ? "/" : urlBase + "/";
+ var similar = RegisteredUrls.Where(c => c.Scheme == urlAcl.Scheme &&
+ InternalUrls.None(x => x.Address == c.Address) &&
+ c.Port == urlAcl.Port &&
+ c.UrlBase == urlAcl.UrlBase);
- return result;
+ foreach (var s in similar)
+ {
+ UnregisterUrl(s);
+ }
}
- private List BuildUrls(string protocol, string url, int port)
+ private void UnregisterUrl(UrlAcl urlAcl)
{
- var urls = new List();
+ _logger.Trace("Removing URL ACL {0}", urlAcl.Url);
+
+ var arguments = String.Format("http delete urlacl {0}", urlAcl.Url);
+ _netshProvider.Run(arguments);
+ }
+
+ private List BuildUrlAcls(string scheme, string address, int port)
+ {
+ var urlAcls = new List();
var urlBase = _configFileProvider.UrlBase;
- if (!String.IsNullOrEmpty(urlBase))
+ if (urlBase.IsNotNullOrWhiteSpace())
{
- urls.Add(BuildUrl(protocol, url, port, urlBase));
+ urlAcls.Add(new UrlAcl
+ {
+ Scheme = scheme,
+ Address = address,
+ Port = port,
+ UrlBase = urlBase.Trim('/') + "/"
+ });
}
- urls.Add(BuildUrl(protocol, url, port, ""));
+ urlAcls.Add(new UrlAcl
+ {
+ Scheme = scheme,
+ Address = address,
+ Port = port,
+ UrlBase = String.Empty
+ });
- return urls;
+ return urlAcls;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Host/NzbDrone.Host.csproj b/src/NzbDrone.Host/NzbDrone.Host.csproj
index a8074931b..0a0cccb44 100644
--- a/src/NzbDrone.Host/NzbDrone.Host.csproj
+++ b/src/NzbDrone.Host/NzbDrone.Host.csproj
@@ -101,6 +101,7 @@
+
diff --git a/src/NzbDrone.Host/Owin/OwinHostController.cs b/src/NzbDrone.Host/Owin/OwinHostController.cs
index 6db1a2477..09efd0b24 100644
--- a/src/NzbDrone.Host/Owin/OwinHostController.cs
+++ b/src/NzbDrone.Host/Owin/OwinHostController.cs
@@ -45,7 +45,7 @@ public void StartServer()
}
}
- _urlAclAdapter.ConfigureUrl();
+ _urlAclAdapter.ConfigureUrls();
_logger.Info("Listening on the following URLs:");
foreach (var url in _urlAclAdapter.Urls)
diff --git a/src/UI/Settings/General/GeneralViewTemplate.hbs b/src/UI/Settings/General/GeneralViewTemplate.hbs
index c5ed1cadf..99067c1cd 100644
--- a/src/UI/Settings/General/GeneralViewTemplate.hbs
+++ b/src/UI/Settings/General/GeneralViewTemplate.hbs
@@ -7,6 +7,7 @@
+