You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Lidarr/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs

238 lines
7.8 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Host.AccessControl
{
public interface IUrlAclAdapter
{
void ConfigureUrls();
List<string> Urls { get; }
}
public class UrlAclAdapter : IUrlAclAdapter
{
private readonly INetshProvider _netshProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IOsInfo _osInfo;
private readonly Logger _logger;
public List<string> Urls
{
get
{
return InternalUrls.Select(c => c.Url).ToList();
}
}
private List<UrlAcl> InternalUrls { get; }
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,
IRuntimeInfo runtimeInfo,
IOsInfo osInfo,
Logger logger)
{
_netshProvider = netshProvider;
_configFileProvider = configFileProvider;
_runtimeInfo = runtimeInfo;
_osInfo = osInfo;
_logger = logger;
InternalUrls = new List<UrlAcl>();
RegisteredUrls = new List<UrlAcl>();
}
public void ConfigureUrls()
{
var enableSsl = _configFileProvider.EnableSsl;
var port = _configFileProvider.Port;
var sslPort = _configFileProvider.SslPort;
if (enableSsl && sslPort == port)
{
throw new LidarrStartupException("Cannot use the same port for HTTP and HTTPS. Port {0}", port);
}
if (RegisteredUrls.Empty())
{
GetRegisteredUrls();
}
var localHostHttpUrls = BuildUrlAcls("http", "localhost", port);
var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, port);
var localHostHttpsUrls = BuildUrlAcls("https", "localhost", sslPort);
var interfaceHttpsUrls = BuildUrlAcls("https", _configFileProvider.BindAddress, sslPort);
if (!enableSsl)
{
localHostHttpsUrls.Clear();
interfaceHttpsUrls.Clear();
}
if (OsInfo.IsWindows && !_runtimeInfo.IsAdmin)
{
var httpUrls = interfaceHttpUrls.All(IsRegistered) ? interfaceHttpUrls : localHostHttpUrls;
var httpsUrls = interfaceHttpsUrls.All(IsRegistered) ? interfaceHttpsUrls : localHostHttpsUrls;
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
{
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)
{
RefreshRegistration();
}
}
}
private void RefreshRegistration()
{
var osVersion = new Version(_osInfo.Version);
if (osVersion.Major < 6) return;
foreach (var urlAcl in InternalUrls)
{
if (IsRegistered(urlAcl) || urlAcl.Address.Equals("localhost")) continue;
RemoveSimilar(urlAcl);
RegisterUrl(urlAcl);
}
}
private bool IsRegistered(UrlAcl urlAcl)
{
return RegisteredUrls.Any(c => c.Scheme == urlAcl.Scheme &&
c.Address == urlAcl.Address &&
c.Port == urlAcl.Port &&
c.UrlBase == urlAcl.UrlBase);
}
private void GetRegisteredUrls()
{
if (OsInfo.IsNotWindows)
{
return;
}
if (RegisteredUrls.Any())
{
return;
}
var arguments = string.Format("http show urlacl");
var output = _netshProvider.Run(arguments);
if (output == null || !output.Standard.Any()) return;
RegisteredUrls = output.Standard.Select(line =>
{
var match = UrlAclRegex.Match(line.Content);
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 void RemoveSimilar(UrlAcl urlAcl)
{
var similar = RegisteredUrls.Where(c => c.Scheme == urlAcl.Scheme &&
InternalUrls.None(x => x.Address == c.Address) &&
c.Port == urlAcl.Port &&
c.UrlBase == urlAcl.UrlBase);
foreach (var s in similar)
{
UnregisterUrl(s);
}
}
private void UnregisterUrl(UrlAcl urlAcl)
{
_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 (urlBase.IsNotNullOrWhiteSpace())
{
urlAcls.Add(new UrlAcl
{
Scheme = scheme,
Address = address,
Port = port,
UrlBase = urlBase.Trim('/') + "/"
});
}
urlAcls.Add(new UrlAcl
{
Scheme = scheme,
Address = address,
Port = port,
UrlBase = string.Empty
});
return urlAcls;
}
}
}