From 97b80191048b23f2e870c9d6a27e368ebaaf594d Mon Sep 17 00:00:00 2001 From: Rob Mensching <rob@firegiant.com> Date: Tue, 2 Jan 2018 23:14:58 -0800 Subject: Initial code commit --- src/wixext/FirewallCompiler.cs | 356 +++++++++++++++++++++ src/wixext/FirewallConstants.cs | 21 ++ src/wixext/FirewallDecompiler.cs | 169 ++++++++++ src/wixext/FirewallErrors.cs | 42 +++ src/wixext/FirewallExtensionData.cs | 24 ++ src/wixext/FirewallExtensionFactory.cs | 18 ++ .../FirewallWindowsInstallerBackendExtension.cs | 26 ++ src/wixext/Tuples/FirewallTupleDefinitions.cs | 31 ++ src/wixext/Tuples/WixFirewallExceptionTuple.cs | 93 ++++++ src/wixext/WixToolset.Firewall.wixext.csproj | 36 +++ src/wixext/WixToolset.Firewall.wixext.targets | 11 + src/wixext/firewall.xsd | 211 ++++++++++++ src/wixext/tables.xml | 28 ++ 13 files changed, 1066 insertions(+) create mode 100644 src/wixext/FirewallCompiler.cs create mode 100644 src/wixext/FirewallConstants.cs create mode 100644 src/wixext/FirewallDecompiler.cs create mode 100644 src/wixext/FirewallErrors.cs create mode 100644 src/wixext/FirewallExtensionData.cs create mode 100644 src/wixext/FirewallExtensionFactory.cs create mode 100644 src/wixext/FirewallWindowsInstallerBackendExtension.cs create mode 100644 src/wixext/Tuples/FirewallTupleDefinitions.cs create mode 100644 src/wixext/Tuples/WixFirewallExceptionTuple.cs create mode 100644 src/wixext/WixToolset.Firewall.wixext.csproj create mode 100644 src/wixext/WixToolset.Firewall.wixext.targets create mode 100644 src/wixext/firewall.xsd create mode 100644 src/wixext/tables.xml (limited to 'src/wixext') diff --git a/src/wixext/FirewallCompiler.cs b/src/wixext/FirewallCompiler.cs new file mode 100644 index 00000000..0696b4b1 --- /dev/null +++ b/src/wixext/FirewallCompiler.cs @@ -0,0 +1,356 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + + /// <summary> + /// The compiler for the WiX Toolset Firewall Extension. + /// </summary> + public sealed class FirewallCompiler : BaseCompilerExtension + { + public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/firewall"; + + /// <summary> + /// Processes an element for the Compiler. + /// </summary> + /// <param name="sourceLineNumbers">Source line number for the parent element.</param> + /// <param name="parentElement">Parent element of element to process.</param> + /// <param name="element">Element to process.</param> + /// <param name="contextValues">Extra information about the context in which this element is being parsed.</param> + public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context) + { + switch (parentElement.Name.LocalName) + { + case "File": + string fileId = context["FileId"]; + string fileComponentId = context["ComponentId"]; + + switch (element.Name.LocalName) + { + case "FirewallException": + this.ParseFirewallExceptionElement(intermediate, section, element, fileComponentId, fileId); + break; + default: + this.ParseHelper.UnexpectedElement(parentElement, element); + break; + } + break; + case "Component": + string componentId = context["ComponentId"]; + + switch (element.Name.LocalName) + { + case "FirewallException": + this.ParseFirewallExceptionElement(intermediate, section, element, componentId, null); + break; + default: + this.ParseHelper.UnexpectedElement(parentElement, element); + break; + } + break; + default: + this.ParseHelper.UnexpectedElement(parentElement, element); + break; + } + } + + /// <summary> + /// Parses a FirewallException element. + /// </summary> + /// <param name="element">The element to parse.</param> + /// <param name="componentId">Identifier of the component that owns this firewall exception.</param> + /// <param name="fileId">The file identifier of the parent element (null if nested under Component).</param> + private void ParseFirewallExceptionElement(Intermediate intermediate, IntermediateSection section, XElement element, string componentId, string fileId) + { + SourceLineNumber sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string name = null; + int attributes = 0; + string file = null; + string program = null; + string port = null; + string protocolValue = null; + int? protocol = null; + string profileValue = null; + int? profile = null; + string scope = null; + string remoteAddresses = null; + string description = null; + + foreach (XAttribute attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "File": + if (null != fileId) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "File", "File")); + } + else + { + file = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + } + break; + case "IgnoreFailure": + if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= 0x1; // feaIgnoreFailures + } + break; + case "Program": + if (null != fileId) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "Program", "File")); + } + else + { + program = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + } + break; + case "Port": + port = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Protocol": + protocolValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (protocolValue) + { + case "tcp": + protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP; + break; + case "udp": + protocol = FirewallConstants.NET_FW_IP_PROTOCOL_UDP; + break; + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Protocol", protocolValue, "tcp", "udp")); + break; + } + break; + case "Scope": + scope = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (scope) + { + case "any": + remoteAddresses = "*"; + break; + case "localSubnet": + remoteAddresses = "LocalSubnet"; + break; + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Scope", scope, "any", "localSubnet")); + break; + } + break; + case "Profile": + profileValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (profileValue) + { + case "domain": + profile = FirewallConstants.NET_FW_PROFILE2_DOMAIN; + break; + case "private": + profile = FirewallConstants.NET_FW_PROFILE2_PRIVATE; + break; + case "public": + profile = FirewallConstants.NET_FW_PROFILE2_PUBLIC; + break; + case "all": + profile = FirewallConstants.NET_FW_PROFILE2_ALL; + break; + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Profile", profileValue, "domain", "private", "public", "all")); + break; + } + break; + case "Description": + description = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + // parse RemoteAddress children + foreach (XElement child in element.Elements()) + { + if (this.Namespace == child.Name.Namespace) + { + SourceLineNumber childSourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "RemoteAddress": + if (null != scope) + { + this.Messaging.Write(FirewallErrors.IllegalRemoteAddressWithScopeAttribute(sourceLineNumbers)); + } + else + { + this.ParseRemoteAddressElement(intermediate, section, child, ref remoteAddresses); + } + break; + default: + this.ParseHelper.UnexpectedElement(element, child); + break; + } + } + else + { + this.ParseHelper.ParseExtensionElement(this.Context.Extensions, intermediate, section, element, child); + } + } + + // Id and Name are required + if (null == id) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (null == name) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name")); + } + + // Scope or child RemoteAddress(es) are required + if (null == remoteAddresses) + { + this.Messaging.Write(ErrorMessages.ExpectedAttributeOrElement(sourceLineNumbers, element.Name.LocalName, "Scope", "RemoteAddress")); + } + + // can't have both Program and File + if (null != program && null != file) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "File", "Program")); + } + + // must be nested under File, have File or Program attributes, or have Port attribute + if (String.IsNullOrEmpty(fileId) && String.IsNullOrEmpty(file) && String.IsNullOrEmpty(program) && String.IsNullOrEmpty(port)) + { + this.Messaging.Write(FirewallErrors.NoExceptionSpecified(sourceLineNumbers)); + } + + if (!this.Messaging.EncounteredError) + { + // at this point, File attribute and File parent element are treated the same + if (null != file) + { + fileId = file; + } + + var row = this.ParseHelper.CreateRow(section, sourceLineNumbers, "WixFirewallException", id); + row.Set(1, name); + row.Set(2, remoteAddresses); + + if (!String.IsNullOrEmpty(port)) + { + row.Set(3, port); + + if (!protocol.HasValue) + { + // default protocol is "TCP" + protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP; + } + } + + if (protocol.HasValue) + { + row.Set(4, protocol); + } + + if (!String.IsNullOrEmpty(fileId)) + { + row.Set(5, $"[#{fileId}]"); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "File", fileId); + } + else if (!String.IsNullOrEmpty(program)) + { + row.Set(5, program); + } + + if (CompilerConstants.IntegerNotSet != attributes) + { + row.Set(6, attributes); + } + + // Default is "all" + row.Set(7, profile ?? FirewallConstants.NET_FW_PROFILE2_ALL); + + row.Set(8, componentId); + + row.Set(9, description); + + if (this.Context.Platform == Platform.ARM) + { + // Ensure ARM version of the CA is referenced + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "CustomAction", "WixSchedFirewallExceptionsInstall_ARM"); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "CustomAction", "WixSchedFirewallExceptionsUninstall_ARM"); + } + else + { + // All other supported platforms use x86 + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "CustomAction", "WixSchedFirewallExceptionsInstall"); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "CustomAction", "WixSchedFirewallExceptionsUninstall"); + } + } + } + + /// <summary> + /// Parses a RemoteAddress element + /// </summary> + /// <param name="element">The element to parse.</param> + private void ParseRemoteAddressElement(Intermediate intermediate, IntermediateSection section, XElement element, ref string remoteAddresses) + { + SourceLineNumber sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + + // no attributes + foreach (XAttribute attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + this.ParseHelper.UnexpectedAttribute(element, attrib); + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + + string address = this.ParseHelper.GetTrimmedInnerText(element); + if (String.IsNullOrEmpty(address)) + { + this.Messaging.Write(FirewallErrors.IllegalEmptyRemoteAddress(sourceLineNumbers)); + } + else + { + if (String.IsNullOrEmpty(remoteAddresses)) + { + remoteAddresses = address; + } + else + { + remoteAddresses = String.Concat(remoteAddresses, ",", address); + } + } + } + } +} diff --git a/src/wixext/FirewallConstants.cs b/src/wixext/FirewallConstants.cs new file mode 100644 index 00000000..16caa5b4 --- /dev/null +++ b/src/wixext/FirewallConstants.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall +{ + using System; + using System.Collections.Generic; + using System.Text; + + static class FirewallConstants + { + // from icftypes.h + public const int NET_FW_IP_PROTOCOL_TCP = 6; + public const int NET_FW_IP_PROTOCOL_UDP = 17; + + // from icftypes.h + public const int NET_FW_PROFILE2_DOMAIN = 0x0001; + public const int NET_FW_PROFILE2_PRIVATE = 0x0002; + public const int NET_FW_PROFILE2_PUBLIC = 0x0004; + public const int NET_FW_PROFILE2_ALL = 0x7FFFFFFF; + } +} diff --git a/src/wixext/FirewallDecompiler.cs b/src/wixext/FirewallDecompiler.cs new file mode 100644 index 00000000..b060f8e2 --- /dev/null +++ b/src/wixext/FirewallDecompiler.cs @@ -0,0 +1,169 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall +{ +#if TODO_CONSIDER_DECOMPILER + using System; + using System.Collections; + using System.Diagnostics; + using System.Globalization; + using WixToolset.Data; + using WixToolset.Extensibility; + using Firewall = WixToolset.Extensions.Serialize.Firewall; + using Wix = WixToolset.Data.Serialize; + + /// <summary> + /// The decompiler for the WiX Toolset Firewall Extension. + /// </summary> + public sealed class FirewallDecompiler : DecompilerExtension + { + /// <summary> + /// Creates a decompiler for Firewall Extension. + /// </summary> + public FirewallDecompiler() + { + this.TableDefinitions = FirewallExtensionData.GetExtensionTableDefinitions(); + } + + /// <summary> + /// Get the extensions library to be removed. + /// </summary> + /// <param name="tableDefinitions">Table definitions for library.</param> + /// <returns>Library to remove from decompiled output.</returns> + public override Library GetLibraryToRemove(TableDefinitionCollection tableDefinitions) + { + return FirewallExtensionData.GetExtensionLibrary(tableDefinitions); + } + + /// <summary> + /// Decompiles an extension table. + /// </summary> + /// <param name="table">The table to decompile.</param> + public override void DecompileTable(Table table) + { + switch (table.Name) + { + case "WixFirewallException": + this.DecompileWixFirewallExceptionTable(table); + break; + default: + base.DecompileTable(table); + break; + } + } + + /// <summary> + /// Decompile the WixFirewallException table. + /// </summary> + /// <param name="table">The table to decompile.</param> + private void DecompileWixFirewallExceptionTable(Table table) + { + foreach (Row row in table.Rows) + { + Firewall.FirewallException fire = new Firewall.FirewallException(); + fire.Id = (string)row[0]; + fire.Name = (string)row[1]; + + string[] addresses = ((string)row[2]).Split(','); + if (1 == addresses.Length) + { + // special-case the Scope attribute values + if ("*" == addresses[0]) + { + fire.Scope = Firewall.FirewallException.ScopeType.any; + } + else if ("LocalSubnet" == addresses[0]) + { + fire.Scope = Firewall.FirewallException.ScopeType.localSubnet; + } + else + { + FirewallDecompiler.AddRemoteAddress(fire, addresses[0]); + } + } + else + { + foreach (string address in addresses) + { + FirewallDecompiler.AddRemoteAddress(fire, address); + } + } + + if (!row.IsColumnEmpty(3)) + { + fire.Port = (string)row[3]; + } + + if (!row.IsColumnEmpty(4)) + { + switch (Convert.ToInt32(row[4])) + { + case FirewallConstants.NET_FW_IP_PROTOCOL_TCP: + fire.Protocol = Firewall.FirewallException.ProtocolType.tcp; + break; + case FirewallConstants.NET_FW_IP_PROTOCOL_UDP: + fire.Protocol = Firewall.FirewallException.ProtocolType.udp; + break; + } + } + + if (!row.IsColumnEmpty(5)) + { + fire.Program = (string)row[5]; + } + + if (!row.IsColumnEmpty(6)) + { + int attr = Convert.ToInt32(row[6]); + if (0x1 == (attr & 0x1)) // feaIgnoreFailures + { + fire.IgnoreFailure = Firewall.YesNoType.yes; + } + } + + if (!row.IsColumnEmpty(7)) + { + switch (Convert.ToInt32(row[7])) + { + case FirewallConstants.NET_FW_PROFILE2_DOMAIN: + fire.Profile = Firewall.FirewallException.ProfileType.domain; + break; + case FirewallConstants.NET_FW_PROFILE2_PRIVATE: + fire.Profile = Firewall.FirewallException.ProfileType.@private; + break; + case FirewallConstants.NET_FW_PROFILE2_PUBLIC: + fire.Profile = Firewall.FirewallException.ProfileType.@public; + break; + case FirewallConstants.NET_FW_PROFILE2_ALL: + fire.Profile = Firewall.FirewallException.ProfileType.all; + break; + } + } + + // Description column is new in v3.6 + if (9 < row.Fields.Length && !row.IsColumnEmpty(9)) + { + fire.Description = (string)row[9]; + } + + Wix.Component component = (Wix.Component)this.Core.GetIndexedElement("Component", (string)row[8]); + if (null != component) + { + component.AddChild(fire); + } + else + { + this.Core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", (string)row[6], "Component")); + } + } + } + + private static void AddRemoteAddress(Firewall.FirewallException fire, string address) + { + Firewall.RemoteAddress remote = new Firewall.RemoteAddress(); + remote.Content = address; + fire.AddChild(remote); + } + } +#endif +} diff --git a/src/wixext/FirewallErrors.cs b/src/wixext/FirewallErrors.cs new file mode 100644 index 00000000..3fff8c8d --- /dev/null +++ b/src/wixext/FirewallErrors.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall +{ + using System.Resources; + using WixToolset.Data; + + public static class FirewallErrors + { + public static Message IllegalRemoteAddressWithScopeAttribute(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IllegalRemoteAddressWithScopeAttribute, "The RemoteAddress element cannot be specified because its parent FirewallException already specified the Scope attribute. To use RemoteAddress elements, omit the Scope attribute."); + } + + public static Message IllegalEmptyRemoteAddress(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IllegalEmptyRemoteAddress, "The RemoteAddress element's inner text cannot be an empty string or completely whitespace."); + } + + public static Message NoExceptionSpecified(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.NoExceptionSpecified, "The FirewallException element doesn't identify the target of the firewall exception. To create an application exception, nest the FirewallException element under a File element or provide a value for the File or Program attributes. To create a port exception, provide a value for the Port attribute."); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, ResourceManager resourceManager, string resourceName, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, resourceManager, resourceName, args); + } + + public enum Ids + { + IllegalRemoteAddressWithScopeAttribute = 6401, + IllegalEmptyRemoteAddress = 6402, + NoExceptionSpecified = 6403, + } + } +} diff --git a/src/wixext/FirewallExtensionData.cs b/src/wixext/FirewallExtensionData.cs new file mode 100644 index 00000000..78939c4e --- /dev/null +++ b/src/wixext/FirewallExtensionData.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall +{ + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Firewall.Tuples; + + public sealed class FirewallExtensionData : BaseExtensionData + { + public override string DefaultCulture => "en-US"; + + public override bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition) + { + tupleDefinition = (name == FirewallTupleDefinitionNames.WixFirewallException) ? FirewallTupleDefinitions.WixFirewallException : null; + return tupleDefinition != null; + } + + public override Intermediate GetLibrary(ITupleDefinitionCreator tupleDefinitions) + { + return Intermediate.Load(typeof(FirewallExtensionData).Assembly, "WixToolset.Firewall.firewall.wixlib", tupleDefinitions); + } + } +} diff --git a/src/wixext/FirewallExtensionFactory.cs b/src/wixext/FirewallExtensionFactory.cs new file mode 100644 index 00000000..4594419d --- /dev/null +++ b/src/wixext/FirewallExtensionFactory.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + + public class FirewallExtensionFactory : BaseExtensionFactory + { + protected override IEnumerable<Type> ExtensionTypes => new[] + { + typeof(FirewallCompiler), + typeof(FirewallExtensionData), + typeof(FirewallWindowsInstallerBackendExtension), + }; + } +} diff --git a/src/wixext/FirewallWindowsInstallerBackendExtension.cs b/src/wixext/FirewallWindowsInstallerBackendExtension.cs new file mode 100644 index 00000000..a1c78f04 --- /dev/null +++ b/src/wixext/FirewallWindowsInstallerBackendExtension.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall +{ + using System.Linq; + using System.Xml; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + public class FirewallWindowsInstallerBackendExtension : BaseWindowsInstallerBackendExtension + { + private static readonly TableDefinition[] Tables = LoadTables(); + + protected override TableDefinition[] TableDefinitionsForTuples => Tables; + + private static TableDefinition[] LoadTables() + { + using (var resourceStream = typeof(FirewallWindowsInstallerBackendExtension).Assembly.GetManifestResourceStream("WixToolset.Firewall.tables.xml")) + using (var reader = XmlReader.Create(resourceStream)) + { + var tables = TableDefinitionCollection.Load(reader); + return tables.ToArray(); + } + } + } +} diff --git a/src/wixext/Tuples/FirewallTupleDefinitions.cs b/src/wixext/Tuples/FirewallTupleDefinitions.cs new file mode 100644 index 00000000..79fc28cf --- /dev/null +++ b/src/wixext/Tuples/FirewallTupleDefinitions.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall.Tuples +{ + using WixToolset.Data; + + public static class FirewallTupleDefinitionNames + { + public static string WixFirewallException { get; } = "WixFirewallException"; + } + + public static partial class FirewallTupleDefinitions + { + public static readonly IntermediateTupleDefinition WixFirewallException = new IntermediateTupleDefinition( + FirewallTupleDefinitionNames.WixFirewallException, + new[] + { + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.WixFirewallException), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Name), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.RemoteAddresses), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Port), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Protocol), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Program), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Attributes), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Profile), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Component_), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Description), IntermediateFieldType.String), + }, + typeof(WixFirewallExceptionTuple)); + } +} diff --git a/src/wixext/Tuples/WixFirewallExceptionTuple.cs b/src/wixext/Tuples/WixFirewallExceptionTuple.cs new file mode 100644 index 00000000..715a4b9b --- /dev/null +++ b/src/wixext/Tuples/WixFirewallExceptionTuple.cs @@ -0,0 +1,93 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Firewall.Tuples +{ + using WixToolset.Data; + + public enum WixFirewallExceptionTupleFields + { + WixFirewallException, + Name, + RemoteAddresses, + Port, + Protocol, + Program, + Attributes, + Profile, + Component_, + Description, + } + + public class WixFirewallExceptionTuple : IntermediateTuple + { + public WixFirewallExceptionTuple() : base(FirewallTupleDefinitions.WixFirewallException, null, null) + { + } + + public WixFirewallExceptionTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(FirewallTupleDefinitions.WixFirewallException, sourceLineNumber, id) + { + } + + public IntermediateField this[WixFirewallExceptionTupleFields index] => this.Fields[(int)index]; + + public string WixFirewallException + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.WixFirewallException].AsString(); + set => this.Set((int)WixFirewallExceptionTupleFields.WixFirewallException, value); + } + + public string Name + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.Name].AsString(); + set => this.Set((int)WixFirewallExceptionTupleFields.Name, value); + } + + public string RemoteAddresses + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.RemoteAddresses].AsString(); + set => this.Set((int)WixFirewallExceptionTupleFields.RemoteAddresses, value); + } + + public string Port + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.Port].AsString(); + set => this.Set((int)WixFirewallExceptionTupleFields.Port, value); + } + + public int Protocol + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.Protocol].AsNumber(); + set => this.Set((int)WixFirewallExceptionTupleFields.Protocol, value); + } + + public string Program + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.Program].AsString(); + set => this.Set((int)WixFirewallExceptionTupleFields.Program, value); + } + + public int Attributes + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.Attributes].AsNumber(); + set => this.Set((int)WixFirewallExceptionTupleFields.Attributes, value); + } + + public int Profile + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.Profile].AsNumber(); + set => this.Set((int)WixFirewallExceptionTupleFields.Profile, value); + } + + public string Component_ + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.Component_].AsString(); + set => this.Set((int)WixFirewallExceptionTupleFields.Component_, value); + } + + public string Description + { + get => this.Fields[(int)WixFirewallExceptionTupleFields.Description].AsString(); + set => this.Set((int)WixFirewallExceptionTupleFields.Description, value); + } + } +} \ No newline at end of file diff --git a/src/wixext/WixToolset.Firewall.wixext.csproj b/src/wixext/WixToolset.Firewall.wixext.csproj new file mode 100644 index 00000000..2d89911c --- /dev/null +++ b/src/wixext/WixToolset.Firewall.wixext.csproj @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> + +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>netstandard2.0</TargetFramework> + <RootNamespace>WixToolset.Firewall</RootNamespace> + <Description>WiX Toolset Firewallity Extension</Description> + <Title>WiX Toolset Firewall Extension</Title> + <IsTool>true</IsTool> + <ContentTargetFolders>build</ContentTargetFolders> + </PropertyGroup> + + <ItemGroup> + <Content Include="$(MSBuildThisFileName).targets" /> + <Content Include="firewall.xsd" PackagePath="tools" /> + <EmbeddedResource Include="tables.xml" /> + <EmbeddedResource Include="$(OutputPath)..\firewall.wixlib" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Data\README.md') " /> + <PackageReference Include="WixToolset.Data" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Data\README.md') " PrivateAssets="all" /> + + <ProjectReference Include="$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Extensibility\README.md') " /> + <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Extensibility\README.md') " PrivateAssets="all" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\wixlib\firewall.wixproj" ReferenceOutputAssembly="false" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.7" PrivateAssets="all" /> + </ItemGroup> +</Project> diff --git a/src/wixext/WixToolset.Firewall.wixext.targets b/src/wixext/WixToolset.Firewall.wixext.targets new file mode 100644 index 00000000..c717450f --- /dev/null +++ b/src/wixext/WixToolset.Firewall.wixext.targets @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> + +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> + <PropertyGroup> + <WixToolsetFirewallWixextPath Condition=" '$(WixToolsetFirewallWixextPath)' == '' ">$(MSBuildThisFileDirectory)..\tools\WixToolset.Firewall.wixext.dll</WixToolsetFirewallWixextPath> + </PropertyGroup> + <ItemGroup> + <WixExtension Include="$(WixToolsetFirewallWixextPath)" /> + </ItemGroup> +</Project> diff --git a/src/wixext/firewall.xsd b/src/wixext/firewall.xsd new file mode 100644 index 00000000..d64aafef --- /dev/null +++ b/src/wixext/firewall.xsd @@ -0,0 +1,211 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> + + +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:xse=" http://wixtoolset.org/schemas/XmlSchemaExtension" + xmlns:html="http://www.w3.org/1999/xhtml" + targetNamespace="http://wixtoolset.org/schemas/v4/wxs/firewall" + xmlns="http://wixtoolset.org/schemas/v4/wxs/firewall"> + <xs:annotation> + <xs:documentation> + The source code schema for the WiX Toolset Firewall Extension. + </xs:documentation> + </xs:annotation> + + <xs:import namespace="http://wixtoolset.org/schemas/v4/wxs" /> + + <xs:element name="FirewallException"> + <xs:annotation> + <xs:documentation> + Registers an exception for a program or a specific port and protocol in the Windows Firewall + on Windows XP SP2, Windows Server 2003 SP1, and later. For more information about the Windows + Firewall, see <html:a href="http://msdn.microsoft.com/en-us/library/aa364679.aspx"> + About Windows Firewall API</html:a>. + </xs:documentation> + <xs:appinfo> + <xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="Component" /> + <xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="File" /> + </xs:appinfo> + </xs:annotation> + + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation> + Explicitly-listed remote addresses that this exception allows through the + firewall. + </xs:documentation> + </xs:annotation> + <xs:element ref="RemoteAddress" /> + </xs:choice> + + <xs:attribute name="Id" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + Unique ID of this firewall exception. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="Name" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + Name of this firewall exception, visible to the user in the firewall + control panel. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="Scope"> + <xs:annotation> + <xs:documentation> + The scope of this firewall exception, which indicates whether incoming + connections can come from any computer including those on the Internet + or only those on the local network subnet. To more precisely specify + allowed remote address, specify a custom scope using RemoteAddress + child elements. + </xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="any" /> + <xs:enumeration value="localSubnet" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + + <xs:attribute name="Port" type="xs:string"> + <xs:annotation> + <xs:documentation> + Port to allow through the firewall for this exception. + + If you use Port and also File or Program in the same + FirewallException element, the exception will fail to install on + Windows XP and Windows Server 2003. IgnoreFailure="yes" can be used to + ignore the resulting failure, but the exception will not be added. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="Protocol"> + <xs:annotation> + <xs:documentation> + IP protocol used for this firewall exception. If Port is defined, + "tcp" is assumed if the protocol is not specified. + + If you use Protocol and also File or Program in the same + FirewallException element, the exception will fail to install on + Windows XP and Windows Server 2003. IgnoreFailure="yes" can be used to + ignore the resulting failure, but the exception will not be added. + </xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="tcp" /> + <xs:enumeration value="udp" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + + <xs:attribute name="File" type="xs:string"> + <xs:annotation> + <xs:documentation> + Identifier of a file to be granted access to all incoming ports and + protocols. If you use File, you cannot also use Program. + + If you use File and also Port or Protocol in the same + FirewallException element, the exception will fail to install on + Windows XP and Windows Server 2003. IgnoreFailure="yes" can be used to + ignore the resulting failure, but the exception will not be added. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="Program" type="xs:string"> + <xs:annotation> + <xs:documentation> + Path to a target program to be granted access to all incoming ports and + protocols. Note that this is a formatted field, so you can use [#fileId] + syntax to refer to a file being installed. If you use Program, you cannot + also use File. + + If you use Program and also Port or Protocol in the same + FirewallException element, the exception will fail to install on + Windows XP and Windows Server 2003. IgnoreFailure="yes" can be used to + ignore the resulting failure, but the exception will not be added. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="IgnoreFailure" type="YesNoType"> + <xs:annotation> + <xs:documentation> + If "yes," failures to register this firewall exception will be silently + ignored. If "no" (the default), failures will cause rollback. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="Profile"> + <xs:annotation> + <xs:documentation> + Profile type for this firewall exception. Default is "all". + </xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="domain" /> + <xs:enumeration value="private" /> + <xs:enumeration value="public" /> + <xs:enumeration value="all" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="Description" type="xs:string"> + <xs:annotation> + <xs:documentation> + Description for this firewall rule displayed in Windows Firewall manager in + Windows Vista and later. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + + <xs:element name="RemoteAddress"> + <xs:annotation> + <xs:documentation> + A remote address to which the port or program can listen. Address formats vary + based on the version of Windows and Windows Firewall the program is being installed + on. For Windows XP SP2 and Windows Server 2003 SP1, see + <html:a href="http://msdn.microsoft.com/en-us/library/aa365270.aspx"> + RemoteAddresses Property</html:a>. + For Windows Vista and Windows Server 2008, see + <html:a href="http://msdn.microsoft.com/en-us/library/aa365366.aspx"> + RemoteAddresses Property</html:a>. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:annotation> + <xs:documentation> + A remote address. + </xs:documentation> + </xs:annotation> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + + <xs:simpleType name="YesNoType"> + <xs:annotation> + <xs:documentation>Values of this type will either be "yes" or "no".</xs:documentation> + </xs:annotation> + <xs:restriction base='xs:NMTOKEN'> + <xs:enumeration value="no"/> + <xs:enumeration value="yes"/> + </xs:restriction> + </xs:simpleType> +</xs:schema> diff --git a/src/wixext/tables.xml b/src/wixext/tables.xml new file mode 100644 index 00000000..5b408b96 --- /dev/null +++ b/src/wixext/tables.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> + + +<tableDefinitions xmlns="http://wixtoolset.org/schemas/v4/wi/tables"> + <tableDefinition name="WixFirewallException"> + <columnDefinition name="WixFirewallException" type="string" length="72" primaryKey="yes" modularize="column" + category="identifier" description="The primary key, a non-localized token." /> + <columnDefinition name="Name" type="localized" length="255" nullable="yes" modularize="property" + category="formatted" description="Localizable display name." /> + <columnDefinition name="RemoteAddresses" type="string" length="0" modularize="property" + category="formatted" description="Remote address to accept incoming connections from." /> + <columnDefinition name="Port" type="string" length="0" modularize="property" nullable="yes" + category="formatted" minValue="1" description="Port number." /> + <columnDefinition name="Protocol" type="number" length="1" nullable="yes" + category="integer" minValue="6" maxValue="17" description="Protocol (6=TCP; 17=UDP)." /> + <columnDefinition name="Program" type="string" length="255" nullable="yes" modularize="property" + category="formatted" description="Exception for a program (formatted path name)." /> + <columnDefinition name="Attributes" type="number" length="4" nullable="yes" + minValue="0" maxValue="65536" description="Vital=1" /> + <columnDefinition name="Profile" type="number" length="4" nullable="no" + category="integer" minValue="1" maxValue="2147483647" description="Profile (1=domain; 2=private; 4=public; 2147483647=all)." /> + <columnDefinition name="Component_" type="string" length="72" modularize="column" + keyTable="Component" keyColumn="1" category="identifier" description="Foreign key into the Component table referencing component that controls the firewall configuration."/> + <columnDefinition name="Description" type="string" length="255" nullable="yes" + category="formatted" description="Description displayed in Windows Firewall manager for this firewall rule."/> + </tableDefinition> +</tableDefinitions> -- cgit v1.2.3-55-g6feb