mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-04 10:02:40 +01:00
commit
3a716a5585
@ -23,9 +23,9 @@ 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.Username).NotEmpty().When(c => c.AuthenticationEnabled);
|
||||
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationEnabled);
|
||||
|
||||
@ -33,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()
|
||||
@ -58,4 +63,4 @@ private void SaveHostConfig(HostConfigResource resource)
|
||||
_configFileProvider.SaveConfigDictionary(dictionary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ namespace NzbDrone.Api.Config
|
||||
{
|
||||
public class HostConfigResource : RestResource
|
||||
{
|
||||
public String BindAddress { get; set; }
|
||||
public Int32 Port { get; set; }
|
||||
public Int32 SslPort { get; set; }
|
||||
public Boolean EnableSsl { get; set; }
|
||||
|
@ -23,6 +23,7 @@ public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
|
||||
Dictionary<string, object> GetConfigDictionary();
|
||||
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
||||
|
||||
string BindAddress { get; }
|
||||
int Port { get; }
|
||||
int SslPort { get; }
|
||||
bool EnableSsl { get; }
|
||||
@ -110,6 +111,22 @@ public void SaveConfigDictionary(Dictionary<string, object> configValues)
|
||||
_eventAggregator.PublishEvent(new ConfigFileSavedEvent());
|
||||
}
|
||||
|
||||
public string BindAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
const string defaultValue = "*";
|
||||
|
||||
string bindAddress = GetValue("BindAddress", defaultValue);
|
||||
if (string.IsNullOrWhiteSpace(bindAddress))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return bindAddress;
|
||||
}
|
||||
}
|
||||
|
||||
public int Port
|
||||
{
|
||||
get { return GetValueInt("Port", 8989); }
|
||||
|
@ -841,6 +841,7 @@
|
||||
<Compile Include="Update\UpdateVerification.cs" />
|
||||
<Compile Include="Update\UpdateVerificationFailedException.cs" />
|
||||
<Compile Include="Validation\FolderValidator.cs" />
|
||||
<Compile Include="Validation\IpValidation.cs" />
|
||||
<Compile Include="Validation\LangaugeValidator.cs" />
|
||||
<Compile Include="Validation\NzbDroneValidationFailure.cs" />
|
||||
<Compile Include="Validation\Paths\DroneFactoryValidator.cs" />
|
||||
|
35
src/NzbDrone.Core/Validation/IpValidation.cs
Normal file
35
src/NzbDrone.Core/Validation/IpValidation.cs
Normal file
@ -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<T, string> ValidIp4Address<T>(this IRuleBuilder<T, string> 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<T, string> NotListenAllIp4Address<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
return ruleBuilder.SetValidator(new RegularExpressionValidator(@"^(?!0\.0\.0\.0)")).WithMessage("Use * instead of 0.0.0.0");
|
||||
}
|
||||
}
|
||||
}
|
20
src/NzbDrone.Host/AccessControl/UrlAcl.cs
Normal file
20
src/NzbDrone.Host/AccessControl/UrlAcl.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String> Urls { get; }
|
||||
}
|
||||
|
||||
@ -20,7 +23,18 @@ public class UrlAclAdapter : IUrlAclAdapter
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public List<String> Urls { get; private set; }
|
||||
public List<String> Urls
|
||||
{
|
||||
get
|
||||
{
|
||||
return InternalUrls.Select(c => c.Url).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private List<UrlAcl> InternalUrls { get; set; }
|
||||
private List<UrlAcl> RegisteredUrls { get; set; }
|
||||
|
||||
private static readonly Regex UrlAclRegex = new Regex(@"(?<scheme>https?)\:\/\/(?<address>.+?)\:(?<port>\d+)/(?<urlbase>.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public UrlAclAdapter(INetshProvider netshProvider,
|
||||
IConfigFileProvider configFileProvider,
|
||||
@ -32,35 +46,56 @@ public UrlAclAdapter(INetshProvider netshProvider,
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_logger = logger;
|
||||
|
||||
Urls = new List<String>();
|
||||
InternalUrls = new List<UrlAcl>();
|
||||
RegisteredUrls = GetRegisteredUrls();
|
||||
}
|
||||
|
||||
public void ConfigureUrl()
|
||||
public void ConfigureUrls()
|
||||
{
|
||||
var localHttpUrls = BuildUrls("http", "localhost", _configFileProvider.Port);
|
||||
var wildcardHttpUrls = BuildUrls("http", "*", _configFileProvider.Port);
|
||||
var localHostHttpUrls = BuildUrlAcls("http", "localhost", _configFileProvider.Port);
|
||||
var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, _configFileProvider.Port);
|
||||
|
||||
var localHttpsUrls = BuildUrls("https", "localhost", _configFileProvider.SslPort);
|
||||
var wildcardHttpsUrls = BuildUrls("https", "*", _configFileProvider.SslPort);
|
||||
var localHostHttpsUrls = BuildUrlAcls("https", "localhost", _configFileProvider.SslPort);
|
||||
var interfaceHttpsUrls = BuildUrlAcls("https", _configFileProvider.BindAddress, _configFileProvider.SslPort);
|
||||
|
||||
if (!_configFileProvider.EnableSsl)
|
||||
{
|
||||
localHttpsUrls.Clear();
|
||||
wildcardHttpsUrls.Clear();
|
||||
localHostHttpsUrls.Clear();
|
||||
interfaceHttpsUrls.Clear();
|
||||
}
|
||||
|
||||
if (OsInfo.IsWindows && !_runtimeInfo.IsAdmin)
|
||||
{
|
||||
var httpUrls = wildcardHttpUrls.All(IsRegistered) ? wildcardHttpUrls : localHttpUrls;
|
||||
var httpsUrls = wildcardHttpsUrls.All(IsRegistered) ? wildcardHttpsUrls : localHttpsUrls;
|
||||
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(wildcardHttpUrls);
|
||||
Urls.AddRange(wildcardHttpsUrls);
|
||||
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<UrlAcl> 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<UrlAcl>();
|
||||
|
||||
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<String> BuildUrls(string protocol, string url, int port)
|
||||
private void UnregisterUrl(UrlAcl urlAcl)
|
||||
{
|
||||
var urls = new List<String>();
|
||||
_logger.Trace("Removing URL ACL {0}", urlAcl.Url);
|
||||
|
||||
var arguments = String.Format("http delete urlacl {0}", urlAcl.Url);
|
||||
_netshProvider.Run(arguments);
|
||||
}
|
||||
|
||||
private List<UrlAcl> BuildUrlAcls(string scheme, string address, int port)
|
||||
{
|
||||
var urlAcls = new List<UrlAcl>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +101,7 @@
|
||||
</Compile>
|
||||
<Compile Include="AccessControl\FirewallAdapter.cs" />
|
||||
<Compile Include="AccessControl\NetshProvider.cs" />
|
||||
<Compile Include="AccessControl\UrlAcl.cs" />
|
||||
<Compile Include="AccessControl\SslAdapter.cs" />
|
||||
<Compile Include="AccessControl\UrlAclAdapter.cs" />
|
||||
<Compile Include="ApplicationModes.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)
|
||||
|
@ -2,6 +2,19 @@
|
||||
<fieldset>
|
||||
<legend>Start-Up</legend>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Bind Address</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-4 help-inline">
|
||||
<i class="icon-nd-form-warning" title="Requires restart to take effect" />
|
||||
<i class="icon-nd-form-info" title="Valid IP4 address or '*' for all interfaces"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 col-sm-pull-1">
|
||||
<input type="text" placeholder="*" name="bindAddress" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Port Number</label>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user