From b3deb46def7d303a79f4a5eba6a921e45d5f16ef Mon Sep 17 00:00:00 2001 From: chris_bednarski Date: Sat, 26 Aug 2023 08:31:28 +1000 Subject: [PATCH] add firewall extension decompiler --- .../FirewallExtensionFixture.cs | 59 +++++- src/ext/Firewall/wixext/FirewallCompiler.cs | 2 +- src/ext/Firewall/wixext/FirewallConstants.cs | 8 +- src/ext/Firewall/wixext/FirewallDecompiler.cs | 173 ++++++++++-------- .../wixext/FirewallExtensionFactory.cs | 3 +- 5 files changed, 164 insertions(+), 81 deletions(-) diff --git a/src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs b/src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs index b89afaf7a..06a877f6a 100644 --- a/src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs @@ -2,9 +2,11 @@ namespace WixToolsetTest.Firewall { + using System.IO; using System.Linq; - using WixInternal.TestSupport; + using System.Xml.Linq; using WixInternal.Core.TestPackage; + using WixInternal.TestSupport; using WixToolset.Firewall; using Xunit; @@ -50,6 +52,55 @@ public void CanBuildUsingFirewallARM64() }, results); } + [Fact] + public void CanRoundtripFirewallExceptions() + { + var folder = TestData.Get(@"TestData", "UsingFirewall"); + var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder }); + var output = Path.Combine(folder, "FirewallExceptionDecompile.xml"); + + build.BuildAndDecompileAndBuild(Build, Decompile, output); + + var doc = XDocument.Load(output); + var actual = doc.Descendants() + .Where(e => e.Name.Namespace == "http://wixtoolset.org/schemas/v4/wxs/firewall") + .Select(fe => new { Name = fe.Name.LocalName, Attributes = fe.Attributes().Select(a => $"{a.Name.LocalName}={a.Value}").ToArray() }) + .ToArray(); + + WixAssert.CompareLineByLine(new[] + { + "FirewallException", + "FirewallException", + }, actual.Select(a => a.Name).ToArray()); + + WixAssert.CompareLineByLine(new[] + { + "Id=ExampleFirewall", + "Name=ExampleApp", + "Scope=any", + "Port=42", + "Protocol=tcp", + "Program=[#filNdJBJmq3UCUIwmXS8x21aAsvqzk]", + "Profile=all", + "Description=An app-based firewall exception", + "Outbound=no", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual[0].Attributes); + + WixAssert.CompareLineByLine(new[] + { + "Id=fex70IVsYNnbwiHQrEepmdTPKH8XYs", + "Name=ExamplePort", + "Scope=localSubnet", + "Port=42", + "Protocol=tcp", + "Profile=all", + "Description=A port-based firewall exception", + "Outbound=yes", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual[1].Attributes); + } + private static void Build(string[] args) { var result = WixRunner.Execute(args); @@ -65,5 +116,11 @@ private static void BuildARM64(string[] args) var result = WixRunner.Execute(newArgs.ToArray()); result.AssertSuccess(); } + + private static void Decompile(string[] args) + { + var result = WixRunner.Execute(args); + result.AssertSuccess(); + } } } diff --git a/src/ext/Firewall/wixext/FirewallCompiler.cs b/src/ext/Firewall/wixext/FirewallCompiler.cs index cbe82d37b..19ee0b6dc 100644 --- a/src/ext/Firewall/wixext/FirewallCompiler.cs +++ b/src/ext/Firewall/wixext/FirewallCompiler.cs @@ -15,7 +15,7 @@ namespace WixToolset.Firewall /// public sealed class FirewallCompiler : BaseCompilerExtension { - public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/firewall"; + public override XNamespace Namespace => FirewallConstants.Namespace; /// /// Processes an element for the Compiler. diff --git a/src/ext/Firewall/wixext/FirewallConstants.cs b/src/ext/Firewall/wixext/FirewallConstants.cs index 7bb12ba47..5ecfe032e 100644 --- a/src/ext/Firewall/wixext/FirewallConstants.cs +++ b/src/ext/Firewall/wixext/FirewallConstants.cs @@ -2,12 +2,14 @@ namespace WixToolset.Firewall { - using System; - using System.Collections.Generic; - using System.Text; + using System.Xml.Linq; static class FirewallConstants { + internal static readonly XNamespace Namespace = "http://wixtoolset.org/schemas/v4/wxs/firewall"; + internal static readonly XName FirewallExceptionName = Namespace + "FirewallException"; + internal static readonly XName RemoteAddressName = Namespace + "RemoteAddress"; + // from icftypes.h public const int NET_FW_RULE_DIR_IN = 1; public const int NET_FW_RULE_DIR_OUT = 2; diff --git a/src/ext/Firewall/wixext/FirewallDecompiler.cs b/src/ext/Firewall/wixext/FirewallDecompiler.cs index c9478de15..69f2c3f4d 100644 --- a/src/ext/Firewall/wixext/FirewallDecompiler.cs +++ b/src/ext/Firewall/wixext/FirewallDecompiler.cs @@ -2,54 +2,53 @@ namespace WixToolset.Firewall { -#if TODO_CONSIDER_DECOMPILER using System; - using System.Collections; - using System.Diagnostics; - using System.Globalization; + using System.Collections.Generic; + using System.Xml.Linq; using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; - using Firewall = WixToolset.Extensions.Serialize.Firewall; - using Wix = WixToolset.Data.Serialize; /// /// The decompiler for the WiX Toolset Firewall Extension. /// - public sealed class FirewallDecompiler : DecompilerExtension + public sealed class FirewallDecompiler : BaseWindowsInstallerDecompilerExtension { - /// - /// Creates a decompiler for Firewall Extension. - /// - public FirewallDecompiler() - { - this.TableDefinitions = FirewallExtensionData.GetExtensionTableDefinitions(); - } + public override IReadOnlyCollection TableDefinitions => FirewallTableDefinitions.All; /// - /// Get the extensions library to be removed. + /// Called at the beginning of the decompilation of a database. /// - /// Table definitions for library. - /// Library to remove from decompiled output. - public override Library GetLibraryToRemove(TableDefinitionCollection tableDefinitions) + /// The collection of all tables. + public override void PreDecompileTables(TableIndexedCollection tables) { - return FirewallExtensionData.GetExtensionLibrary(tableDefinitions); } /// /// Decompiles an extension table. /// /// The table to decompile. - public override void DecompileTable(Table table) + public override bool TryDecompileTable(Table table) { switch (table.Name) { - case "WixFirewallException": + case "Wix4FirewallException": this.DecompileWixFirewallExceptionTable(table); break; default: - base.DecompileTable(table); - break; + return false; } + + return true; + } + + /// + /// Finalize decompilation. + /// + /// The collection of all tables. + public override void PostDecompileTables(TableIndexedCollection tables) + { + this.FinalizeFirewallExceptionTable(tables); } /// @@ -60,38 +59,42 @@ 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]; + var firewallException = new XElement(FirewallConstants.FirewallExceptionName, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1)) + ); - string[] addresses = ((string)row[2]).Split(','); - if (1 == addresses.Length) + if (!row.IsColumnEmpty(2)) { - // special-case the Scope attribute values - if ("*" == addresses[0]) + string[] addresses = ((string)row[2]).Split(','); + if (addresses.Length == 1) { - fire.Scope = Firewall.FirewallException.ScopeType.any; - } - else if ("LocalSubnet" == addresses[0]) - { - fire.Scope = Firewall.FirewallException.ScopeType.localSubnet; + // special-case the Scope attribute values + if (addresses[0] == "*") + { + firewallException.Add(new XAttribute("Scope", "any")); + } + else if (addresses[0] == "LocalSubnet") + { + firewallException.Add(new XAttribute("Scope", "localSubnet")); + } + else + { + FirewallDecompiler.AddRemoteAddress(firewallException, addresses[0]); + } } else { - FirewallDecompiler.AddRemoteAddress(fire, addresses[0]); - } - } - else - { - foreach (string address in addresses) - { - FirewallDecompiler.AddRemoteAddress(fire, address); + foreach (string address in addresses) + { + FirewallDecompiler.AddRemoteAddress(firewallException, address); + } } } if (!row.IsColumnEmpty(3)) { - fire.Port = (string)row[3]; + firewallException.Add(new XAttribute("Port", row.FieldAsString(3))); } if (!row.IsColumnEmpty(4)) @@ -99,26 +102,23 @@ private void DecompileWixFirewallExceptionTable(Table table) switch (Convert.ToInt32(row[4])) { case FirewallConstants.NET_FW_IP_PROTOCOL_TCP: - fire.Protocol = Firewall.FirewallException.ProtocolType.tcp; + firewallException.Add(new XAttribute("Protocol", "tcp")); break; case FirewallConstants.NET_FW_IP_PROTOCOL_UDP: - fire.Protocol = Firewall.FirewallException.ProtocolType.udp; + firewallException.Add(new XAttribute("Protocol", "udp")); break; } } if (!row.IsColumnEmpty(5)) { - fire.Program = (string)row[5]; + firewallException.Add(new XAttribute("Program", row.FieldAsString(5))); } if (!row.IsColumnEmpty(6)) { - int attr = Convert.ToInt32(row[6]); - if (0x1 == (attr & 0x1)) // feaIgnoreFailures - { - fire.IgnoreFailure = Firewall.YesNoType.yes; - } + var attr = Convert.ToInt32(row[6]); + AttributeIfNotNull("IgnoreFailure", (attr & 0x1) == 0x1); } if (!row.IsColumnEmpty(7)) @@ -126,24 +126,23 @@ private void DecompileWixFirewallExceptionTable(Table table) switch (Convert.ToInt32(row[7])) { case FirewallConstants.NET_FW_PROFILE2_DOMAIN: - fire.Profile = Firewall.FirewallException.ProfileType.domain; + firewallException.Add(new XAttribute("Profile", "domain")); break; case FirewallConstants.NET_FW_PROFILE2_PRIVATE: - fire.Profile = Firewall.FirewallException.ProfileType.@private; + firewallException.Add(new XAttribute("Profile", "private")); break; case FirewallConstants.NET_FW_PROFILE2_PUBLIC: - fire.Profile = Firewall.FirewallException.ProfileType.@public; + firewallException.Add(new XAttribute("Profile", "public")); break; case FirewallConstants.NET_FW_PROFILE2_ALL: - fire.Profile = Firewall.FirewallException.ProfileType.all; + firewallException.Add(new XAttribute("Profile", "all")); break; } } - // Description column is new in v3.6 - if (9 < row.Fields.Length && !row.IsColumnEmpty(9)) + if (!row.IsColumnEmpty(9)) { - fire.Description = (string)row[9]; + firewallException.Add(new XAttribute("Description", row.FieldAsString(9))); } if (!row.IsColumnEmpty(10)) @@ -151,32 +150,56 @@ private void DecompileWixFirewallExceptionTable(Table table) switch (Convert.ToInt32(row[10])) { case FirewallConstants.NET_FW_RULE_DIR_IN: - fire.Direction = Firewall.FirewallException.DirectionType.@in; + + firewallException.Add(AttributeIfNotNull("Outbound", false)); break; case FirewallConstants.NET_FW_RULE_DIR_OUT: - fire.Direction = Firewall.FirewallException.DirectionType.@out; + firewallException.Add(AttributeIfNotNull("Outbound", true)); break; } } - 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")); - } + this.DecompilerHelper.IndexElement(row, firewallException); } } - private static void AddRemoteAddress(Firewall.FirewallException fire, string address) + private static void AddRemoteAddress(XElement firewallException, string address) + { + var remoteAddress = new XElement(FirewallConstants.RemoteAddressName, + new XAttribute("Value", address) + ); + + firewallException.AddAfterSelf(remoteAddress); + } + + private static XAttribute AttributeIfNotNull(string name, bool value) + { + return new XAttribute(name, value ? "yes" : "no"); + } + + /// + /// Finalize the FirewallException table. + /// + /// Collection of all tables. + private void FinalizeFirewallExceptionTable(TableIndexedCollection tables) { - Firewall.RemoteAddress remote = new Firewall.RemoteAddress(); - remote.Content = address; - fire.AddChild(remote); + if (tables.TryGetTable("Wix4FirewallException", out var firewallExceptionTable)) + { + foreach (var row in firewallExceptionTable.Rows) + { + var xmlConfig = this.DecompilerHelper.GetIndexedElement(row); + + var componentId = row.FieldAsString(8); + if (this.DecompilerHelper.TryGetIndexedElement("Component", componentId, out var component)) + { + component.Add(xmlConfig); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, firewallExceptionTable.Name, row.GetPrimaryKey(), "Component_", componentId, "Component")); + } + } + } } } -#endif } diff --git a/src/ext/Firewall/wixext/FirewallExtensionFactory.cs b/src/ext/Firewall/wixext/FirewallExtensionFactory.cs index 279b322a2..8ce6f4ba0 100644 --- a/src/ext/Firewall/wixext/FirewallExtensionFactory.cs +++ b/src/ext/Firewall/wixext/FirewallExtensionFactory.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -13,6 +13,7 @@ public class FirewallExtensionFactory : BaseExtensionFactory typeof(FirewallCompiler), typeof(FirewallExtensionData), typeof(FirewallWindowsInstallerBackendBinderExtension), + typeof(FirewallDecompiler), }; } }