diff options
Diffstat (limited to 'src/main.lib/Plugins/TargetPlugins')
19 files changed, 887 insertions, 0 deletions
diff --git a/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBinding.cs b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBinding.cs new file mode 100644 index 0000000..b6125cc --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBinding.cs @@ -0,0 +1,54 @@ +using PKISharp.WACS.Clients; +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Interfaces; +using PKISharp.WACS.Services; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + internal class IISBinding : ITargetPlugin + { + private readonly ILogService _log; + private readonly IIISClient _iisClient; + private IISBindingOptions _options; + private readonly IISBindingHelper _helper; + + public IISBinding(ILogService logService, IIISClient iisClient, IISBindingHelper helper, IISBindingOptions options) + { + _iisClient = iisClient; + _log = logService; + _options = options; + _helper = helper; + } + + public Target Generate() + { + var allBindings = _helper.GetBindings(false); + var matchingBindings = allBindings.Where(x => x.HostUnicode == _options.Host); + if (matchingBindings.Count() == 0) + { + _log.Error("Binding {binding} not yet found in IIS, create it or use the Manual target plugin instead", _options.Host); + return null; + } + else if (!matchingBindings.Any(b => b.SiteId == _options.SiteId)) + { + var newMatch = matchingBindings.First(); + _log.Warning("Binding {binding} moved from site {a} to site {b}", _options.Host, _options.SiteId, newMatch.SiteId); + _options.SiteId = newMatch.SiteId; + } + return new Target() + { + FriendlyName = $"[{nameof(IISBinding)}] {_options.Host}", + CommonName = _options.Host, + Parts = new[] { + new TargetPart { + Identifiers = new List<string> { _options.Host }, + SiteId = _options.SiteId + } + } + }; + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingArguments.cs b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingArguments.cs new file mode 100644 index 0000000..7340fb2 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingArguments.cs @@ -0,0 +1,8 @@ +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + class IISBindingArguments + { + public string SiteId { get; set; } + public string Host { get; set; } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingArgumentsProvider.cs b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingArgumentsProvider.cs new file mode 100644 index 0000000..c93354e --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingArgumentsProvider.cs @@ -0,0 +1,28 @@ +using Fclp; +using PKISharp.WACS.Configuration; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + class IISBindingArgumentsProvider : BaseArgumentsProvider<IISBindingArguments> + { + public override string Name => "IIS Binding plugin"; + public override string Group => "Target"; + public override string Condition => "--target iisbinding"; + + public override bool Active(IISBindingArguments current) + { + return !string.IsNullOrEmpty(current.SiteId) || + !string.IsNullOrEmpty(current.Host); + } + + public override void Configure(FluentCommandLineParser<IISBindingArguments> parser) + { + parser.Setup(o => o.SiteId) + .As("siteid") + .WithDescription("Id of the site where the binding should be found (optional)."); + parser.Setup(o => o.Host) + .As("host") + .WithDescription("Host of the binding to get a certificate for."); + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingOptions.cs b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingOptions.cs new file mode 100644 index 0000000..81b254e --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingOptions.cs @@ -0,0 +1,30 @@ +using PKISharp.WACS.Plugins.Base; +using PKISharp.WACS.Plugins.Base.Options; +using PKISharp.WACS.Services; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + [Plugin("2f5dd428-0f5d-4c8a-8fd0-56fc1b5985ce")] + class IISBindingOptions : TargetPluginOptions<IISBinding> + { + public override string Name => "IISBinding"; + public override string Description => "Single binding of an IIS website"; + + /// <summary> + /// Restrict search to a specific site + /// </summary> + public long SiteId { get; set; } + + /// <summary> + /// Host name of the binding to look for + /// </summary> + public string Host { get; set; } + + public override void Show(IInputService input) + { + base.Show(input); + input.Show("Host", Host, level: 1); + input.Show("SiteId", SiteId.ToString(), level: 1); + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingOptionsFactory.cs b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingOptionsFactory.cs new file mode 100644 index 0000000..f5f359b --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISBinding/IISBindingOptionsFactory.cs @@ -0,0 +1,84 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.Plugins.Base.Factories; +using PKISharp.WACS.Services; +using System.Linq; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + internal class IISBindingOptionsFactory : TargetPluginOptionsFactory<IISBinding, IISBindingOptions> + { + public override bool Hidden => !_iisClient.HasWebSites; + protected IIISClient _iisClient; + protected IISBindingHelper _helper; + private ILogService _log; + private IArgumentsService _arguments; + + public IISBindingOptionsFactory( + ILogService log, IIISClient iisClient, + IISBindingHelper helper, IArgumentsService arguments) + { + _iisClient = iisClient; + _helper = helper; + _log = log; + _arguments = arguments; + } + + public override IISBindingOptions Aquire(IInputService inputService, RunLevel runLevel) + { + var ret = new IISBindingOptions(); + var bindings = _helper.GetBindings(_arguments.MainArguments.HideHttps).Where(x => !x.Hidden); + if (!bindings.Any()) + { + _log.Error($"No sites with named bindings have been configured in IIS. Add one or choose '{ManualOptions.DescriptionText}'."); + return null; + } + var chosenTarget = inputService.ChooseFromList( + "Choose binding", + bindings, + x => Choice.Create(x), + "Abort"); + if (chosenTarget != null) + { + ret.SiteId = chosenTarget.SiteId; + ret.Host = chosenTarget.HostUnicode; + return ret; + } + else + { + return null; + } + } + + public override IISBindingOptions Default() + { + var ret = new IISBindingOptions(); + var args = _arguments.GetArguments<IISBindingArguments>(); + var hostName = _arguments.TryGetRequiredArgument(nameof(args.Host), args.Host).ToLower(); + var rawSiteId = args.SiteId; + var filterSet = _helper.GetBindings(false); + if (!string.IsNullOrEmpty(rawSiteId)) + { + if (long.TryParse(rawSiteId, out long siteId)) + { + filterSet = filterSet.Where(x => x.SiteId == siteId).ToList(); + } + else + { + _log.Error("Invalid SiteId {siteId}", rawSiteId); + return null; + } + } + var chosenTarget = filterSet.Where(x => x.HostUnicode == hostName || x.HostPunycode == hostName).FirstOrDefault(); + if (chosenTarget != null) + { + ret.SiteId = chosenTarget.SiteId; + ret.Host = chosenTarget.HostUnicode; + return ret; + } + else + { + return null; + } + } + } +}
\ No newline at end of file diff --git a/src/main.lib/Plugins/TargetPlugins/IISSite/IISSite.cs b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSite.cs new file mode 100644 index 0000000..26cb8af --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSite.cs @@ -0,0 +1,57 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Interfaces; +using PKISharp.WACS.Services; +using System.Linq; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + internal class IISSite : ITargetPlugin + { + protected ILogService _log; + protected IIISClient _iisClient; + protected IISSiteHelper _helper; + protected IISSiteOptions _options; + + public IISSite(ILogService logService, IIISClient iisClient, IISSiteHelper helper, IISSiteOptions options) + { + _log = logService; + _iisClient = iisClient; + _helper = helper; + _options = options; + } + + public Target Generate() + { + var site = _helper.GetSites(false, false).FirstOrDefault(s => s.Id == _options.SiteId); + if (site == null) + { + _log.Error($"SiteId {_options.SiteId} not found"); + return null; + } + var hosts = site.Hosts; + if (_options.ExcludeBindings != null) + { + hosts = hosts.Except(_options.ExcludeBindings).ToList(); + } + var cn = _options.CommonName; + var cnDefined = !string.IsNullOrWhiteSpace(cn); + var cnValid = cnDefined && hosts.Contains(cn); + if (cnDefined && !cnValid) + { + _log.Warning("Specified common name {cn} not valid", cn); + } + return new Target() + { + FriendlyName = $"[{nameof(IISSite)}] {site.Name}", + CommonName = cnValid ? cn : hosts.FirstOrDefault(), + Parts = new[] { + new TargetPart() { + Identifiers = hosts, + SiteId = site.Id + } + } + }; + } + } +}
\ No newline at end of file diff --git a/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteArguments.cs b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteArguments.cs new file mode 100644 index 0000000..e03ae08 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteArguments.cs @@ -0,0 +1,9 @@ +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + class IISSiteArguments + { + public string SiteId { get; set; } + public string CommonName { get; set; } + public string ExcludeBindings { get; set; } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteArgumentsProvider.cs b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteArgumentsProvider.cs new file mode 100644 index 0000000..0f186c0 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteArgumentsProvider.cs @@ -0,0 +1,31 @@ +using Fclp; +using PKISharp.WACS.Configuration; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + class IISSiteArgumentsProvider : BaseArgumentsProvider<IISSiteArguments> + { + public override string Name => "IIS Site(s) plugin"; + public override string Group => "Target"; + public override string Condition => "--target iissite|iissites"; + public override bool Active(IISSiteArguments current) + { + return !string.IsNullOrEmpty(current.SiteId) || + !string.IsNullOrEmpty(current.CommonName) || + !string.IsNullOrEmpty(current.ExcludeBindings); + } + + public override void Configure(FluentCommandLineParser<IISSiteArguments> parser) + { + parser.Setup(o => o.SiteId) + .As("siteid") + .WithDescription("Identifier of the site that the plugin should create the target from. For iissites this may be a comma separated list."); + parser.Setup(o => o.CommonName) + .As("commonname") + .WithDescription("Specify the common name of the certificate that should be requested for the target. By default this will be the first binding that is enumerated."); + parser.Setup(o => o.ExcludeBindings) + .As("excludebindings") + .WithDescription("Exclude host names from the certificate. This may be a comma separated list."); + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptions.cs b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptions.cs new file mode 100644 index 0000000..1051d08 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptions.cs @@ -0,0 +1,32 @@ +using PKISharp.WACS.Plugins.Base; +using PKISharp.WACS.Plugins.Base.Options; +using PKISharp.WACS.Services; +using System.Collections.Generic; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + [Plugin("d7940b23-f570-460e-ab15-2c822a79009b")] + class IISSiteOptions : TargetPluginOptions<IISSite>, IIISSiteOptions + { + public override string Name => "IISSite"; + public override string Description => "All bindings of an IIS website"; + + public long SiteId { get; set; } + public string CommonName { get; set; } + public List<string> ExcludeBindings { get; set; } + + public override void Show(IInputService input) + { + base.Show(input); + input.Show("SiteId", SiteId.ToString(), level: 1); + if (!string.IsNullOrEmpty(CommonName)) + { + input.Show("CommonName", CommonName, level: 1); + } + if (ExcludeBindings != null && ExcludeBindings.Count > 0) + { + input.Show("ExcludeBindings", string.Join(",", ExcludeBindings), level: 1); + } + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptionsFactory.cs b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptionsFactory.cs new file mode 100644 index 0000000..289b59c --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptionsFactory.cs @@ -0,0 +1,85 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.Extensions; +using PKISharp.WACS.Plugins.Base.Factories; +using PKISharp.WACS.Services; +using System.Linq; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + internal class IISSiteOptionsFactory : TargetPluginOptionsFactory<IISSite, IISSiteOptions> + { + public override bool Hidden => !_iisClient.HasWebSites; + protected IIISClient _iisClient; + protected IISSiteHelper _siteHelper; + protected IISSiteOptionsHelper _optionsHelper; + private readonly ILogService _log; + private readonly IArgumentsService _arguments; + + public IISSiteOptionsFactory( + ILogService log, IIISClient iisClient, + IISSiteHelper helper, IArgumentsService arguments) + { + _iisClient = iisClient; + _siteHelper = helper; + _optionsHelper = new IISSiteOptionsHelper(log); + _log = log; + _arguments = arguments; + } + + public override IISSiteOptions Aquire(IInputService input, RunLevel runLevel) + { + var ret = new IISSiteOptions(); + var sites = _siteHelper. + GetSites(_arguments.MainArguments.HideHttps, true). + Where(x => x.Hidden == false). + Where(x => x.Hosts.Any()); + if (!sites.Any()) + { + _log.Error($"No sites with named bindings have been configured in IIS. Add one or choose '{ManualOptions.DescriptionText}'."); + return null; + } + var chosen = input.ChooseFromList("Choose site", + sites, + x => Choice.Create(x, x.Name), + "Abort"); + if (chosen != null) + { + ret.SiteId = chosen.Id; + if (_optionsHelper.AquireAdvancedOptions(input, chosen.Hosts, runLevel, ret)) + { + return ret; + } + + } + return null; + } + + public override IISSiteOptions Default() + { + var ret = new IISSiteOptions(); + var args = _arguments.GetArguments<IISSiteArguments>(); + var rawSiteId = _arguments.TryGetRequiredArgument(nameof(args.SiteId), args.SiteId); + if (long.TryParse(rawSiteId, out long siteId)) + { + var site = _siteHelper.GetSites(false, false).FirstOrDefault(binding => binding.Id == siteId); + if (site != null) + { + ret.SiteId = site.Id; + if (_optionsHelper.DefaultAdvancedOptions(args, site.Hosts, RunLevel.Unattended, ret)) + { + return ret; + } + } + else + { + _log.Error("Unable to find SiteId {siteId}", siteId); + } + } + else + { + _log.Error("Invalid SiteId {siteId}", args.SiteId); + } + return null; + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptionsHelper.cs b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptionsHelper.cs new file mode 100644 index 0000000..0483514 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSite/IISSiteOptionsHelper.cs @@ -0,0 +1,77 @@ +using PKISharp.WACS.Extensions; +using PKISharp.WACS.Services; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + class IISSiteOptionsHelper + { + private ILogService _log; + + public IISSiteOptionsHelper(ILogService log) + { + _log = log; + } + + public bool AquireAdvancedOptions(IInputService input, IEnumerable<string> chosen, RunLevel runLevel, IIISSiteOptions ret) + { + if (runLevel.HasFlag(RunLevel.Advanced)) + { + // Exclude bindings + input.WritePagedList(chosen.Select(x => Choice.Create(x, ""))); + ret.ExcludeBindings = input.RequestString("Press enter to include all listed hosts, or type a comma-separated lists of exclusions").ParseCsv(); + } + + var remaining = chosen.Except(ret.ExcludeBindings ?? new List<string>()); + if (remaining.Count() == 0) + { + _log.Error("No bindings remain"); + return false; + } + + // Set common name + if (remaining.Count() > 1) + { + ret.CommonName = input.ChooseFromList( + "Select primary domain (common name)", + remaining, + x => Choice.Create(x), + "Default"); + } + return true; + } + + public bool DefaultAdvancedOptions(IISSiteArguments args, IEnumerable<string> chosen, RunLevel runLevel, IIISSiteOptions ret) + { + ret.ExcludeBindings = args.ExcludeBindings.ParseCsv(); + if (ret.ExcludeBindings != null) + { + ret.ExcludeBindings = ret.ExcludeBindings.Select(x => x.ConvertPunycode()).ToList(); + } + var remaining = chosen.Except(ret.ExcludeBindings ?? new List<string>()); + var commonName = args.CommonName; + if (!string.IsNullOrWhiteSpace(commonName)) + { + commonName = commonName.ToLower().Trim().ConvertPunycode(); + if (remaining.Contains(commonName)) + { + ret.CommonName = commonName; + } + else + { + _log.Error("Common name {commonName} not found or excluded", commonName); + return false; + } + } + return true; + } + + } + + public interface IIISSiteOptions + { + List<string> ExcludeBindings { get; set; } + string CommonName { get; set; } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISSites/IISSites.cs b/src/main.lib/Plugins/TargetPlugins/IISSites/IISSites.cs new file mode 100644 index 0000000..32b46d6 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSites/IISSites.cs @@ -0,0 +1,69 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Interfaces; +using PKISharp.WACS.Services; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + internal class IISSites : ITargetPlugin + { + protected ILogService _log; + protected IIISClient _iisClient; + protected IISSiteHelper _helper; + protected IISSitesOptions _options; + + public IISSites(ILogService log, IIISClient iisClient, IISSiteHelper helper, IISSitesOptions options) + { + _log = log; + _iisClient = iisClient; + _helper = helper; + _options = options; + } + + public Target Generate() + { + var sites = _helper.GetSites(false, false); + var filtered = new List<IISSiteHelper.IISSiteOption>(); + if (_options.All == true) + { + filtered = sites; + } + else + { + foreach (var id in _options.SiteIds) + { + var site = sites.FirstOrDefault(s => s.Id == id); + if (site != null) + { + filtered.Add(site); + } + else + { + _log.Warning("SiteId {Id} not found", id); + } + } + } + var allHosts = filtered.SelectMany(x => x.Hosts); + var exclude = _options.ExcludeBindings ?? new List<string>(); + allHosts = allHosts.Except(exclude).ToList(); + var cn = _options.CommonName; + var cnDefined = !string.IsNullOrWhiteSpace(cn); + var cnValid = cnDefined && allHosts.Contains(cn); + if (cnDefined && !cnValid) + { + _log.Warning("Specified common name {cn} not valid", cn); + } + return new Target() + { + FriendlyName = $"[{nameof(IISSites)}] {(_options.All == true ? "All" : string.Join(",", _options.SiteIds))}", + CommonName = cnValid ? cn : allHosts.FirstOrDefault(), + Parts = filtered.Select(site => new TargetPart { + Identifiers = site.Hosts.Except(exclude).ToList(), + SiteId = site.Id + }) + }; + } + } +}
\ No newline at end of file diff --git a/src/main.lib/Plugins/TargetPlugins/IISSites/IISSitesOptions.cs b/src/main.lib/Plugins/TargetPlugins/IISSites/IISSitesOptions.cs new file mode 100644 index 0000000..61abc53 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSites/IISSitesOptions.cs @@ -0,0 +1,40 @@ +using PKISharp.WACS.Plugins.Base; +using PKISharp.WACS.Plugins.Base.Options; +using PKISharp.WACS.Services; +using System.Collections.Generic; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + [Plugin("cdd79a68-4a87-4039-bee8-5a0ebdca41cb")] + class IISSitesOptions : TargetPluginOptions<IISSites>, IIISSiteOptions + { + public override string Name => "IISSites"; + public override string Description => "All bindings of multiple IIS websites"; + + public bool? All { get; set; } + public List<long> SiteIds { get; set; } + public string CommonName { get; set; } + public List<string> ExcludeBindings { get; set; } + + public override void Show(IInputService input) + { + base.Show(input); + if (All != null) + { + input.Show("SiteIds", All == true ? "All" : "None", level: 1); + } + if (SiteIds != null) + { + input.Show("SiteIds", string.Join(",", SiteIds), level: 1); + } + if (!string.IsNullOrEmpty(CommonName)) + { + input.Show("CommonName", CommonName, level: 1); + } + if (ExcludeBindings != null && ExcludeBindings.Count > 0) + { + input.Show("ExcludeBindings", string.Join(",", ExcludeBindings), level: 1); + } + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/IISSites/IISSitesOptionsFactory.cs b/src/main.lib/Plugins/TargetPlugins/IISSites/IISSitesOptionsFactory.cs new file mode 100644 index 0000000..90a336e --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/IISSites/IISSitesOptionsFactory.cs @@ -0,0 +1,133 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.Extensions; +using PKISharp.WACS.Plugins.Base.Factories; +using PKISharp.WACS.Services; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + internal class IISSitesOptionsFactory : TargetPluginOptionsFactory<IISSites, IISSitesOptions> + { + public override bool Hidden => !_iisClient.HasWebSites; + protected IIISClient _iisClient; + protected IISSiteHelper _siteHelper; + protected IISSiteOptionsHelper _optionsHelper; + private ILogService _log; + private IArgumentsService _arguments; + + public IISSitesOptionsFactory(ILogService log, IIISClient iisClient, + IISSiteHelper helper, IArgumentsService arguments) + { + _iisClient = iisClient; + _siteHelper = helper; + _log = log; + _arguments = arguments; + _optionsHelper = new IISSiteOptionsHelper(log); + } + + public override IISSitesOptions Aquire(IInputService input, RunLevel runLevel) + { + var ret = new IISSitesOptions(); + var sites = _siteHelper.GetSites(_arguments.MainArguments.HideHttps, true). + Where(x => x.Hidden == false). + Where(x => x.Hosts.Any()). + ToList(); + if (!sites.Any()) + { + _log.Error($"No sites with named bindings have been configured in IIS. Add one or choose '{ManualOptions.DescriptionText}'."); + return null; + } + input.WritePagedList(sites.Select(x => Choice.Create(x, $"{x.Name} ({x.Hosts.Count()} bindings)", x.Id.ToString())).ToList()); + var sanInput = input.RequestString("Enter a comma separated list of SiteIds or 'S' for all sites"); + sites = ProcessSiteIds(ret, sites, sanInput); + if (sites == null) + { + return null; + } + var hosts = sites.SelectMany(x => x.Hosts).Distinct().OrderBy(x => x); + if (_optionsHelper.AquireAdvancedOptions(input, hosts, runLevel, ret)) + { + return ret; + } + return ret; + } + + public override IISSitesOptions Default() + { + var ret = new IISSitesOptions(); + var args = _arguments.GetArguments<IISSiteArguments>(); + var sites = _siteHelper.GetSites(false, false); + var rawSiteIds = _arguments.TryGetRequiredArgument(nameof(args.SiteId), args.SiteId); + sites = ProcessSiteIds(ret, sites, rawSiteIds); + if (sites == null) + { + return null; + } + if (_optionsHelper.DefaultAdvancedOptions(args, sites.SelectMany(s => s.Hosts), RunLevel.Unattended, ret)) + { + return ret; + } + return null; + } + + private List<IISSiteHelper.IISSiteOption> ProcessSiteIds(IISSitesOptions options, List<IISSiteHelper.IISSiteOption> sites, string sanInput) + { + if (string.Equals(sanInput, "s", StringComparison.InvariantCultureIgnoreCase)) + { + options.All = true; + } + else + { + sites = FilterOptions(sites, sanInput); + if (sites == null) + { + return null; + } + options.SiteIds = sites.Select(x => x.Id).OrderBy(x => x).ToList(); + } + return sites; + } + + private List<IISSiteHelper.IISSiteOption> FilterOptions(List<IISSiteHelper.IISSiteOption> targets, string sanInput) + { + var siteList = new List<IISSiteHelper.IISSiteOption>(); + if (string.Equals(sanInput, "s", StringComparison.InvariantCultureIgnoreCase)) + { + return targets; + } + else + { + var siteIDs = sanInput.ParseCsv(); + foreach (var idString in siteIDs) + { + if (int.TryParse(idString, out var id)) + { + var site = targets.Where(t => t.Id == id).FirstOrDefault(); + if (site != null) + { + siteList.Add(site); + } + else + { + _log.Error($"SiteId '{idString}' not found"); + return null; + } + } + else + { + _log.Error($"Invalid SiteId '{idString}', should be a number"); + return null; + } + } + if (siteList.Count == 0) + { + _log.Warning($"No valid sites selected"); + return null; + } + } + return siteList; + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/Manual/Manual.cs b/src/main.lib/Plugins/TargetPlugins/Manual/Manual.cs new file mode 100644 index 0000000..c6cb542 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/Manual/Manual.cs @@ -0,0 +1,33 @@ +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Interfaces; +using PKISharp.WACS.Services; +using System.Collections.Generic; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + internal class Manual : ITargetPlugin + { + private ILogService _log; + private ManualOptions _options; + + public Manual(ILogService logService, ManualOptions options) + { + _log = logService; + _options = options; + } + + public Target Generate() + { + return new Target() + { + FriendlyName = $"[{nameof(Manual)}] {_options.CommonName}", + CommonName = _options.CommonName, + Parts = new List<TargetPart> { + new TargetPart { + Identifiers = _options.AlternativeNames + } + } + }; + } + } +}
\ No newline at end of file diff --git a/src/main.lib/Plugins/TargetPlugins/Manual/ManualArguments.cs b/src/main.lib/Plugins/TargetPlugins/Manual/ManualArguments.cs new file mode 100644 index 0000000..18cae37 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/Manual/ManualArguments.cs @@ -0,0 +1,8 @@ +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + class ManualArguments + { + public string CommonName { get; set; } + public string Host { get; set; } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/Manual/ManualArgumentsProvider.cs b/src/main.lib/Plugins/TargetPlugins/Manual/ManualArgumentsProvider.cs new file mode 100644 index 0000000..34a76e1 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/Manual/ManualArgumentsProvider.cs @@ -0,0 +1,27 @@ +using Fclp; +using PKISharp.WACS.Configuration; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + class ManualArgumentsProvider : BaseArgumentsProvider<ManualArguments> + { + public override string Name => "Manual plugin"; + public override string Group => "Target"; + public override string Condition => "--target manual"; + + public override bool Active(ManualArguments current) + { + return !string.IsNullOrEmpty(current.Host); + } + + public override void Configure(FluentCommandLineParser<ManualArguments> parser) + { + parser.Setup(o => o.CommonName) + .As("commonname") + .WithDescription("Specify the common name of the certificate. If not provided the first host name will be used."); + parser.Setup(o => o.Host) + .As("host") + .WithDescription("A host name to get a certificate for. This may be a comma separated list."); + } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/Manual/ManualOptions.cs b/src/main.lib/Plugins/TargetPlugins/Manual/ManualOptions.cs new file mode 100644 index 0000000..e99a6a0 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/Manual/ManualOptions.cs @@ -0,0 +1,18 @@ +using PKISharp.WACS.Plugins.Base; +using PKISharp.WACS.Plugins.Base.Options; +using System.Collections.Generic; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + [Plugin("e239db3b-b42f-48aa-b64f-46d4f3e9941b")] + class ManualOptions : TargetPluginOptions<Manual> + { + public static string DescriptionText = "Manual input"; + + public override string Name => "Manual"; + public override string Description => DescriptionText; + + public string CommonName { get; set; } + public List<string> AlternativeNames { get; set; } + } +} diff --git a/src/main.lib/Plugins/TargetPlugins/Manual/ManualOptionsFactory.cs b/src/main.lib/Plugins/TargetPlugins/Manual/ManualOptionsFactory.cs new file mode 100644 index 0000000..3a4c3a7 --- /dev/null +++ b/src/main.lib/Plugins/TargetPlugins/Manual/ManualOptionsFactory.cs @@ -0,0 +1,64 @@ +using PKISharp.WACS.Extensions; +using PKISharp.WACS.Plugins.Base.Factories; +using PKISharp.WACS.Services; +using System.Linq; + +namespace PKISharp.WACS.Plugins.TargetPlugins +{ + internal class ManualOptionsFactory : TargetPluginOptionsFactory<Manual, ManualOptions> + { + private readonly IArgumentsService _arguments; + public ManualOptionsFactory(IArgumentsService arguments) + { + _arguments = arguments; + } + + public override ManualOptions Aquire(IInputService inputService, RunLevel runLevel) + { + var input = inputService.RequestString("Enter comma-separated list of host names, starting with the common name"); + if (string.IsNullOrEmpty(input)) + { + return null; + } + else + { + return Create(input); + } + } + + public override ManualOptions Default() + { + var args = _arguments.GetArguments<ManualArguments>(); + var input = _arguments.TryGetRequiredArgument(nameof(args.Host), args.Host); + var ret = Create(input); + var commonName = args.CommonName; + if (!string.IsNullOrWhiteSpace(commonName)) + { + commonName = commonName.ToLower().Trim().ConvertPunycode(); + ret.CommonName = commonName; + if (!ret.AlternativeNames.Contains(commonName)) + { + ret.AlternativeNames.Insert(0, commonName); + } + } + return ret; + } + + private ManualOptions Create(string input) + { + var sanList = input.ParseCsv().Select(x => x.ConvertPunycode()); + if (sanList != null) + { + return new ManualOptions() + { + CommonName = sanList.First(), + AlternativeNames = sanList.ToList() + }; + } + else + { + return null; + } + } + } +} |