Skip to content

Commit

Permalink
add firewall extension decompiler
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbednarski committed Aug 25, 2023
1 parent 537e79d commit a6883a2
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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();
}
}
}
2 changes: 1 addition & 1 deletion src/ext/Firewall/wixext/FirewallCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace WixToolset.Firewall
/// </summary>
public sealed class FirewallCompiler : BaseCompilerExtension
{
public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/firewall";
public override XNamespace Namespace => FirewallConstants.Namespace;

/// <summary>
/// Processes an element for the Compiler.
Expand Down
8 changes: 5 additions & 3 deletions src/ext/Firewall/wixext/FirewallConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
173 changes: 98 additions & 75 deletions src/ext/Firewall/wixext/FirewallDecompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// The decompiler for the WiX Toolset Firewall Extension.
/// </summary>
public sealed class FirewallDecompiler : DecompilerExtension
public sealed class FirewallDecompiler : BaseWindowsInstallerDecompilerExtension
{
/// <summary>
/// Creates a decompiler for Firewall Extension.
/// </summary>
public FirewallDecompiler()
{
this.TableDefinitions = FirewallExtensionData.GetExtensionTableDefinitions();
}
public override IReadOnlyCollection<TableDefinition> TableDefinitions => FirewallTableDefinitions.All;

/// <summary>
/// Get the extensions library to be removed.
/// Called at the beginning of the decompilation of a database.
/// </summary>
/// <param name="tableDefinitions">Table definitions for library.</param>
/// <returns>Library to remove from decompiled output.</returns>
public override Library GetLibraryToRemove(TableDefinitionCollection tableDefinitions)
/// <param name="tables">The collection of all tables.</param>
public override void PreDecompileTables(TableIndexedCollection tables)
{
return FirewallExtensionData.GetExtensionLibrary(tableDefinitions);
}

/// <summary>
/// Decompiles an extension table.
/// </summary>
/// <param name="table">The table to decompile.</param>
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;
}

/// <summary>
/// Finalize decompilation.
/// </summary>
/// <param name="tables">The collection of all tables.</param>
public override void PostDecompileTables(TableIndexedCollection tables)
{
this.FinalizeFirewallExceptionTable(tables);
}

/// <summary>
Expand All @@ -60,123 +59,147 @@ 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))
{
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))
{
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))
{
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");
}

/// <summary>
/// Finalize the FirewallException table.
/// </summary>
/// <param name="tables">Collection of all tables.</param>
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
}
3 changes: 2 additions & 1 deletion src/ext/Firewall/wixext/FirewallExtensionFactory.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -13,6 +13,7 @@ public class FirewallExtensionFactory : BaseExtensionFactory
typeof(FirewallCompiler),
typeof(FirewallExtensionData),
typeof(FirewallWindowsInstallerBackendBinderExtension),
typeof(FirewallDecompiler),
};
}
}

0 comments on commit a6883a2

Please sign in to comment.