// 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.Reflection; using System.Security; using System.Xml.Linq; using WixToolset.Data; using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; /// /// The decompiler for the WiX Toolset Firewall Extension. /// public sealed class FirewallDecompiler : BaseWindowsInstallerDecompilerExtension { public override IReadOnlyCollection TableDefinitions => FirewallTableDefinitions.All; private IParseHelper ParseHelper { get; set; } public override void PreDecompile(IWindowsInstallerDecompileContext context, IWindowsInstallerDecompilerHelper helper) { base.PreDecompile(context, helper); this.ParseHelper = context.ServiceProvider.GetService(); } /// /// Called at the beginning of the decompilation of a database. /// /// The collection of all tables. public override void PreDecompileTables(TableIndexedCollection tables) { } /// /// Decompiles an extension table. /// /// The table to decompile. public override bool TryDecompileTable(Table table) { switch (table.Name) { case "WixFirewallException": case "Wix4FirewallException": case "Wix5FirewallException": this.DecompileWixFirewallExceptionTable(table); break; default: return false; } return true; } /// /// Finalize decompilation. /// /// The collection of all tables. public override void PostDecompileTables(TableIndexedCollection tables) { this.FinalizeFirewallExceptionTable(tables); } /// /// Decompile the WixFirewallException table. /// /// The table to decompile. private void DecompileWixFirewallExceptionTable(Table table) { foreach (var row in table.Rows) { var firewallException = new XElement(FirewallConstants.FirewallExceptionName, new XAttribute("Id", row.FieldAsString(0)), new XAttribute("Name", row.FieldAsString(1)) ); if (!row.IsColumnEmpty(2)) { string[] addresses = ((string)row[2]).Split(','); if (addresses.Length == 1) { switch(addresses[0]) { case "*": firewallException.Add(new XAttribute("Scope", "any")); break; case "LocalSubnet": firewallException.Add(new XAttribute("Scope", "localSubnet")); break; case "dns": firewallException.Add(new XAttribute("Scope", "DNS")); break; case "dhcp": firewallException.Add(new XAttribute("Scope", "DHCP")); break; case "wins": firewallException.Add(new XAttribute("Scope", "WINS")); break; case "DefaultGateway": firewallException.Add(new XAttribute("Scope", "defaultGateway")); break; default: if (this.ParseHelper.ContainsProperty(addresses[0])) { firewallException.Add(new XAttribute("Scope", addresses[0])); } else { FirewallDecompiler.AddRemoteAddress(firewallException, addresses[0]); } break; } } else { foreach (var address in addresses) { FirewallDecompiler.AddRemoteAddress(firewallException, address); } } } if (!row.IsColumnEmpty(3)) { firewallException.Add(new XAttribute("Port", row.FieldAsString(3))); } if (!row.IsColumnEmpty(4)) { switch (row.FieldAsString(4)) { case FirewallConstants.IntegerNotSetString: break; case "6": firewallException.Add(new XAttribute("Protocol", "tcp")); break; case "17": firewallException.Add(new XAttribute("Protocol", "udp")); break; default: firewallException.Add(new XAttribute("Protocol", row.FieldAsString(4))); break; } } if (!row.IsColumnEmpty(5)) { firewallException.Add(new XAttribute("Program", row.FieldAsString(5))); } if (!row.IsColumnEmpty(6)) { var attr = row.FieldAsInteger(6); if ((attr & 0x1) == 0x1) { AttributeIfNotNull("IgnoreFailure", true); } if ((attr & 0x2) == 0x2) { firewallException.Add(new XAttribute("OnUpdate", "doNothing")); } else if ((attr & 0x4) == 0x4) { firewallException.Add(new XAttribute("OnUpdate", "enableOnly")); } } if (!row.IsColumnEmpty(7)) { switch (row.FieldAsString(7)) { case FirewallConstants.IntegerNotSetString: break; case "1": firewallException.Add(new XAttribute("Profile", "domain")); break; case "2": firewallException.Add(new XAttribute("Profile", "private")); break; case "4": firewallException.Add(new XAttribute("Profile", "public")); break; case "2147483647": firewallException.Add(new XAttribute("Profile", "all")); break; default: firewallException.Add(new XAttribute("Profile", row.FieldAsString(7))); break; } } if (!row.IsColumnEmpty(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: break; case FirewallConstants.NET_FW_RULE_DIR_OUT: firewallException.Add(AttributeIfNotNull("Outbound", true)); break; } } // Introduced in 5.0.0 if (row.Fields.Length > 11) { if (!row.IsColumnEmpty(11)) { var action = row.FieldAsString(11); switch (action) { case FirewallConstants.IntegerNotSetString: break; case "1": firewallException.Add(new XAttribute("Action", "allow")); break; case "0": firewallException.Add(new XAttribute("Action", "block")); break; default: firewallException.Add(new XAttribute("Action", action)); break; } } if (!row.IsColumnEmpty(12)) { var edgeTraversal = row.FieldAsString(12); switch (edgeTraversal) { case FirewallConstants.IntegerNotSetString: break; case "0": firewallException.Add(new XAttribute("EdgeTraversal", "deny")); break; case "1": firewallException.Add(new XAttribute("EdgeTraversal", "allow")); break; case "2": firewallException.Add(new XAttribute("EdgeTraversal", "deferToApp")); break; case "3": firewallException.Add(new XAttribute("EdgeTraversal", "deferToUser")); break; default: firewallException.Add(new XAttribute("EdgeTraversal", edgeTraversal)); break; } } if (!row.IsColumnEmpty(13)) { var enabled = row.FieldAsString(13); switch (enabled) { case FirewallConstants.IntegerNotSetString: break; case "1": firewallException.Add(new XAttribute("Enabled", "yes")); break; case "0": firewallException.Add(new XAttribute("Enabled", "no")); break; default: firewallException.Add(new XAttribute("Enabled", enabled)); break; } } if (!row.IsColumnEmpty(14)) { firewallException.Add(new XAttribute("Grouping", row.FieldAsString(14))); } if (!row.IsColumnEmpty(15)) { firewallException.Add(new XAttribute("IcmpTypesAndCodes", row.FieldAsString(15))); } if (!row.IsColumnEmpty(16)) { string[] interfaces = row.FieldAsString(16).Split(new[] { FirewallConstants.FORBIDDEN_FIREWALL_CHAR }, StringSplitOptions.RemoveEmptyEntries); if (interfaces.Length == 1) { firewallException.Add(new XAttribute("Interface", interfaces[0].ToCamelCase())); } else { foreach (var interfaceItem in interfaces) { FirewallDecompiler.AddInterface(firewallException, interfaceItem.ToCamelCase()); } } } if (!row.IsColumnEmpty(17)) { string[] interfaceTypes = row.FieldAsString(17).Split(','); if (interfaceTypes.Length == 1) { firewallException.Add(new XAttribute("InterfaceType", interfaceTypes[0].ToCamelCase())); } else { foreach (var interfaceType in interfaceTypes) { FirewallDecompiler.AddInterfaceType(firewallException, interfaceType.ToCamelCase()); } } } if (!row.IsColumnEmpty(18)) { string[] addresses = row.FieldAsString(18).Split(','); if (addresses.Length == 1) { switch (addresses[0]) { case "*": firewallException.Add(new XAttribute("LocalScope", "any")); break; case "LocalSubnet": firewallException.Add(new XAttribute("LocalScope", "localSubnet")); break; case "dns": firewallException.Add(new XAttribute("LocalScope", "DNS")); break; case "dhcp": firewallException.Add(new XAttribute("LocalScope", "DHCP")); break; case "wins": firewallException.Add(new XAttribute("LocalScope", "WINS")); break; case "DefaultGateway": firewallException.Add(new XAttribute("LocalScope", "defaultGateway")); break; default: if (this.ParseHelper.ContainsProperty(addresses[0])) { firewallException.Add(new XAttribute("LocalScope", addresses[0])); } else { FirewallDecompiler.AddLocalAddress(firewallException, addresses[0]); } break; } } else { foreach (var address in addresses) { FirewallDecompiler.AddLocalAddress(firewallException, address); } } } if (!row.IsColumnEmpty(19)) { firewallException.Add(new XAttribute("RemotePort", row.FieldAsString(19))); } if (!row.IsColumnEmpty(20)) { firewallException.Add(new XAttribute("Service", row.FieldAsString(20))); } if (!row.IsColumnEmpty(21)) { firewallException.Add(new XAttribute("LocalAppPackageId", row.FieldAsString(21))); } if (!row.IsColumnEmpty(22)) { firewallException.Add(new XAttribute("LocalUserAuthorizedList", row.FieldAsString(22))); } if (!row.IsColumnEmpty(23)) { firewallException.Add(new XAttribute("LocalUserOwner", row.FieldAsString(23))); } if (!row.IsColumnEmpty(24)) { firewallException.Add(new XAttribute("RemoteMachineAuthorizedList", row.FieldAsString(24))); } if (!row.IsColumnEmpty(25)) { firewallException.Add(new XAttribute("RemoteUserAuthorizedList", row.FieldAsString(25))); } if (!row.IsColumnEmpty(26)) { var secureFlags = row.FieldAsString(26); switch (secureFlags) { case FirewallConstants.IntegerNotSetString: break; case "0": firewallException.Add(new XAttribute("IPSecSecureFlags", "none")); break; case "1": firewallException.Add(new XAttribute("IPSecSecureFlags", "noEncapsulation")); break; case "2": firewallException.Add(new XAttribute("IPSecSecureFlags", "withIntegrity")); break; case "3": firewallException.Add(new XAttribute("IPSecSecureFlags", "negotiateEncryption")); break; case "4": firewallException.Add(new XAttribute("IPSecSecureFlags", "encrypt")); break; default: firewallException.Add(new XAttribute("IPSecSecureFlags", secureFlags)); break; } } } this.DecompilerHelper.IndexElement(row, firewallException); } } private static void AddRemoteAddress(XElement firewallException, string address) { var remoteAddress = new XElement(FirewallConstants.RemoteAddressName, new XAttribute("Value", address) ); firewallException.Add(remoteAddress); } private static void AddInterfaceType(XElement firewallException, string type) { var interfaceType = new XElement(FirewallConstants.InterfaceTypeName, new XAttribute("Value", type) ); firewallException.Add(interfaceType); } private static void AddLocalAddress(XElement firewallException, string address) { var localAddress = new XElement(FirewallConstants.LocalAddressName, new XAttribute("Value", address) ); firewallException.Add(localAddress); } private static void AddInterface(XElement firewallException, string value) { var interfaceName = new XElement(FirewallConstants.InterfaceName, new XAttribute("Name", value) ); firewallException.Add(interfaceName); } 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) { if (tables.TryGetTable("Wix5FirewallException", 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")); } } } } } internal static class StringExtensions { public static string ToCamelCase(this string str) { if (String.IsNullOrEmpty(str)) { return str; } var camelCase = str[0].ToString().ToLowerInvariant() + str.Substring(1); return camelCase; } } }