From dfb7512b85536b7726080648f2228cf8d0153724 Mon Sep 17 00:00:00 2001 From: chris_bednarski Date: Thu, 21 Sep 2023 16:03:28 +1000 Subject: [PATCH] add firewall extension decompiler, make msi modifications work, add all attributes --- src/ext/Firewall/ca/firewall.cpp | 980 ++++++++++++++---- .../FirewallExtensionFixture.cs | 485 ++++++++- .../UsingFirewall/PackageComponents.wxs | 39 +- .../UsingProperties/Package.en-us.wxl | 9 + .../TestData/UsingProperties/Package.wxs | 15 + .../UsingProperties/PackageComponents.wxs | 53 + .../TestData/UsingProperties/example.txt | 1 + .../WixToolsetTest.Firewall.csproj | 2 + src/ext/Firewall/wixext/FirewallCompiler.cs | 623 +++++++++-- src/ext/Firewall/wixext/FirewallConstants.cs | 17 + src/ext/Firewall/wixext/FirewallDecompiler.cs | 317 +++++- src/ext/Firewall/wixext/FirewallErrors.cs | 29 +- .../wixext/FirewallTableDefinitions.cs | 28 +- .../Symbols/WixFirewallExceptionSymbol.cs | 142 ++- .../wixext/WixToolset.Firewall.wixext.csproj | 4 +- src/ext/Util/wixext/UtilCompiler.cs | 13 +- .../Directory.Packages.props.pp | 1 + .../burn/WixTestTools/Firewall/RuleDetails.cs | 8 +- .../CrossVersionMerge.wixproj | 13 + .../CrossVersionMerge/Module401.msm | Bin 0 -> 192512 bytes .../CrossVersionMerge/package.wxs | 35 + .../FirewallRulesInterfaces.wixproj | 13 + .../FirewallRulesInterfaces/product.wxs | 32 + .../FirewallRulesProperties.wixproj | 13 + .../FirewallRulesProperties/product.wxs | 57 + .../Module401/Module401.wixproj | 10 + .../Module401/data/test.txt | 1 + .../Module401/module.wxs | 36 + .../ModuleCurrent/ModuleCurrent.wixproj | 10 + .../ModuleCurrent/data/test.txt | 1 + .../ModuleCurrent/module.wxs | 36 + .../NestedService/NestedService.wixproj | 14 + .../NestedService/product.wxs | 25 + .../FirewallExtensionTests.cs | 640 +++++++++++- .../WixToolsetTest.MsiE2E.csproj | 1 + 35 files changed, 3356 insertions(+), 347 deletions(-) create mode 100644 src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/Package.en-us.wxl create mode 100644 src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/Package.wxs create mode 100644 src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/PackageComponents.wxs create mode 100644 src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/example.txt create mode 100644 src/test/msi/TestData/FirewallExtensionTests/CrossVersionMerge/CrossVersionMerge.wixproj create mode 100644 src/test/msi/TestData/FirewallExtensionTests/CrossVersionMerge/Module401.msm create mode 100644 src/test/msi/TestData/FirewallExtensionTests/CrossVersionMerge/package.wxs create mode 100644 src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/FirewallRulesInterfaces.wixproj create mode 100644 src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/product.wxs create mode 100644 src/test/msi/TestData/FirewallExtensionTests/FirewallRulesProperties/FirewallRulesProperties.wixproj create mode 100644 src/test/msi/TestData/FirewallExtensionTests/FirewallRulesProperties/product.wxs create mode 100644 src/test/msi/TestData/FirewallExtensionTests/Module401/Module401.wixproj create mode 100644 src/test/msi/TestData/FirewallExtensionTests/Module401/data/test.txt create mode 100644 src/test/msi/TestData/FirewallExtensionTests/Module401/module.wxs create mode 100644 src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/ModuleCurrent.wixproj create mode 100644 src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/data/test.txt create mode 100644 src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/module.wxs create mode 100644 src/test/msi/TestData/FirewallExtensionTests/NestedService/NestedService.wixproj create mode 100644 src/test/msi/TestData/FirewallExtensionTests/NestedService/product.wxs diff --git a/src/ext/Firewall/ca/firewall.cpp b/src/ext/Firewall/ca/firewall.cpp index eed6f9df9..f50ae4099 100644 --- a/src/ext/Firewall/ca/firewall.cpp +++ b/src/ext/Firewall/ca/firewall.cpp @@ -3,34 +3,54 @@ #include "precomp.h" LPCWSTR vcsFirewallExceptionQuery = - L"SELECT `Name`, `RemoteAddresses`, `Port`, `Protocol`, `Program`, `Attributes`, `Profile`, `Component_`, `Description`, `Direction` FROM `Wix5FirewallException`"; -enum eFirewallExceptionQuery { feqName = 1, feqRemoteAddresses, feqPort, feqProtocol, feqProgram, feqAttributes, feqProfile, feqComponent, feqDescription, feqDirection }; -enum eFirewallExceptionTarget { fetPort = 1, fetApplication, fetUnknown }; -enum eFirewallExceptionAttributes { feaIgnoreFailures = 1 }; +L"SELECT `Name`, `RemoteAddresses`, `Port`, `Protocol`, `Program`, `Attributes`, `Profile`, `Component_`, `Description`, `Direction`, `Action`, `EdgeTraversal`, `Enabled`, `Grouping`, `IcmpTypesAndCodes`, `Interfaces`, `InterfaceTypes`, `LocalAddresses`, `RemotePort`, `ServiceName`, `LocalAppPackageId`, `LocalUserAuthorizedList`, `LocalUserOwner`, `RemoteMachineAuthorizedList`, `RemoteUserAuthorizedList`, `SecureFlags` FROM `Wix5FirewallException`"; +enum eFirewallExceptionQuery { feqName = 1, feqRemoteAddresses, feqPort, feqProtocol, feqProgram, feqAttributes, feqProfile, feqComponent, feqDescription, feqDirection, feqAction, feqEdgeTraversal, feqEnabled, feqGrouping, feqIcmpTypesAndCodes, feqInterfaces, feqInterfaceTypes, feqLocalAddresses, feqRemotePort, feqServiceName, feqLocalAppPackageId, feqLocalUserAuthorizedList, feqLocalUserOwner, feqRemoteMachineAuthorizedList, feqRemoteUserAuthorizedList, feqSecureFlags }; +enum eFirewallExceptionAttributes { feaIgnoreFailures = 1, feaIgnoreUpdates = 2, feaEnableOnUpdate = 4, feaAddINetFwRule2 = 8, feaAddINetFwRule3 = 16 }; struct FIREWALL_EXCEPTION_ATTRIBUTES { LPWSTR pwzName; - - LPWSTR pwzRemoteAddresses; - LPWSTR pwzPort; - int iProtocol; - LPWSTR pwzProgram; int iAttributes; - int iProfile; + + // INetFwRule + int iAction; + LPWSTR pwzApplicationName; LPWSTR pwzDescription; int iDirection; + int iEnabled; + LPWSTR pwzGrouping; + LPWSTR pwzIcmpTypesAndCodes; + LPWSTR pwzInterfaces; + LPWSTR pwzInterfaceTypes; + LPWSTR pwzLocalAddresses; + LPWSTR pwzLocalPorts; + int iProfile; + int iProtocol; + LPWSTR pwzRemoteAddresses; + LPWSTR pwzRemotePorts; + LPWSTR pwzServiceName; + + // INetFwRule2 + int iEdgeTraversal; + + // INetFwRule3 + LPWSTR pwzLocalAppPackageId; + LPWSTR pwzLocalUserAuthorizedList; + LPWSTR pwzLocalUserOwner; + LPWSTR pwzRemoteMachineAuthorizedList; + LPWSTR pwzRemoteUserAuthorizedList; + int iSecureFlags; }; /****************************************************************** - SchedFirewallExceptions - immediate custom action worker to + SchedFirewallExceptions - immediate custom action worker to register and remove firewall exceptions. ********************************************************************/ static UINT SchedFirewallExceptions( __in MSIHANDLE hInstall, - WCA_TODO todoSched - ) + __in WCA_TODO todoSched +) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; @@ -67,19 +87,19 @@ static UINT SchedFirewallExceptions( hr = WcaGetRecordFormattedString(hRec, feqRemoteAddresses, &attrs.pwzRemoteAddresses); ExitOnFailure(hr, "Failed to get firewall exception remote addresses."); - hr = WcaGetRecordFormattedString(hRec, feqPort, &attrs.pwzPort); + hr = WcaGetRecordFormattedString(hRec, feqPort, &attrs.pwzLocalPorts); ExitOnFailure(hr, "Failed to get firewall exception port."); - hr = WcaGetRecordInteger(hRec, feqProtocol, &attrs.iProtocol); + hr = WcaGetRecordFormattedInteger(hRec, feqProtocol, &attrs.iProtocol); ExitOnFailure(hr, "Failed to get firewall exception protocol."); - hr = WcaGetRecordFormattedString(hRec, feqProgram, &attrs.pwzProgram); + hr = WcaGetRecordFormattedString(hRec, feqProgram, &attrs.pwzApplicationName); ExitOnFailure(hr, "Failed to get firewall exception program."); hr = WcaGetRecordInteger(hRec, feqAttributes, &attrs.iAttributes); ExitOnFailure(hr, "Failed to get firewall exception attributes."); - - hr = WcaGetRecordInteger(hRec, feqProfile, &attrs.iProfile); + + hr = WcaGetRecordFormattedInteger(hRec, feqProfile, &attrs.iProfile); ExitOnFailure(hr, "Failed to get firewall exception profile."); hr = WcaGetRecordString(hRec, feqComponent, &pwzComponent); @@ -91,6 +111,54 @@ static UINT SchedFirewallExceptions( hr = WcaGetRecordInteger(hRec, feqDirection, &attrs.iDirection); ExitOnFailure(hr, "Failed to get firewall exception direction."); + hr = WcaGetRecordFormattedInteger(hRec, feqAction, &attrs.iAction); + ExitOnFailure(hr, "Failed to get firewall exception action."); + + hr = WcaGetRecordFormattedInteger(hRec, feqEdgeTraversal, &attrs.iEdgeTraversal); + ExitOnFailure(hr, "Failed to get firewall exception edge traversal."); + + hr = WcaGetRecordFormattedInteger(hRec, feqEnabled, &attrs.iEnabled); + ExitOnFailure(hr, "Failed to get firewall exception enabled flag."); + + hr = WcaGetRecordFormattedString(hRec, feqGrouping, &attrs.pwzGrouping); + ExitOnFailure(hr, "Failed to get firewall exception grouping."); + + hr = WcaGetRecordFormattedString(hRec, feqIcmpTypesAndCodes, &attrs.pwzIcmpTypesAndCodes); + ExitOnFailure(hr, "Failed to get firewall exception ICMP types and codes."); + + hr = WcaGetRecordFormattedString(hRec, feqInterfaces, &attrs.pwzInterfaces); + ExitOnFailure(hr, "Failed to get firewall exception interfaces."); + + hr = WcaGetRecordFormattedString(hRec, feqInterfaceTypes, &attrs.pwzInterfaceTypes); + ExitOnFailure(hr, "Failed to get firewall exception interface types."); + + hr = WcaGetRecordFormattedString(hRec, feqLocalAddresses, &attrs.pwzLocalAddresses); + ExitOnFailure(hr, "Failed to get firewall exception local addresses."); + + hr = WcaGetRecordFormattedString(hRec, feqRemotePort, &attrs.pwzRemotePorts); + ExitOnFailure(hr, "Failed to get firewall exception remote port."); + + hr = WcaGetRecordFormattedString(hRec, feqServiceName, &attrs.pwzServiceName); + ExitOnFailure(hr, "Failed to get firewall exception service name."); + + hr = WcaGetRecordFormattedString(hRec, feqLocalAppPackageId, &attrs.pwzLocalAppPackageId); + ExitOnFailure(hr, "Failed to get firewall exception local app package id."); + + hr = WcaGetRecordFormattedString(hRec, feqLocalUserAuthorizedList, &attrs.pwzLocalUserAuthorizedList); + ExitOnFailure(hr, "Failed to get firewall exception local user authorized list."); + + hr = WcaGetRecordFormattedString(hRec, feqLocalUserOwner, &attrs.pwzLocalUserOwner); + ExitOnFailure(hr, "Failed to get firewall exception local user owner."); + + hr = WcaGetRecordFormattedString(hRec, feqRemoteMachineAuthorizedList, &attrs.pwzRemoteMachineAuthorizedList); + ExitOnFailure(hr, "Failed to get firewall exception remote machine authorized list."); + + hr = WcaGetRecordFormattedString(hRec, feqRemoteUserAuthorizedList, &attrs.pwzRemoteUserAuthorizedList); + ExitOnFailure(hr, "Failed to get firewall exception remote user authorized list."); + + hr = WcaGetRecordFormattedInteger(hRec, feqSecureFlags, &attrs.iSecureFlags); + ExitOnFailure(hr, "Failed to get firewall exception secure flag."); + // figure out what we're doing for this exception, treating reinstall the same as install WCA_TODO todoComponent = WcaGetComponentToDo(pwzComponent); if ((WCA_TODO_REINSTALL == todoComponent ? WCA_TODO_INSTALL : todoComponent) != todoSched) @@ -99,7 +167,6 @@ static UINT SchedFirewallExceptions( continue; } - // action :: name :: profile :: remoteaddresses :: attributes :: target :: {port::protocol | path} ++cFirewallExceptions; hr = WcaWriteIntegerToCaData(todoComponent, &pwzCustomActionData); ExitOnFailure(hr, "failed to write exception action to custom action data"); @@ -116,40 +183,75 @@ static UINT SchedFirewallExceptions( hr = WcaWriteIntegerToCaData(attrs.iAttributes, &pwzCustomActionData); ExitOnFailure(hr, "failed to write exception attributes to custom action data"); - if (*attrs.pwzProgram) - { - // If program is defined, we have an application exception. - hr = WcaWriteIntegerToCaData(fetApplication, &pwzCustomActionData); - ExitOnFailure(hr, "failed to write exception target (application) to custom action data"); - - hr = WcaWriteStringToCaData(attrs.pwzProgram, &pwzCustomActionData); - ExitOnFailure(hr, "failed to write application path to custom action data"); - } - else - { - // we have a port-only exception - hr = WcaWriteIntegerToCaData(fetPort, &pwzCustomActionData); - ExitOnFailure(hr, "failed to write exception target (port) to custom action data"); - } - - hr = WcaWriteStringToCaData(attrs.pwzPort, &pwzCustomActionData); + hr = WcaWriteStringToCaData(attrs.pwzApplicationName, &pwzCustomActionData); ExitOnFailure(hr, "failed to write application path to custom action data"); + hr = WcaWriteStringToCaData(attrs.pwzLocalPorts, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write local ports to custom action data"); + hr = WcaWriteIntegerToCaData(attrs.iProtocol, &pwzCustomActionData); ExitOnFailure(hr, "failed to write exception protocol to custom action data"); hr = WcaWriteStringToCaData(attrs.pwzDescription, &pwzCustomActionData); - ExitOnFailure(hr, "failed to write firewall rule description to custom action data"); + ExitOnFailure(hr, "failed to write firewall exception description to custom action data"); hr = WcaWriteIntegerToCaData(attrs.iDirection, &pwzCustomActionData); - ExitOnFailure(hr, "failed to write firewall rule direction to custom action data"); + ExitOnFailure(hr, "failed to write firewall exception direction to custom action data"); + + hr = WcaWriteIntegerToCaData(attrs.iAction, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write exception action to custom action data"); + + hr = WcaWriteIntegerToCaData(attrs.iEdgeTraversal, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write exception edge traversal to custom action data"); + + hr = WcaWriteIntegerToCaData(attrs.iEnabled, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write exception enabled flag to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzGrouping, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write grouping to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzIcmpTypesAndCodes, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write icmp types and codes to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzInterfaces, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write interfaces to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzInterfaceTypes, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write interface types to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzLocalAddresses, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write local addresses to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzRemotePorts, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write remote ports to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzServiceName, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write service name to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzLocalAppPackageId, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write local app package id to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzLocalUserAuthorizedList, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write local user authorized list to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzLocalUserOwner, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write local user owner to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzRemoteMachineAuthorizedList, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write remote machine authorized list to custom action data"); + + hr = WcaWriteStringToCaData(attrs.pwzRemoteUserAuthorizedList, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write remote user authorized list to custom action data"); + + hr = WcaWriteIntegerToCaData(attrs.iSecureFlags, &pwzCustomActionData); + ExitOnFailure(hr, "failed to write exception secure flags to custom action data"); } // reaching the end of the list is actually a good thing, not an error if (E_NOMOREITEMS == hr) { hr = S_OK; - } + } ExitOnFailure(hr, "failure occured while processing Wix5FirewallException table"); // schedule ExecFirewallExceptions if there's anything to do @@ -160,14 +262,14 @@ static UINT SchedFirewallExceptions( if (WCA_TODO_INSTALL == todoSched) { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION5(L"RollbackFirewallExceptionsInstall"), pwzCustomActionData, cFirewallExceptions * COST_FIREWALL_EXCEPTION); - ExitOnFailure(hr, "failed to schedule firewall install exceptions rollback"); + ExitOnFailure(hr, "failed to schedule firewall install exceptions rollback"); hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION5(L"ExecFirewallExceptionsInstall"), pwzCustomActionData, cFirewallExceptions * COST_FIREWALL_EXCEPTION); ExitOnFailure(hr, "failed to schedule firewall install exceptions execution"); } else { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION5(L"RollbackFirewallExceptionsUninstall"), pwzCustomActionData, cFirewallExceptions * COST_FIREWALL_EXCEPTION); - ExitOnFailure(hr, "failed to schedule firewall uninstall exceptions rollback"); + ExitOnFailure(hr, "failed to schedule firewall uninstall exceptions rollback"); hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION5(L"ExecFirewallExceptionsUninstall"), pwzCustomActionData, cFirewallExceptions * COST_FIREWALL_EXCEPTION); ExitOnFailure(hr, "failed to schedule firewall uninstall exceptions execution"); } @@ -180,53 +282,68 @@ static UINT SchedFirewallExceptions( LExit: ReleaseStr(attrs.pwzName); ReleaseStr(attrs.pwzRemoteAddresses); - ReleaseStr(attrs.pwzPort); - ReleaseStr(attrs.pwzProgram); + ReleaseStr(attrs.pwzLocalPorts); + ReleaseStr(attrs.pwzApplicationName); ReleaseStr(attrs.pwzDescription); + ReleaseStr(attrs.pwzGrouping); + ReleaseStr(attrs.pwzIcmpTypesAndCodes); + ReleaseStr(attrs.pwzInterfaces); + ReleaseStr(attrs.pwzInterfaceTypes); + ReleaseStr(attrs.pwzLocalAddresses); + ReleaseStr(attrs.pwzRemotePorts); + ReleaseStr(attrs.pwzServiceName); + ReleaseStr(attrs.pwzLocalAppPackageId); + ReleaseStr(attrs.pwzLocalUserAuthorizedList); + ReleaseStr(attrs.pwzLocalUserOwner); + ReleaseStr(attrs.pwzRemoteMachineAuthorizedList); + ReleaseStr(attrs.pwzRemoteUserAuthorizedList); ReleaseStr(pwzComponent); ReleaseStr(pwzCustomActionData); return WcaFinalize(er = FAILED(hr) ? ERROR_INSTALL_FAILURE : er); } -/****************************************************************** + +/******************************************************************* SchedFirewallExceptionsInstall - immediate custom action entry point to register firewall exceptions. ********************************************************************/ extern "C" UINT __stdcall SchedFirewallExceptionsInstall( __in MSIHANDLE hInstall - ) +) { return SchedFirewallExceptions(hInstall, WCA_TODO_INSTALL); } -/****************************************************************** + +/******************************************************************* SchedFirewallExceptionsUninstall - immediate custom action entry point to remove firewall exceptions. ********************************************************************/ extern "C" UINT __stdcall SchedFirewallExceptionsUninstall( __in MSIHANDLE hInstall - ) +) { return SchedFirewallExceptions(hInstall, WCA_TODO_UNINSTALL); } -/****************************************************************** + +/******************************************************************* GetFirewallRules - Get the collection of firewall rules. ********************************************************************/ static HRESULT GetFirewallRules( __in BOOL fIgnoreFailures, __out INetFwRules** ppNetFwRules - ) +) { HRESULT hr = S_OK; INetFwPolicy2* pNetFwPolicy2 = NULL; INetFwRules* pNetFwRules = NULL; *ppNetFwRules = NULL; - + do { ReleaseNullObject(pNetFwPolicy2); @@ -262,7 +379,7 @@ static HRESULT GetFirewallRules( *ppNetFwRules = pNetFwRules; pNetFwRules = NULL; - + LExit: ReleaseObject(pNetFwPolicy2); ReleaseObject(pNetFwRules); @@ -270,51 +387,380 @@ static HRESULT GetFirewallRules( return hr; } -/****************************************************************** - CreateFwRuleObject - CoCreate a firewall rule, and set the common set of properties which are shared - between port and application firewall rules + +/******************************************************************* + CreateFwRuleObject - CoCreate a firewall rule, and set the name ********************************************************************/ static HRESULT CreateFwRuleObject( __in BSTR bstrName, - __in FIREWALL_EXCEPTION_ATTRIBUTES const& attrs, __out INetFwRule** ppNetFwRule - ) +) +{ + HRESULT hr = S_OK; + INetFwRule* pNetFwRule = NULL; + *ppNetFwRule = NULL; + + hr = ::CoCreateInstance(__uuidof(NetFwRule), NULL, CLSCTX_ALL, __uuidof(INetFwRule), (LPVOID*)&pNetFwRule); + ExitOnFailure(hr, "failed to create NetFwRule object"); + + hr = pNetFwRule->put_Name(bstrName); + ExitOnFailure(hr, "failed to set firewall exception name"); + + *ppNetFwRule = pNetFwRule; + +LExit: + return hr; +} + + +/********************************************************************* + GetFwRuleInterfaces - pack firewall rule interfaces into a VARIANT. + The populated VARIANT needs to be cleaned up by the calling function. + +**********************************************************************/ +static HRESULT GetFwRuleInterfaces( + __in FIREWALL_EXCEPTION_ATTRIBUTES const& attrs, + __out VARIANT& vInterfaces +) +{ + HRESULT hr = S_OK; + BSTR bstrInterfaces = NULL; + const WCHAR FORBIDDEN_FIREWALL_CHAR = L'|'; + LONG iInterfacesCount = 0; + UINT iLength = 0; + LONG iIndex = 0; + + ::VariantInit(&vInterfaces); + ExitOnNull(attrs.pwzInterfaces, hr, S_OK, "No interfaces to pack"); + + bstrInterfaces = ::SysAllocString(attrs.pwzInterfaces); + ExitOnNull(bstrInterfaces, hr, E_OUTOFMEMORY, "failed SysAllocString for interfaces"); + + iLength = ::SysStringLen(bstrInterfaces); + + LPWSTR pwzT = bstrInterfaces; + while (*pwzT) + { + if (FORBIDDEN_FIREWALL_CHAR == *pwzT) + { + *pwzT = L'\0'; + pwzT++; + + // skip empty values inside the interfaces eg. ||| + if (*pwzT && FORBIDDEN_FIREWALL_CHAR != *pwzT) + { + iInterfacesCount++; + } + } + else + { + if (pwzT == bstrInterfaces) + { + iInterfacesCount++; + } + + pwzT++; + } + } + + ExitOnNull(iInterfacesCount, hr, S_OK, "All interfaces are empty values"); + + vInterfaces.vt = VT_ARRAY | VT_VARIANT; + // this will be cleaned up by ReleaseVariant call of the calling function + vInterfaces.parray = SafeArrayCreateVector(VT_VARIANT, 0, iInterfacesCount); + + for (LPCWSTR pwzElement = bstrInterfaces; pwzElement < (bstrInterfaces + iLength); ++pwzElement) + { + if (*pwzElement) + { + VARIANT vElement; + ::VariantInit(&vElement); + + vElement.vt = VT_BSTR; + // this will be cleaned up by ReleaseVariant call of the calling function + vElement.bstrVal = ::SysAllocString(pwzElement); + ExitOnNull(vElement.bstrVal, hr, E_OUTOFMEMORY, "failed SysAllocString for interface element"); + + hr = SafeArrayPutElement(vInterfaces.parray, &iIndex, &vElement); + ExitOnFailure(hr, "failed to put interface '%ls' into safe array", pwzElement); + + pwzElement += ::SysStringLen(vElement.bstrVal); + iIndex++; + } + } + +LExit: + ReleaseBSTR(bstrInterfaces); + + return hr; +} + +/****************************************************************************** + UpdateFwRule2Object - update properties for a firewall INetFwRule2 interface. + Requires Windows 7 / 2008 R2 + + ******************************************************************************/ +static HRESULT UpdateFwRule2Object( + __in INetFwRule* pNetFwRule, + __in BOOL fUpdateRule, + __in FIREWALL_EXCEPTION_ATTRIBUTES const& attrs +) { HRESULT hr = S_OK; + INetFwRule2* pNetFwRule2 = NULL; + + hr = pNetFwRule->QueryInterface(__uuidof(INetFwRule2), (LPVOID*)&pNetFwRule2); + ExitOnFailure(hr, "failed to query INetFwRule2 interface"); + + if (MSI_NULL_INTEGER != attrs.iEdgeTraversal) + { + hr = pNetFwRule2->put_EdgeTraversalOptions(attrs.iEdgeTraversal); + ExitOnFailure(hr, "failed to set exception edge traversal option"); + } + else if (fUpdateRule) + { + hr = pNetFwRule2->put_EdgeTraversalOptions(NET_FW_EDGE_TRAVERSAL_TYPE_DENY); + ExitOnFailure(hr, "failed to remove exception edge traversal option"); + } + +LExit: + ReleaseObject(pNetFwRule2); + + return hr; +} + + +/****************************************************************************** + UpdateFwRule3Object - update properties for a firewall INetFwRule3 interface. + Requires Windows 8 / 2012 + + ******************************************************************************/ +static HRESULT UpdateFwRule3Object( + __in INetFwRule* pNetFwRule, + __in BOOL fUpdateRule, + __in FIREWALL_EXCEPTION_ATTRIBUTES const& attrs +) +{ + HRESULT hr = S_OK; + + BSTR bstrLocalAppPackageId = NULL; + BSTR bstrLocalUserAuthorizedList = NULL; + BSTR bstrLocalUserOwner = NULL; + BSTR bstrRemoteMachineAuthorizedList = NULL; + BSTR bstrRemoteUserAuthorizedList = NULL; + INetFwRule3* pNetFwRule3 = NULL; + + bstrLocalAppPackageId = ::SysAllocString(attrs.pwzLocalAppPackageId); + ExitOnNull(bstrLocalAppPackageId, hr, E_OUTOFMEMORY, "failed SysAllocString for local app package id"); + bstrLocalUserAuthorizedList = ::SysAllocString(attrs.pwzLocalUserAuthorizedList); + ExitOnNull(bstrLocalUserAuthorizedList, hr, E_OUTOFMEMORY, "failed SysAllocString for local user authorized list"); + bstrLocalUserOwner = ::SysAllocString(attrs.pwzLocalUserOwner); + ExitOnNull(bstrLocalUserOwner, hr, E_OUTOFMEMORY, "failed SysAllocString for local user owner"); + bstrRemoteMachineAuthorizedList = ::SysAllocString(attrs.pwzRemoteMachineAuthorizedList); + ExitOnNull(bstrRemoteMachineAuthorizedList, hr, E_OUTOFMEMORY, "failed SysAllocString for remote machine authorized list"); + bstrRemoteUserAuthorizedList = ::SysAllocString(attrs.pwzRemoteUserAuthorizedList); + ExitOnNull(bstrRemoteUserAuthorizedList, hr, E_OUTOFMEMORY, "failed SysAllocString for remote user authorized list"); + + hr = pNetFwRule->QueryInterface(__uuidof(INetFwRule3), (LPVOID*)&pNetFwRule3); + ExitOnFailure(hr, "failed to query INetFwRule3 interface"); + + if (bstrLocalAppPackageId && *bstrLocalAppPackageId) + { + hr = pNetFwRule3->put_LocalAppPackageId(bstrLocalAppPackageId); + ExitOnFailure(hr, "failed to set exception local app package id"); + } + else if (fUpdateRule) + { + hr = pNetFwRule3->put_LocalAppPackageId(NULL); + ExitOnFailure(hr, "failed to remove exception local app package id"); + } + + if (bstrLocalUserAuthorizedList && *bstrLocalUserAuthorizedList) + { + hr = pNetFwRule3->put_LocalUserAuthorizedList(bstrLocalUserAuthorizedList); + ExitOnFailure(hr, "failed to set exception local user authorized list"); + } + else if (fUpdateRule) + { + hr = pNetFwRule3->put_LocalUserAuthorizedList(NULL); + ExitOnFailure(hr, "failed to remove exception local user authorized list"); + } + + if (bstrLocalUserOwner && *bstrLocalUserOwner) + { + hr = pNetFwRule3->put_LocalUserOwner(bstrLocalUserOwner); + ExitOnFailure(hr, "failed to set exception local user owner"); + } + else if (fUpdateRule) + { + hr = pNetFwRule3->put_LocalUserOwner(NULL); + ExitOnFailure(hr, "failed to remove exception local user owner"); + } + + if (bstrRemoteMachineAuthorizedList && *bstrRemoteMachineAuthorizedList) + { + hr = pNetFwRule3->put_RemoteMachineAuthorizedList(bstrRemoteMachineAuthorizedList); + ExitOnFailure(hr, "failed to set exception remote machine authorized list"); + } + else if (fUpdateRule) + { + hr = pNetFwRule3->put_RemoteMachineAuthorizedList(NULL); + ExitOnFailure(hr, "failed to remove exception remote machine authorized list"); + } + + if (bstrRemoteUserAuthorizedList && *bstrRemoteUserAuthorizedList) + { + hr = pNetFwRule3->put_RemoteUserAuthorizedList(bstrRemoteUserAuthorizedList); + ExitOnFailure(hr, "failed to set exception remote user authorized list"); + } + else if (fUpdateRule) + { + hr = pNetFwRule3->put_RemoteUserAuthorizedList(NULL); + ExitOnFailure(hr, "failed to remove exception remote user authorized list"); + } + + if (MSI_NULL_INTEGER != attrs.iSecureFlags) + { + hr = pNetFwRule3->put_SecureFlags(attrs.iSecureFlags); + ExitOnFailure(hr, "failed to set exception IPsec secure flags"); + } + else if (fUpdateRule) + { + hr = pNetFwRule3->put_SecureFlags(NET_FW_AUTHENTICATE_NONE); + ExitOnFailure(hr, "failed to reset exception IPsec secure flags"); + } + +LExit: + ReleaseBSTR(bstrLocalAppPackageId); + ReleaseBSTR(bstrLocalUserAuthorizedList); + ReleaseBSTR(bstrLocalUserOwner); + ReleaseBSTR(bstrRemoteMachineAuthorizedList); + ReleaseBSTR(bstrRemoteUserAuthorizedList); + ReleaseObject(pNetFwRule3); + + return hr; +} + + +/********************************************************************** + UpdateFwRuleObject - update all properties for a basic firewall rule. + Requires Windows Vista / 2008 + + **********************************************************************/ +static HRESULT UpdateFwRuleObject( + __in INetFwRule* pNetFwRule, + __in BOOL fUpdateRule, + __in FIREWALL_EXCEPTION_ATTRIBUTES const& attrs +) +{ + HRESULT hr = S_OK; + BSTR bstrEmpty = NULL; BSTR bstrRemoteAddresses = NULL; + BSTR bstrFile = NULL; BSTR bstrPort = NULL; BSTR bstrDescription = NULL; - INetFwRule* pNetFwRule = NULL; - *ppNetFwRule = NULL; + BSTR bstrGrouping = NULL; + BSTR bstrIcmpTypesAndCodes = NULL; + BSTR bstrInterfaceTypes = NULL; + BSTR bstrLocalAddresses = NULL; + BSTR bstrRemotePort = NULL; + BSTR bstrServiceName = NULL; + VARIANT vInterfaces; + ::VariantInit(&vInterfaces); + LONG iProtocol = 0; + + INetFwRule2* pNetFwRule2 = NULL; // convert to BSTRs to make COM happy + bstrEmpty = ::SysAllocString(L""); + ExitOnNull(bstrEmpty, hr, E_OUTOFMEMORY, "failed SysAllocString for empty placeholder"); + bstrRemoteAddresses = ::SysAllocString(attrs.pwzRemoteAddresses); ExitOnNull(bstrRemoteAddresses, hr, E_OUTOFMEMORY, "failed SysAllocString for remote addresses"); - bstrPort = ::SysAllocString(attrs.pwzPort); + bstrFile = ::SysAllocString(attrs.pwzApplicationName); + ExitOnNull(bstrFile, hr, E_OUTOFMEMORY, "failed SysAllocString for application name"); + bstrPort = ::SysAllocString(attrs.pwzLocalPorts); ExitOnNull(bstrPort, hr, E_OUTOFMEMORY, "failed SysAllocString for port"); bstrDescription = ::SysAllocString(attrs.pwzDescription); ExitOnNull(bstrDescription, hr, E_OUTOFMEMORY, "failed SysAllocString for description"); + bstrGrouping = ::SysAllocString(attrs.pwzGrouping); + ExitOnNull(bstrGrouping, hr, E_OUTOFMEMORY, "failed SysAllocString for grouping"); + bstrIcmpTypesAndCodes = ::SysAllocString(attrs.pwzIcmpTypesAndCodes); + ExitOnNull(bstrIcmpTypesAndCodes, hr, E_OUTOFMEMORY, "failed SysAllocString for icmp types and codes"); + bstrInterfaceTypes = ::SysAllocString(attrs.pwzInterfaceTypes); + ExitOnNull(bstrInterfaceTypes, hr, E_OUTOFMEMORY, "failed SysAllocString for interface types"); + bstrLocalAddresses = ::SysAllocString(attrs.pwzLocalAddresses); + ExitOnNull(bstrLocalAddresses, hr, E_OUTOFMEMORY, "failed SysAllocString for local addresses"); + bstrRemotePort = ::SysAllocString(attrs.pwzRemotePorts); + ExitOnNull(bstrRemotePort, hr, E_OUTOFMEMORY, "failed SysAllocString for remote port"); + bstrServiceName = ::SysAllocString(attrs.pwzServiceName); + ExitOnNull(bstrServiceName, hr, E_OUTOFMEMORY, "failed SysAllocString for service name"); + + if (fUpdateRule) + { + hr = pNetFwRule->get_Protocol(&iProtocol); + ExitOnFailure(hr, "failed to get exception protocol"); - hr = ::CoCreateInstance(__uuidof(NetFwRule), NULL, CLSCTX_ALL, __uuidof(INetFwRule), (void**)&pNetFwRule); - ExitOnFailure(hr, "failed to create NetFwRule object"); + // If you are editing a TCP port rule and converting it into an ICMP rule, + // first delete the ports, change protocol from TCP to ICMP, and then add the ports. - hr = pNetFwRule->put_Name(bstrName); - ExitOnFailure(hr, "failed to set exception name"); + switch (iProtocol) + { + case NET_FW_IP_PROTOCOL_ANY: + break; - hr = pNetFwRule->put_Profiles(static_cast(attrs.iProfile)); - ExitOnFailure(hr, "failed to set exception profile"); + case 1: // ICMP + hr = pNetFwRule->put_IcmpTypesAndCodes(NULL); + ExitOnFailure(hr, "failed to remove exception icmp types and codes"); + // fall through and reset ports too + default: + hr = pNetFwRule->put_LocalPorts(NULL); + ExitOnFailure(hr, "failed to update exception local ports to NULL"); + + hr = pNetFwRule->put_RemotePorts(NULL); + ExitOnFailure(hr, "failed to update exception remote ports to NULL"); + break; + } + } + + if (MSI_NULL_INTEGER != attrs.iProfile) + { + hr = pNetFwRule->put_Profiles(static_cast (attrs.iProfile)); + ExitOnFailure(hr, "failed to set exception profile"); + } + else if (fUpdateRule) + { + hr = pNetFwRule->put_Profiles(NET_FW_PROFILE2_ALL); + ExitOnFailure(hr, "failed to reset exception profile to all"); + } + + // The Protocol property must be set before the LocalPorts/RemotePorts properties or an error will be returned. if (MSI_NULL_INTEGER != attrs.iProtocol) { - hr = pNetFwRule->put_Protocol(static_cast(attrs.iProtocol)); + hr = pNetFwRule->put_Protocol(static_cast (attrs.iProtocol)); ExitOnFailure(hr, "failed to set exception protocol"); } + else if (fUpdateRule) + { + if ((bstrPort && *bstrPort) || (bstrRemotePort && *bstrRemotePort)) + { + // default protocol is "TCP" in the WiX firewall compiler if a port is specified + hr = pNetFwRule->put_Protocol(NET_FW_IP_PROTOCOL_TCP); + ExitOnFailure(hr, "failed to reset exception protocol to TCP"); + } + else + { + hr = pNetFwRule->put_Protocol(NET_FW_IP_PROTOCOL_ANY); + ExitOnFailure(hr, "failed to reset exception protocol to ANY"); + } + } if (bstrPort && *bstrPort) { hr = pNetFwRule->put_LocalPorts(bstrPort); - ExitOnFailure(hr, "failed to set exception port"); + ExitOnFailure(hr, "failed to set exception local ports '%ls'", bstrPort); } if (bstrRemoteAddresses && *bstrRemoteAddresses) @@ -322,122 +768,195 @@ static HRESULT CreateFwRuleObject( hr = pNetFwRule->put_RemoteAddresses(bstrRemoteAddresses); ExitOnFailure(hr, "failed to set exception remote addresses '%ls'", bstrRemoteAddresses); } + else if (fUpdateRule) + { + hr = pNetFwRule->put_RemoteAddresses(bstrEmpty); + ExitOnFailure(hr, "failed to remove exception remote addresses"); + } if (bstrDescription && *bstrDescription) { hr = pNetFwRule->put_Description(bstrDescription); ExitOnFailure(hr, "failed to set exception description '%ls'", bstrDescription); } + else if (fUpdateRule) + { + hr = pNetFwRule->put_Description(bstrEmpty); + ExitOnFailure(hr, "failed to remove exception description"); + } if (MSI_NULL_INTEGER != attrs.iDirection) { hr = pNetFwRule->put_Direction(static_cast (attrs.iDirection)); ExitOnFailure(hr, "failed to set exception direction"); } + else if (fUpdateRule) + { + hr = pNetFwRule->put_Direction(NET_FW_RULE_DIR_IN); + ExitOnFailure(hr, "failed to reset exception direction to in"); + } - *ppNetFwRule = pNetFwRule; - pNetFwRule = NULL; + if (MSI_NULL_INTEGER != attrs.iAction) + { + hr = pNetFwRule->put_Action(static_cast (attrs.iAction)); + ExitOnFailure(hr, "failed to set exception action"); + } + else if (fUpdateRule) + { + hr = pNetFwRule->put_Action(NET_FW_ACTION_ALLOW); + ExitOnFailure(hr, "failed to reset exception action to allow"); + } -LExit: - ReleaseBSTR(bstrRemoteAddresses); - ReleaseBSTR(bstrPort); - ReleaseBSTR(bstrDescription); - ReleaseObject(pNetFwRule); + if (bstrFile && *bstrFile) + { + hr = pNetFwRule->put_ApplicationName(bstrFile); + ExitOnFailure(hr, "failed to set exception application name"); + } + else if (fUpdateRule) + { + hr = pNetFwRule->put_ApplicationName(NULL); + ExitOnFailure(hr, "failed to remove exception application name"); + } - return hr; -} + if (MSI_NULL_INTEGER != attrs.iEdgeTraversal) + { + switch (attrs.iEdgeTraversal) + { + default: + hr = pNetFwRule->put_EdgeTraversal(NET_FW_EDGE_TRAVERSAL_TYPE_DENY != attrs.iEdgeTraversal ? VARIANT_TRUE : VARIANT_FALSE); + ExitOnFailure(hr, "failed to set exception edge traversal"); + break; -/****************************************************************** - AddApplicationException + // handled by put_EdgeTraversalOptions + case NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_APP: + case NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_USER: + break; + } + } + else if (fUpdateRule) + { + hr = pNetFwRule->put_EdgeTraversal(VARIANT_FALSE); + ExitOnFailure(hr, "failed to remove exception edge traversal"); + } -********************************************************************/ -static HRESULT AddApplicationException( - __in FIREWALL_EXCEPTION_ATTRIBUTES const& attrs, - __in BOOL fIgnoreFailures - ) -{ - HRESULT hr = S_OK; - BSTR bstrFile = NULL; - BSTR bstrName = NULL; - INetFwRules* pNetFwRules = NULL; - INetFwRule* pNetFwRule = NULL; + // enable even when iEnabled == MSI_NULL_INTEGER + hr = pNetFwRule->put_Enabled(attrs.iEnabled ? VARIANT_TRUE : VARIANT_FALSE); + ExitOnFailure(hr, "failed to set exception enabled flag"); - // convert to BSTRs to make COM happy - bstrFile = ::SysAllocString(attrs.pwzProgram); - ExitOnNull(bstrFile, hr, E_OUTOFMEMORY, "failed SysAllocString for path"); - bstrName = ::SysAllocString(attrs.pwzName); - ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for name"); + if (bstrGrouping && *bstrGrouping) + { + hr = pNetFwRule->put_Grouping(bstrGrouping); + ExitOnFailure(hr, "failed to set exception grouping '%ls'", bstrGrouping); + } + else if (fUpdateRule) + { + hr = pNetFwRule->put_Grouping(bstrEmpty); + ExitOnFailure(hr, "failed to remove exception grouping"); + } - // get the collection of firewall rules - hr = GetFirewallRules(fIgnoreFailures, &pNetFwRules); - ExitOnFailure(hr, "failed to get firewall rules object"); - if (S_FALSE == hr) // user or package author chose to ignore missing firewall + if (bstrIcmpTypesAndCodes && *bstrIcmpTypesAndCodes) { - ExitFunction(); + hr = pNetFwRule->put_IcmpTypesAndCodes(bstrIcmpTypesAndCodes); + ExitOnFailure(hr, "failed to set exception icmp types and codes '%ls'", bstrIcmpTypesAndCodes); } - // try to find it (i.e., support reinstall) - hr = pNetFwRules->Item(bstrName, &pNetFwRule); - if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + hr = GetFwRuleInterfaces(attrs, vInterfaces); + ExitOnFailure(hr, "failed to prepare exception interfaces '%ls'", attrs.pwzInterfaces); + + if (attrs.pwzInterfaces && *attrs.pwzInterfaces) { - hr = CreateFwRuleObject(bstrName, attrs, &pNetFwRule); - ExitOnFailure(hr, "failed to create FwRule object"); + hr = pNetFwRule->put_Interfaces(vInterfaces); + ExitOnFailure(hr, "failed to set exception interfaces '%ls'", attrs.pwzInterfaces); + } + else if (fUpdateRule) + { + hr = pNetFwRule->put_Interfaces(vInterfaces); + ExitOnFailure(hr, "failed to remove exception interfaces"); + } - // set edge traversal to true - hr = pNetFwRule->put_EdgeTraversal(VARIANT_TRUE); - ExitOnFailure(hr, "failed to set application exception edgetraversal property"); - - // set path - hr = pNetFwRule->put_ApplicationName(bstrFile); - ExitOnFailure(hr, "failed to set application name"); - - // enable it - hr = pNetFwRule->put_Enabled(VARIANT_TRUE); - ExitOnFailure(hr, "failed to to enable application exception"); - - // add it to the list of authorized apps - hr = pNetFwRules->Add(pNetFwRule); - ExitOnFailure(hr, "failed to add app to the authorized apps list"); + if (bstrInterfaceTypes && *bstrInterfaceTypes) + { + hr = pNetFwRule->put_InterfaceTypes(bstrInterfaceTypes); + ExitOnFailure(hr, "failed to set exception interface types '%ls'", bstrInterfaceTypes); } - else + else if (fUpdateRule) { - // we found an existing app exception (if we succeeded, that is) - ExitOnFailure(hr, "failed trying to find existing app"); - - // enable it (just in case it was disabled) - pNetFwRule->put_Enabled(VARIANT_TRUE); + hr = pNetFwRule->put_InterfaceTypes(bstrEmpty); + ExitOnFailure(hr, "failed to remove exception interface types"); + } + + if (bstrLocalAddresses && *bstrLocalAddresses) + { + hr = pNetFwRule->put_LocalAddresses(bstrLocalAddresses); + ExitOnFailure(hr, "failed to set exception local addresses '%ls'", bstrLocalAddresses); + } + else if (fUpdateRule) + { + hr = pNetFwRule->put_LocalAddresses(bstrEmpty); + ExitOnFailure(hr, "failed to remove exception local addresses"); + } + + if (bstrRemotePort && *bstrRemotePort) + { + hr = pNetFwRule->put_RemotePorts(bstrRemotePort); + ExitOnFailure(hr, "failed to set exception remote ports '%ls'", bstrRemotePort); + } + + if (bstrServiceName && *bstrServiceName) + { + hr = pNetFwRule->put_ServiceName(bstrServiceName); + ExitOnFailure(hr, "failed to set exception service name '%ls'", bstrServiceName); + } + else if (fUpdateRule) + { + hr = pNetFwRule->put_ServiceName(NULL); + ExitOnFailure(hr, "failed to remove exception service name"); } LExit: - ReleaseBSTR(bstrName); + ReleaseBSTR(bstrRemoteAddresses); ReleaseBSTR(bstrFile); - ReleaseObject(pNetFwRules); - ReleaseObject(pNetFwRule); + ReleaseBSTR(bstrPort); + ReleaseBSTR(bstrDescription); + ReleaseBSTR(bstrGrouping); + ReleaseBSTR(bstrIcmpTypesAndCodes); + ReleaseBSTR(bstrInterfaceTypes); + ReleaseBSTR(bstrLocalAddresses); + ReleaseBSTR(bstrRemotePort); + ReleaseBSTR(bstrServiceName); + ReleaseVariant(vInterfaces); + ReleaseObject(pNetFwRule2); - return fIgnoreFailures ? S_OK : hr; + return hr; } -/****************************************************************** - AddPortException + +/******************************************************************* + AddFirewallException ********************************************************************/ -static HRESULT AddPortException( +static HRESULT AddFirewallException( __in FIREWALL_EXCEPTION_ATTRIBUTES const& attrs, __in BOOL fIgnoreFailures - ) +) { HRESULT hr = S_OK; BSTR bstrName = NULL; INetFwRules* pNetFwRules = NULL; INetFwRule* pNetFwRule = NULL; + BOOL fIgnoreUpdates = feaIgnoreUpdates == (attrs.iAttributes & feaIgnoreUpdates); + BOOL fEnableOnUpdate = feaEnableOnUpdate == (attrs.iAttributes & feaEnableOnUpdate); + BOOL fAddINetFwRule2 = feaAddINetFwRule2 == (attrs.iAttributes & feaAddINetFwRule2); + BOOL fAddINetFwRule3 = feaAddINetFwRule3 == (attrs.iAttributes & feaAddINetFwRule3); + // convert to BSTRs to make COM happy bstrName = ::SysAllocString(attrs.pwzName); ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for name"); // get the collection of firewall rules hr = GetFirewallRules(fIgnoreFailures, &pNetFwRules); - ExitOnFailure(hr, "failed to get firewall rules object"); + ExitOnFailure(hr, "failed to get firewall exception object"); if (S_FALSE == hr) // user or package author chose to ignore missing firewall { ExitFunction(); @@ -447,24 +966,56 @@ static HRESULT AddPortException( hr = pNetFwRules->Item(bstrName, &pNetFwRule); if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) { - hr = CreateFwRuleObject(bstrName, attrs, &pNetFwRule); - ExitOnFailure(hr, "failed to create FwRule object"); + hr = CreateFwRuleObject(bstrName, &pNetFwRule); + ExitOnFailure(hr, "failed to create FwRule object '%ls'", attrs.pwzName); - // enable it - hr = pNetFwRule->put_Enabled(VARIANT_TRUE); - ExitOnFailure(hr, "failed to to enable port exception"); + // set attributes of the new firewall rule + hr = UpdateFwRuleObject(pNetFwRule, FALSE, attrs); + ExitOnFailure(hr, "failed to create INetFwRule firewall exception '%ls'", attrs.pwzName); + + if (fAddINetFwRule2) + { + hr = UpdateFwRule2Object(pNetFwRule, FALSE, attrs); + ExitOnFailure(hr, "failed to create INetFwRule2 firewall exception '%ls'", attrs.pwzName); + } + + if (fAddINetFwRule3) + { + hr = UpdateFwRule3Object(pNetFwRule, FALSE, attrs); + ExitOnFailure(hr, "failed to create INetFwRule3 firewall exception '%ls'", attrs.pwzName); + } - // add it to the list of authorized ports hr = pNetFwRules->Add(pNetFwRule); - ExitOnFailure(hr, "failed to add app to the authorized ports list"); + ExitOnFailure(hr, "failed to add firewall exception '%ls' to the list", attrs.pwzName); } else { - // we found an existing port exception (if we succeeded, that is) - ExitOnFailure(hr, "failed trying to find existing port rule"); + // we found an existing firewall rule (if we succeeded, that is) + ExitOnFailure(hr, "failed trying to find existing firewall exception '%ls'", attrs.pwzName); + + if (fEnableOnUpdate) + { + hr = pNetFwRule->put_Enabled(VARIANT_TRUE); + ExitOnFailure(hr, "failed to enable existing firewall exception '%ls'", attrs.pwzName); + } + else if (!fIgnoreUpdates) + { + // overwrite attributes of the existing firewall rule + hr = UpdateFwRuleObject(pNetFwRule, TRUE, attrs); + ExitOnFailure(hr, "failed to update INetFwRule firewall exception '%ls'", attrs.pwzName); - // enable it (just in case it was disabled) - pNetFwRule->put_Enabled(VARIANT_TRUE); + if (fAddINetFwRule2) + { + hr = UpdateFwRule2Object(pNetFwRule, TRUE, attrs); + ExitOnFailure(hr, "failed to update INetFwRule2 firewall exception '%ls'", attrs.pwzName); + } + + if (fAddINetFwRule3) + { + hr = UpdateFwRule3Object(pNetFwRule, TRUE, attrs); + ExitOnFailure(hr, "failed to update INetFwRule3 firewall exception '%ls'", attrs.pwzName); + } + } } LExit: @@ -475,14 +1026,15 @@ static HRESULT AddPortException( return fIgnoreFailures ? S_OK : hr; } -/****************************************************************** + +/******************************************************************* RemoveException - Removes all exception rules with the given name. ********************************************************************/ static HRESULT RemoveException( __in LPCWSTR wzName, __in BOOL fIgnoreFailures - ) +) { HRESULT hr = S_OK;; INetFwRules* pNetFwRules = NULL; @@ -500,7 +1052,7 @@ static HRESULT RemoveException( } hr = pNetFwRules->Remove(bstrName); - ExitOnFailure(hr, "failed to remove firewall rule"); + ExitOnFailure(hr, "failed to remove firewall exception for name %ls", wzName); LExit: ReleaseBSTR(bstrName); @@ -509,20 +1061,20 @@ static HRESULT RemoveException( return fIgnoreFailures ? S_OK : hr; } -/****************************************************************** - ExecFirewallExceptions - deferred custom action entry point to + +/******************************************************************* + ExecFirewallExceptions - deferred custom action entry point to register and remove firewall exceptions. ********************************************************************/ extern "C" UINT __stdcall ExecFirewallExceptions( __in MSIHANDLE hInstall - ) +) { HRESULT hr = S_OK; LPWSTR pwz = NULL; LPWSTR pwzCustomActionData = NULL; int iTodo = WCA_TODO_UNKNOWN; - int iTarget = fetUnknown; FIREWALL_EXCEPTION_ATTRIBUTES attrs = { 0 }; @@ -530,7 +1082,7 @@ extern "C" UINT __stdcall ExecFirewallExceptions( hr = WcaInitialize(hInstall, "ExecFirewallExceptions"); ExitOnFailure(hr, "failed to initialize"); - hr = WcaGetProperty( L"CustomActionData", &pwzCustomActionData); + hr = WcaGetProperty(L"CustomActionData", &pwzCustomActionData); ExitOnFailure(hr, "failed to get CustomActionData"); WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzCustomActionData); @@ -569,60 +1121,82 @@ extern "C" UINT __stdcall ExecFirewallExceptions( ExitOnFailure(hr, "failed to read attributes from custom action data"); BOOL fIgnoreFailures = feaIgnoreFailures == (attrs.iAttributes & feaIgnoreFailures); - hr = WcaReadIntegerFromCaData(&pwz, &iTarget); - ExitOnFailure(hr, "failed to read target from custom action data"); + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzApplicationName); + ExitOnFailure(hr, "failed to read file path from custom action data"); - if (iTarget == fetApplication) - { - hr = WcaReadStringFromCaData(&pwz, &attrs.pwzProgram); - ExitOnFailure(hr, "failed to read file path from custom action data"); - } - - hr = WcaReadStringFromCaData(&pwz, &attrs.pwzPort); + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzLocalPorts); ExitOnFailure(hr, "failed to read port from custom action data"); + hr = WcaReadIntegerFromCaData(&pwz, &attrs.iProtocol); ExitOnFailure(hr, "failed to read protocol from custom action data"); + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzDescription); ExitOnFailure(hr, "failed to read protocol from custom action data"); + hr = WcaReadIntegerFromCaData(&pwz, &attrs.iDirection); ExitOnFailure(hr, "failed to read direction from custom action data"); - switch (iTarget) - { - case fetPort: - switch (iTodo) - { - case WCA_TODO_INSTALL: - case WCA_TODO_REINSTALL: - WcaLog(LOGMSG_STANDARD, "Installing firewall exception %ls on port %ls, protocol %d", attrs.pwzName, attrs.pwzPort, attrs.iProtocol); - hr = AddPortException(attrs, fIgnoreFailures); - ExitOnFailure(hr, "failed to add/update port exception for name '%ls' on port %ls, protocol %d", attrs.pwzName, attrs.pwzPort, attrs.iProtocol); - break; + hr = WcaReadIntegerFromCaData(&pwz, &attrs.iAction); + ExitOnFailure(hr, "failed to read action from custom action data"); - case WCA_TODO_UNINSTALL: - WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception %ls on port %ls, protocol %d", attrs.pwzName, attrs.pwzPort, attrs.iProtocol); - hr = RemoveException(attrs.pwzName, fIgnoreFailures); - ExitOnFailure(hr, "failed to remove port exception for name '%ls' on port %ls, protocol %d", attrs.pwzName, attrs.pwzPort, attrs.iProtocol); - break; - } - break; + hr = WcaReadIntegerFromCaData(&pwz, &attrs.iEdgeTraversal); + ExitOnFailure(hr, "failed to read edge traversal from custom action data"); - case fetApplication: - switch (iTodo) - { - case WCA_TODO_INSTALL: - case WCA_TODO_REINSTALL: - WcaLog(LOGMSG_STANDARD, "Installing firewall exception %ls (%ls)", attrs.pwzName, attrs.pwzProgram); - hr = AddApplicationException(attrs, fIgnoreFailures); - ExitOnFailure(hr, "failed to add/update application exception for name '%ls', file '%ls'", attrs.pwzName, attrs.pwzProgram); - break; + hr = WcaReadIntegerFromCaData(&pwz, &attrs.iEnabled); + ExitOnFailure(hr, "failed to read enabled flag from custom action data"); - case WCA_TODO_UNINSTALL: - WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception %ls (%ls)", attrs.pwzName, attrs.pwzProgram); - hr = RemoveException(attrs.pwzName, fIgnoreFailures); - ExitOnFailure(hr, "failed to remove application exception for name '%ls', file '%ls'", attrs.pwzName, attrs.pwzProgram); - break; - } + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzGrouping); + ExitOnFailure(hr, "failed to read grouping from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzIcmpTypesAndCodes); + ExitOnFailure(hr, "failed to read icmp types and codes from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzInterfaces); + ExitOnFailure(hr, "failed to read interfaces from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzInterfaceTypes); + ExitOnFailure(hr, "failed to read interface types from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzLocalAddresses); + ExitOnFailure(hr, "failed to read local addresses from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzRemotePorts); + ExitOnFailure(hr, "failed to read remote port from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzServiceName); + ExitOnFailure(hr, "failed to read service name from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzLocalAppPackageId); + ExitOnFailure(hr, "failed to read local app package id from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzLocalUserAuthorizedList); + ExitOnFailure(hr, "failed to read local user authorized list from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzLocalUserOwner); + ExitOnFailure(hr, "failed to read local user owner from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzRemoteMachineAuthorizedList); + ExitOnFailure(hr, "failed to read remote machine authorized list from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &attrs.pwzRemoteUserAuthorizedList); + ExitOnFailure(hr, "failed to read remote user authorized list from custom action data"); + + hr = WcaReadIntegerFromCaData(&pwz, &attrs.iSecureFlags); + ExitOnFailure(hr, "failed to read exception secure flags from custom action data"); + + switch (iTodo) + { + case WCA_TODO_INSTALL: + case WCA_TODO_REINSTALL: + WcaLog(LOGMSG_STANDARD, "Installing firewall exception %ls", attrs.pwzName); + hr = AddFirewallException(attrs, fIgnoreFailures); + ExitOnFailure(hr, "failed to add/update firewall exception for name '%ls'", attrs.pwzName); + break; + + case WCA_TODO_UNINSTALL: + WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception %ls", attrs.pwzName); + hr = RemoveException(attrs.pwzName, fIgnoreFailures); + ExitOnFailure(hr, "failed to remove firewall exception"); break; } } @@ -631,9 +1205,21 @@ extern "C" UINT __stdcall ExecFirewallExceptions( ReleaseStr(pwzCustomActionData); ReleaseStr(attrs.pwzName); ReleaseStr(attrs.pwzRemoteAddresses); - ReleaseStr(attrs.pwzProgram); - ReleaseStr(attrs.pwzPort); + ReleaseStr(attrs.pwzApplicationName); + ReleaseStr(attrs.pwzLocalPorts); ReleaseStr(attrs.pwzDescription); + ReleaseStr(attrs.pwzGrouping); + ReleaseStr(attrs.pwzIcmpTypesAndCodes); + ReleaseStr(attrs.pwzInterfaces); + ReleaseStr(attrs.pwzInterfaceTypes); + ReleaseStr(attrs.pwzLocalAddresses); + ReleaseStr(attrs.pwzRemotePorts); + ReleaseStr(attrs.pwzServiceName); + ReleaseStr(attrs.pwzLocalAppPackageId); + ReleaseStr(attrs.pwzLocalUserAuthorizedList); + ReleaseStr(attrs.pwzLocalUserOwner); + ReleaseStr(attrs.pwzRemoteMachineAuthorizedList); + ReleaseStr(attrs.pwzRemoteUserAuthorizedList); ::CoUninitialize(); return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS); diff --git a/src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs b/src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs index 7119e92da..df18f0e05 100644 --- a/src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs @@ -2,6 +2,7 @@ namespace WixToolsetTest.Firewall { + using System.Data; using System.IO; using System.Linq; using System.Xml.Linq; @@ -27,12 +28,27 @@ public void CanBuildUsingFirewall() "CustomAction:Wix5RollbackFirewallExceptionsUninstall_X86\t3329\tWix5FWCA_X86\tExecFirewallExceptions\t", "CustomAction:Wix5SchedFirewallExceptionsInstall_X86\t1\tWix5FWCA_X86\tSchedFirewallExceptionsInstall\t", "CustomAction:Wix5SchedFirewallExceptionsUninstall_X86\t1\tWix5FWCA_X86\tSchedFirewallExceptionsUninstall\t", - "Wix5FirewallException:ExampleFirewall\tExampleApp\t*\t42\t6\t[#filNdJBJmq3UCUIwmXS8x21aAsvqzk]\t0\t2147483647\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tAn app-based firewall exception\t1", - "Wix5FirewallException:fex_ZpDsnKyHlYiA24JHzvFxm3uLZ8\tExampleDefaultGatewayScope\tDefaultGateway\t4432\t6\t\t0\t2\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tdefaultGateway scope firewall exception\t1", - "Wix5FirewallException:fex6bkfWwpiRGI.wVFx0T7W4LXIHxU\tExampleDHCPScope\tdhcp\t\t211\ttest.exe\t0\t4\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDHCP scope firewall exception\t1", - "Wix5FirewallException:fex70IVsYNnbwiHQrEepmdTPKH8XYs\tExamplePort\tLocalSubnet\t42\t6\t\t0\t2147483647\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tA port-based firewall exception\t2", - "Wix5FirewallException:fexXxaXCXXFh.UxO_BjmZxi1B1du_Q\tExampleWINSScope\twins\t6573\t6\t\t0\t1\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tWINS scope firewall exception\t1", - "Wix5FirewallException:fexxY71H2ZBkPalv7uid1Yy4qaA_lA\tExampleDNSScope\tdns\t356\t17\t\t0\t2147483647\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDNS scope firewall exception\t1", + "Wix5FirewallException:ExampleFirewall\tExampleApp\t*\t42\t6\t[#filNdJBJmq3UCUIwmXS8x21aAsvqzk]\t2\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tAn app-based firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex.BGtyMRGAhxb2hG.49JvWYz7fM0\tLocalScopeExample2\t*\t\t-2147483648\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tRule with local scope property\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t[LOCALSCOPE_PROP]\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex0HTxATWjpC2PCoY6DB7f2D1WaKU\tLocalScopeExample1\t*\t\t-2147483648\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple rule with local scope\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\tLocalSubnet\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex4FeP470wYcFpw.g7fbIKiLnZPzg\tExampleDNSScope\tdns\t356\t17\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDNS scope firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex4zTcT0Iwu3dUtHIHXD5qfymvpcM\tdefertouser\t\t\t-2147483648\tfw.exe\t8\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDefer to user edge traversal\t1\t-2147483648\t3\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex8vMfBplrod4daEz3PqDTeX6olGE\tExampleDefaultGatewayScope\tDefaultGateway\t4432\t6\t\t0\t2\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tdefaultGateway scope firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexAMmHzFDyQmubTOnKS1Cn0Y3q_Ug\tINetFwRule3 properties\t*\t\t-2147483648\t\t16\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tINetFwRule3 passed via properties\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t[PROP1]\t[PROP2]\t[PROP3]\t[PROP4]\t[PROP5]\t[PROP6]", + "Wix5FirewallException:fexArlOkFR7CAwVZ2wk8yNdiREydu0\tRemotePortExample2\t\t\t6\tfw.exe\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tRule with remote port property\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t[REMOTEPORT_PROP]\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexaUTe2tRRcSYrPUTn44DAZhE.40Q\tExamplePort\tLocalSubnet\t42\t6\t\t4\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tA port-based firewall exception\t2\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexD6w20c5HfNi4l1vHFj_eet4cC8I\tExampleWINSScope\twins\t6573\t6\t\t0\t1\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tWINS scope firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexeD3yox6fMflfRy7sDwSN2CMCS2s\tExampleService\t\t12000\t6\t%windir%\\system32\\svchost.exe\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tA port-based service exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\tDHCP,WINS\t\tftpsrv\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexeok6aI2_AlclZggec4d8PBLFXLw\tinterface property\t\t54671\t6\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tInterfaces with property\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t[INTERFACE_PROPERTY]\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexEPvcf4iexD1mVQdvxm7tD02nZEc\tICMPExample1\t\t\t2\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple ICMP rule\t1\t-2147483648\t-2147483648\t-2147483648\t\t4:*,9:*,12:*\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexfzjTQsWwZkHQpObtl0XaUosfcRk\tGroupingExample1\t\t\t-2147483648\tfw.exe\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple rule with grouping\t1\t-2147483648\t-2147483648\t-2147483648\t@yourresources.dll,-1005\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexHx2xbwZYzAi0oYp4YGWevJQs5eM\tRemotePortExample1\t*\t\t6\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple rule with remote port\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t34560\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexpWUzK53RVnaluW36gSmphPRY8VY\tExampleDHCPScope\tdhcp\t\t211\ttest.exe\t0\t4\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDHCP scope firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexuanTga5xaaFzr9JsAnUmpCNediw\tICMPExample2\t\t\t2\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tRule with ICMP property\t1\t-2147483648\t-2147483648\t-2147483648\t\t[ICMP_PROP]\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexv60s7u2Dmd1imH5vEFYKPgEWhG4\tinterface nested\t127.0.0.1\t54671\t6\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tInterfaces with nested elements\t1\t-2147483648\t-2147483648\t-2147483648\t\t\tWi-Fi|Local Area Connection\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexVr6uHcOCak5MHuTLwujjh_oKtbI\tGroupingExample2\t\t8732\t6\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tRule with grouping property\t1\t-2147483648\t-2147483648\t-2147483648\t[GROUPING_PROP]\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexwjf4OTFVE9SNiC4goVxBA6ENJBE\tINetFwRule3 values\t*\t\t-2147483648\t\t16\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple INetFwRule3 values\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\tS-1-15-2-1239072475-3687740317-1842961305-3395936705-4023953123-1525404051-2779347315\tO:LSD:(A;;CC;;;S-1-5-84-0-0-0-0-0)\tS-1-5-21-1898747406-2352535518-1247798438-1914\t127.0.0.1\tO:LSD:(A;;CC;;;S-1-5-84-0-0-0-0-0)\t3", + "Wix5FirewallException:ServiceInstall.nested\tExampleNestedService\tLocalSubnet\t3546-7890\t6\t\t1\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tA port-based firewall exception for a windows service\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\tLan,Wireless\t\t\tsvc1\t\t\t\t\t\t-2147483648", }, results); } @@ -51,12 +67,69 @@ public void CanBuildUsingFirewallARM64() "CustomAction:Wix5RollbackFirewallExceptionsUninstall_A64\t3329\tWix5FWCA_A64\tExecFirewallExceptions\t", "CustomAction:Wix5SchedFirewallExceptionsInstall_A64\t1\tWix5FWCA_A64\tSchedFirewallExceptionsInstall\t", "CustomAction:Wix5SchedFirewallExceptionsUninstall_A64\t1\tWix5FWCA_A64\tSchedFirewallExceptionsUninstall\t", - "Wix5FirewallException:ExampleFirewall\tExampleApp\t*\t42\t6\t[#filNdJBJmq3UCUIwmXS8x21aAsvqzk]\t0\t2147483647\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tAn app-based firewall exception\t1", - "Wix5FirewallException:fex_ZpDsnKyHlYiA24JHzvFxm3uLZ8\tExampleDefaultGatewayScope\tDefaultGateway\t4432\t6\t\t0\t2\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tdefaultGateway scope firewall exception\t1", - "Wix5FirewallException:fex6bkfWwpiRGI.wVFx0T7W4LXIHxU\tExampleDHCPScope\tdhcp\t\t211\ttest.exe\t0\t4\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDHCP scope firewall exception\t1", - "Wix5FirewallException:fex70IVsYNnbwiHQrEepmdTPKH8XYs\tExamplePort\tLocalSubnet\t42\t6\t\t0\t2147483647\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tA port-based firewall exception\t2", - "Wix5FirewallException:fexXxaXCXXFh.UxO_BjmZxi1B1du_Q\tExampleWINSScope\twins\t6573\t6\t\t0\t1\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tWINS scope firewall exception\t1", - "Wix5FirewallException:fexxY71H2ZBkPalv7uid1Yy4qaA_lA\tExampleDNSScope\tdns\t356\t17\t\t0\t2147483647\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDNS scope firewall exception\t1", + "Wix5FirewallException:ExampleFirewall\tExampleApp\t*\t42\t6\t[#filNdJBJmq3UCUIwmXS8x21aAsvqzk]\t2\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tAn app-based firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex.BGtyMRGAhxb2hG.49JvWYz7fM0\tLocalScopeExample2\t*\t\t-2147483648\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tRule with local scope property\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t[LOCALSCOPE_PROP]\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex0HTxATWjpC2PCoY6DB7f2D1WaKU\tLocalScopeExample1\t*\t\t-2147483648\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple rule with local scope\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\tLocalSubnet\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex4FeP470wYcFpw.g7fbIKiLnZPzg\tExampleDNSScope\tdns\t356\t17\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDNS scope firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex4zTcT0Iwu3dUtHIHXD5qfymvpcM\tdefertouser\t\t\t-2147483648\tfw.exe\t8\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDefer to user edge traversal\t1\t-2147483648\t3\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fex8vMfBplrod4daEz3PqDTeX6olGE\tExampleDefaultGatewayScope\tDefaultGateway\t4432\t6\t\t0\t2\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tdefaultGateway scope firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexAMmHzFDyQmubTOnKS1Cn0Y3q_Ug\tINetFwRule3 properties\t*\t\t-2147483648\t\t16\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tINetFwRule3 passed via properties\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t[PROP1]\t[PROP2]\t[PROP3]\t[PROP4]\t[PROP5]\t[PROP6]", + "Wix5FirewallException:fexArlOkFR7CAwVZ2wk8yNdiREydu0\tRemotePortExample2\t\t\t6\tfw.exe\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tRule with remote port property\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t[REMOTEPORT_PROP]\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexaUTe2tRRcSYrPUTn44DAZhE.40Q\tExamplePort\tLocalSubnet\t42\t6\t\t4\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tA port-based firewall exception\t2\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexD6w20c5HfNi4l1vHFj_eet4cC8I\tExampleWINSScope\twins\t6573\t6\t\t0\t1\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tWINS scope firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexeD3yox6fMflfRy7sDwSN2CMCS2s\tExampleService\t\t12000\t6\t%windir%\\system32\\svchost.exe\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tA port-based service exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\tDHCP,WINS\t\tftpsrv\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexeok6aI2_AlclZggec4d8PBLFXLw\tinterface property\t\t54671\t6\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tInterfaces with property\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t[INTERFACE_PROPERTY]\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexEPvcf4iexD1mVQdvxm7tD02nZEc\tICMPExample1\t\t\t2\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple ICMP rule\t1\t-2147483648\t-2147483648\t-2147483648\t\t4:*,9:*,12:*\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexfzjTQsWwZkHQpObtl0XaUosfcRk\tGroupingExample1\t\t\t-2147483648\tfw.exe\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple rule with grouping\t1\t-2147483648\t-2147483648\t-2147483648\t@yourresources.dll,-1005\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexHx2xbwZYzAi0oYp4YGWevJQs5eM\tRemotePortExample1\t*\t\t6\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple rule with remote port\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t34560\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexpWUzK53RVnaluW36gSmphPRY8VY\tExampleDHCPScope\tdhcp\t\t211\ttest.exe\t0\t4\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tDHCP scope firewall exception\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexuanTga5xaaFzr9JsAnUmpCNediw\tICMPExample2\t\t\t2\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tRule with ICMP property\t1\t-2147483648\t-2147483648\t-2147483648\t\t[ICMP_PROP]\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexv60s7u2Dmd1imH5vEFYKPgEWhG4\tinterface nested\t127.0.0.1\t54671\t6\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tInterfaces with nested elements\t1\t-2147483648\t-2147483648\t-2147483648\t\t\tWi-Fi|Local Area Connection\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexVr6uHcOCak5MHuTLwujjh_oKtbI\tGroupingExample2\t\t8732\t6\t\t0\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tRule with grouping property\t1\t-2147483648\t-2147483648\t-2147483648\t[GROUPING_PROP]\t\t\t\t\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexwjf4OTFVE9SNiC4goVxBA6ENJBE\tINetFwRule3 values\t*\t\t-2147483648\t\t16\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tSimple INetFwRule3 values\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\t\t\t\t\tS-1-15-2-1239072475-3687740317-1842961305-3395936705-4023953123-1525404051-2779347315\tO:LSD:(A;;CC;;;S-1-5-84-0-0-0-0-0)\tS-1-5-21-1898747406-2352535518-1247798438-1914\t127.0.0.1\tO:LSD:(A;;CC;;;S-1-5-84-0-0-0-0-0)\t3", + "Wix5FirewallException:ServiceInstall.nested\tExampleNestedService\tLocalSubnet\t3546-7890\t6\t\t1\t-2147483648\tfilNdJBJmq3UCUIwmXS8x21aAsvqzk\tA port-based firewall exception for a windows service\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t\tLan,Wireless\t\t\tsvc1\t\t\t\t\t\t-2147483648", + }, results); + } + + [Fact] + public void CanBuildWithProperties() + { + var folder = TestData.Get(@"TestData\UsingProperties"); + var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder }); + + var results = build.BuildAndQuery(Build, "Wix5FirewallException", "CustomAction"); + WixAssert.CompareLineByLine(new[] + { + "CustomAction:Wix5ExecFirewallExceptionsInstall_X86\t3073\tWix5FWCA_X86\tExecFirewallExceptions\t", + "CustomAction:Wix5ExecFirewallExceptionsUninstall_X86\t3073\tWix5FWCA_X86\tExecFirewallExceptions\t", + "CustomAction:Wix5RollbackFirewallExceptionsInstall_X86\t3329\tWix5FWCA_X86\tExecFirewallExceptions\t", + "CustomAction:Wix5RollbackFirewallExceptionsUninstall_X86\t3329\tWix5FWCA_X86\tExecFirewallExceptions\t", + "CustomAction:Wix5SchedFirewallExceptionsInstall_X86\t1\tWix5FWCA_X86\tSchedFirewallExceptionsInstall\t", + "CustomAction:Wix5SchedFirewallExceptionsUninstall_X86\t1\tWix5FWCA_X86\tSchedFirewallExceptionsUninstall\t", + "Wix5FirewallException:fexRrE4bS.DwUJQMvzX0ALEsx7jrZs\tSingle Nested properties\t[REMOTEADDRESS]\t\t-2147483648\t\t0\t-2147483648\tFirewallComponent\t\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t[INTERFACE]\t[INTERFACETYPE]\t[LOCALADDRESS]\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexvEy1GfdOjHlKcvsguyqK6mvYKyk\t[NAME]\t[REMOTESCOPE]\t[LOCALPORT]\t[PROTOCOL]\t[PROGRAM]\t16\t[PROFILE]\tFirewallComponent\t[DESCRIPTION]\t1\t[ACTION]\t[EDGETRAVERSAL]\t[ENABLED]\t[GROUPING]\t[ICMPTYPES]\t[INTERFACE]\t[INTERFACETYPE]\t[LOCALSCOPE]\t[REMOTEPORT]\t[SERVICE]\t[PACKAGEID]\t[LOCALUSERS]\t[LOCALOWNER]\t[REMOTEMACHINES]\t[REMOTEUSERS]\t[SECUREFLAGS]", + "Wix5FirewallException:fexWywW3VGiEuG23FOv1YM6h7R6F5Q\tMultiple Nested properties\t[REMOTEADDRESS1],[REMOTEADDRESS2]\t\t-2147483648\t\t0\t-2147483648\tFirewallComponent\t\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t[INTERFACE1]|[INTERFACE2]\t[INTERFACETYPE1],[INTERFACETYPE2]\t[LOCALADDRESS1],[LOCALADDRESS2]\t\t\t\t\t\t\t\t-2147483648", + }, results); + } + + [Fact] + public void CanBuildWithPropertiesUsingFirewallARM64() + { + var folder = TestData.Get(@"TestData\UsingProperties"); + var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder }); + + var results = build.BuildAndQuery(BuildARM64, "Wix5FirewallException", "CustomAction"); + WixAssert.CompareLineByLine(new[] + { + "CustomAction:Wix5ExecFirewallExceptionsInstall_A64\t3073\tWix5FWCA_A64\tExecFirewallExceptions\t", + "CustomAction:Wix5ExecFirewallExceptionsUninstall_A64\t3073\tWix5FWCA_A64\tExecFirewallExceptions\t", + "CustomAction:Wix5RollbackFirewallExceptionsInstall_A64\t3329\tWix5FWCA_A64\tExecFirewallExceptions\t", + "CustomAction:Wix5RollbackFirewallExceptionsUninstall_A64\t3329\tWix5FWCA_A64\tExecFirewallExceptions\t", + "CustomAction:Wix5SchedFirewallExceptionsInstall_A64\t1\tWix5FWCA_A64\tSchedFirewallExceptionsInstall\t", + "CustomAction:Wix5SchedFirewallExceptionsUninstall_A64\t1\tWix5FWCA_A64\tSchedFirewallExceptionsUninstall\t", + "Wix5FirewallException:fexRrE4bS.DwUJQMvzX0ALEsx7jrZs\tSingle Nested properties\t[REMOTEADDRESS]\t\t-2147483648\t\t0\t-2147483648\tFirewallComponent\t\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t[INTERFACE]\t[INTERFACETYPE]\t[LOCALADDRESS]\t\t\t\t\t\t\t\t-2147483648", + "Wix5FirewallException:fexvEy1GfdOjHlKcvsguyqK6mvYKyk\t[NAME]\t[REMOTESCOPE]\t[LOCALPORT]\t[PROTOCOL]\t[PROGRAM]\t16\t[PROFILE]\tFirewallComponent\t[DESCRIPTION]\t1\t[ACTION]\t[EDGETRAVERSAL]\t[ENABLED]\t[GROUPING]\t[ICMPTYPES]\t[INTERFACE]\t[INTERFACETYPE]\t[LOCALSCOPE]\t[REMOTEPORT]\t[SERVICE]\t[PACKAGEID]\t[LOCALUSERS]\t[LOCALOWNER]\t[REMOTEMACHINES]\t[REMOTEUSERS]\t[SECUREFLAGS]", + "Wix5FirewallException:fexWywW3VGiEuG23FOv1YM6h7R6F5Q\tMultiple Nested properties\t[REMOTEADDRESS1],[REMOTEADDRESS2]\t\t-2147483648\t\t0\t-2147483648\tFirewallComponent\t\t1\t-2147483648\t-2147483648\t-2147483648\t\t\t[INTERFACE1]|[INTERFACE2]\t[INTERFACETYPE1],[INTERFACETYPE2]\t[LOCALADDRESS1],[LOCALADDRESS2]\t\t\t\t\t\t\t\t-2147483648", }, results); } @@ -83,6 +156,59 @@ public void CanRoundtripFirewallExceptions() "FirewallException", "FirewallException", "FirewallException", + "FirewallException", + "FirewallException", + "LocalAddress", + "LocalAddress", + "FirewallException", + "RemoteAddress", + "Interface", + "Interface", + "FirewallException", + "FirewallException", + "InterfaceType", + "InterfaceType", + "FirewallException", + "FirewallException", + "FirewallException", + "FirewallException", + "FirewallException", + "FirewallException", + "FirewallException", + "FirewallException", + "FirewallException", + "FirewallException", + }, actual.Select(a => a.Name).ToArray()); + } + + [Fact] + public void CanRoundtripFirewallExceptionsWithProperties() + { + var folder = TestData.Get(@"TestData", "UsingProperties"); + var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder }); + var output = Path.Combine(folder, "FirewallPropertiesDecompile.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", + "FirewallException", + "RemoteAddress", + "RemoteAddress", + "Interface", + "Interface", + "InterfaceType", + "InterfaceType", + "LocalAddress", + "LocalAddress", }, actual.Select(a => a.Name).ToArray()); } @@ -98,9 +224,8 @@ public void RoundtripAttributesAreCorrectForApp() "Port=42", "Protocol=tcp", "Program=[#filNdJBJmq3UCUIwmXS8x21aAsvqzk]", - "Profile=all", + "OnUpdate=DoNothing", "Description=An app-based firewall exception", - "Outbound=no", "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", }, actual.Attributes); } @@ -111,12 +236,12 @@ public void RoundtripAttributesAreCorrectForPort() var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "ExamplePort"); WixAssert.CompareLineByLine(new[] { - "Id=fex70IVsYNnbwiHQrEepmdTPKH8XYs", + "Id=fexaUTe2tRRcSYrPUTn44DAZhE.40Q", "Name=ExamplePort", "Scope=localSubnet", "Port=42", "Protocol=tcp", - "Profile=all", + "OnUpdate=EnableOnly", "Description=A port-based firewall exception", "Outbound=yes", "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", @@ -129,14 +254,12 @@ public void RoundtripAttributesAreCorrectForDNSScope() var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "ExampleDNSScope"); WixAssert.CompareLineByLine(new[] { - "Id=fexxY71H2ZBkPalv7uid1Yy4qaA_lA", + "Id=fex4FeP470wYcFpw.g7fbIKiLnZPzg", "Name=ExampleDNSScope", "Scope=DNS", "Port=356", "Protocol=udp", - "Profile=all", "Description=DNS scope firewall exception", - "Outbound=no", "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", }, actual.Attributes); } @@ -147,14 +270,13 @@ public void RoundtripAttributesAreCorrectForDHCPScope() var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "ExampleDHCPScope"); WixAssert.CompareLineByLine(new[] { - "Id=fex6bkfWwpiRGI.wVFx0T7W4LXIHxU", + "Id=fexpWUzK53RVnaluW36gSmphPRY8VY", "Name=ExampleDHCPScope", "Scope=DHCP", "Protocol=211", "Program=test.exe", "Profile=public", "Description=DHCP scope firewall exception", - "Outbound=no", "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall" }, actual.Attributes); } @@ -165,14 +287,13 @@ public void RoundtripAttributesAreCorrectForWINSScope() var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "ExampleWINSScope"); WixAssert.CompareLineByLine(new[] { - "Id=fexXxaXCXXFh.UxO_BjmZxi1B1du_Q", + "Id=fexD6w20c5HfNi4l1vHFj_eet4cC8I", "Name=ExampleWINSScope", "Scope=WINS", "Port=6573", "Protocol=tcp", "Profile=domain", "Description=WINS scope firewall exception", - "Outbound=no", "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", }, actual.Attributes); } @@ -183,18 +304,322 @@ public void RoundtripAttributesAreCorrectForDefaultGatewayScope() var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "ExampleDefaultGatewayScope"); WixAssert.CompareLineByLine(new[] { - "Id=fex_ZpDsnKyHlYiA24JHzvFxm3uLZ8", + "Id=fex8vMfBplrod4daEz3PqDTeX6olGE", "Name=ExampleDefaultGatewayScope", "Scope=defaultGateway", "Port=4432", "Protocol=tcp", "Profile=private", "Description=defaultGateway scope firewall exception", - "Outbound=no", "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", }, actual.Attributes); } + [Fact] + public void RoundtripAttributesAreCorrectForINetFwRule3Values() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "INetFwRule3 values"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexwjf4OTFVE9SNiC4goVxBA6ENJBE", + "Name=INetFwRule3 values", + "Scope=any", + "Description=Simple INetFwRule3 values", + "LocalAppPackageId=S-1-15-2-1239072475-3687740317-1842961305-3395936705-4023953123-1525404051-2779347315", + "LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-84-0-0-0-0-0)", + "LocalUserOwner=S-1-5-21-1898747406-2352535518-1247798438-1914", + "RemoteMachineAuthorizedList=127.0.0.1", + "RemoteUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-84-0-0-0-0-0)", + "IPSecSecureFlags=NegotiateEncryption", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForINetFwRule3Properties() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "INetFwRule3 properties"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexAMmHzFDyQmubTOnKS1Cn0Y3q_Ug", + "Name=INetFwRule3 properties", + "Scope=any", + "Description=INetFwRule3 passed via properties", + "LocalAppPackageId=[PROP1]", + "LocalUserAuthorizedList=[PROP2]", + "LocalUserOwner=[PROP3]", + "RemoteMachineAuthorizedList=[PROP4]", + "RemoteUserAuthorizedList=[PROP5]", + "IPSecSecureFlags=[PROP6]", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForGroupingValue() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "GroupingExample1"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexfzjTQsWwZkHQpObtl0XaUosfcRk", + "Name=GroupingExample1", + "Program=fw.exe", + "Description=Simple rule with grouping", + "Grouping=@yourresources.dll,-1005", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForGroupingProperty() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "GroupingExample2"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexVr6uHcOCak5MHuTLwujjh_oKtbI", + "Name=GroupingExample2", + "Port=8732", + "Protocol=tcp", + "Description=Rule with grouping property", + "Grouping=[GROUPING_PROP]", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForIcmpValue() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "ICMPExample1"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexEPvcf4iexD1mVQdvxm7tD02nZEc", + "Name=ICMPExample1", + "Protocol=2", + "Description=Simple ICMP rule", + "IcmpTypesAndCodes=4:*,9:*,12:*", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForIcmpProperty() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "ICMPExample2"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexuanTga5xaaFzr9JsAnUmpCNediw", + "Name=ICMPExample2", + "Protocol=2", + "Description=Rule with ICMP property", + "IcmpTypesAndCodes=[ICMP_PROP]", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForLocalScopeValue() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "LocalScopeExample1"); + WixAssert.CompareLineByLine(new[] + { + "Id=fex0HTxATWjpC2PCoY6DB7f2D1WaKU", + "Name=LocalScopeExample1", + "Scope=any", + "Description=Simple rule with local scope", + "LocalScope=localSubnet", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForLocalScopeProperty() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "LocalScopeExample2"); + WixAssert.CompareLineByLine(new[] + { + "Id=fex.BGtyMRGAhxb2hG.49JvWYz7fM0", + "Name=LocalScopeExample2", + "Scope=any", + "Description=Rule with local scope property", + "LocalScope=[LOCALSCOPE_PROP]", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForRemotePorts() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "RemotePortExample1"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexHx2xbwZYzAi0oYp4YGWevJQs5eM", + "Name=RemotePortExample1", + "Scope=any", + "Protocol=tcp", + "Description=Simple rule with remote port", + "RemotePort=34560", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectForRemotePortsProperty() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "RemotePortExample2"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexArlOkFR7CAwVZ2wk8yNdiREydu0", + "Name=RemotePortExample2", + "Protocol=tcp", + "Program=fw.exe", + "Description=Rule with remote port property", + "RemotePort=[REMOTEPORT_PROP]", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", + }, actual.Attributes); + } + + [Fact] + public void RoundtripAttributesAreCorrectWhenPropertiesAreUsed() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "[NAME]", "UsingProperties"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexvEy1GfdOjHlKcvsguyqK6mvYKyk", + "Name=[NAME]", + "Scope=[REMOTESCOPE]", + "Port=[LOCALPORT]", + "Protocol=[PROTOCOL]", + "Program=[PROGRAM]", + "Profile=[PROFILE]", + "Description=[DESCRIPTION]", + "Action=[ACTION]", + "EdgeTraversal=[EDGETRAVERSAL]", + "Enabled=[ENABLED]", + "Grouping=[GROUPING]", + "IcmpTypesAndCodes=[ICMPTYPES]", + "Interface=[INTERFACE]", + "InterfaceType=[INTERFACETYPE]", + "LocalScope=[LOCALSCOPE]", + "RemotePort=[REMOTEPORT]", + "Service=[SERVICE]", + "LocalAppPackageId=[PACKAGEID]", + "LocalUserAuthorizedList=[LOCALUSERS]", + "LocalUserOwner=[LOCALOWNER]", + "RemoteMachineAuthorizedList=[REMOTEMACHINES]", + "RemoteUserAuthorizedList=[REMOTEUSERS]", + "IPSecSecureFlags=[SECUREFLAGS]", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall" + }, actual.Attributes); + + var folder = TestData.Get(@"TestData", "UsingProperties"); + var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder }); + var output = Path.Combine(folder, $"FirewallNothingNested.xml"); + + build.BuildAndDecompileAndBuild(Build, Decompile, output); + + var doc = XDocument.Load(output); + var related = doc.Descendants() + .Where(e => + { + return e.Name.Namespace == "http://wixtoolset.org/schemas/v4/wxs/firewall" && + e.Parent.Attributes().Any(a => a.Name.LocalName == "Name" && a.Value == "[NAME]"); + }); + + var nested = related.Select(e => e.Attributes().Single(a => a.Name.LocalName == "Name").Value); + Assert.False(nested.Any()); + } + + [Fact] + public void RoundtripAttributesAreCorrectWhenNestedPropertiesAreUsed() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "Single Nested properties", "UsingProperties"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexRrE4bS.DwUJQMvzX0ALEsx7jrZs", + "Name=Single Nested properties", + "Scope=[REMOTEADDRESS]", + "Interface=[INTERFACE]", + "InterfaceType=[INTERFACETYPE]", + "LocalScope=[LOCALADDRESS]", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall" + }, actual.Attributes); + + var folder = TestData.Get(@"TestData", "UsingProperties"); + var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder }); + var output = Path.Combine(folder, $"FirewallSingleNested.xml"); + + build.BuildAndDecompileAndBuild(Build, Decompile, output); + + var doc = XDocument.Load(output); + var related = doc.Descendants() + .Where(e => + { + return e.Name.Namespace == "http://wixtoolset.org/schemas/v4/wxs/firewall" && + e.Parent.Attributes().Any(a => a.Name.LocalName == "Name" && a.Value == "Single Nested properties"); + }); + + var nested = related.Select(e => e.Attributes().Single(a => a.Name.LocalName == "Name").Value); + Assert.False(nested.Any()); + } + + [Fact] + public void RoundtripAttributesAreCorrectWhenMultipleNestedPropertiesAreUsed() + { + var actual = BuildAndDecompileAndBuild("http://wixtoolset.org/schemas/v4/wxs/firewall", "Multiple Nested properties", "UsingProperties"); + WixAssert.CompareLineByLine(new[] + { + "Id=fexWywW3VGiEuG23FOv1YM6h7R6F5Q", + "Name=Multiple Nested properties", + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall" + }, actual.Attributes); + + var folder = TestData.Get(@"TestData", "UsingProperties"); + var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder }); + var output = Path.Combine(folder, $"FirewallMultipleNested.xml"); + + build.BuildAndDecompileAndBuild(Build, Decompile, output); + + var doc = XDocument.Load(output); + var related = doc.Descendants() + .Where(e => + { + return e.Name.Namespace == "http://wixtoolset.org/schemas/v4/wxs/firewall" && + e.Parent.Attributes().Any(a => a.Name.LocalName == "Name" && a.Value == "Multiple Nested properties"); + }); + + var interfaces = related.Where(e => e.Name.LocalName == "Interface") + .Select(e => e.Attributes().Single(a => a.Name.LocalName == "Name").Value); + WixAssert.CompareLineByLine(new[] + { + "[INTERFACE1]", + "[INTERFACE2]", + }, interfaces.ToArray()); + + var interfaceTypes = related.Where(e => e.Name.LocalName == "InterfaceType") + .Select(e => e.Attributes().Single(a => a.Name.LocalName == "Value").Value); + WixAssert.CompareLineByLine(new[] + { + "[INTERFACETYPE1]", + "[INTERFACETYPE2]", + }, interfaceTypes.ToArray()); + + var remotes = related.Where(e => e.Name.LocalName == "RemoteAddress") + .Select(e => e.Attributes().Single(a => a.Name.LocalName == "Value").Value); + WixAssert.CompareLineByLine(new[] + { + "[REMOTEADDRESS1]", + "[REMOTEADDRESS2]", + }, remotes.ToArray()); + + var locals = related.Where(e => e.Name.LocalName == "LocalAddress") + .Select(e => e.Attributes().Single(a => a.Name.LocalName == "Value").Value); + WixAssert.CompareLineByLine(new[] + { + "[LOCALADDRESS1]", + "[LOCALADDRESS2]", + }, locals.ToArray()); + } + private static void Build(string[] args) { var result = WixRunner.Execute(args); @@ -216,15 +641,16 @@ private static void Decompile(string[] args) var result = WixRunner.Execute(args); result.AssertSuccess(); } + class AttributeVerifier { public string Name { get; set; } public string[] Attributes { get; set; } } - private static AttributeVerifier BuildAndDecompileAndBuild(string nameSpace, string ruleName) + private static AttributeVerifier BuildAndDecompileAndBuild(string nameSpace, string ruleName, string path = "UsingFirewall") { - var folder = TestData.Get(@"TestData", "UsingFirewall"); + var folder = TestData.Get(@"TestData", path); var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder }); var output = Path.Combine(folder, $"Firewall{ruleName}.xml"); @@ -232,7 +658,10 @@ private static AttributeVerifier BuildAndDecompileAndBuild(string nameSpace, str var doc = XDocument.Load(output); var actual = doc.Descendants() - .Where(e => e.Name.Namespace == nameSpace) + .Where(e => + { + return e.Name.Namespace == nameSpace && e.Name.LocalName == "FirewallException"; + }) .Select(fe => new AttributeVerifier { Name = fe.Attributes().Single(a => a.Name.LocalName == "Name").Value, diff --git a/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs index 957aa6427..4bb2e192c 100644 --- a/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs @@ -6,16 +6,51 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/Package.en-us.wxl b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/Package.en-us.wxl new file mode 100644 index 000000000..f1df1234b --- /dev/null +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/Package.en-us.wxl @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/Package.wxs b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/Package.wxs new file mode 100644 index 000000000..814becd1d --- /dev/null +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/Package.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/PackageComponents.wxs b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/PackageComponents.wxs new file mode 100644 index 000000000..05c3ea8ae --- /dev/null +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/PackageComponents.wxs @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/example.txt b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/example.txt new file mode 100644 index 000000000..1b4ffe8a4 --- /dev/null +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingProperties/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/ext/Firewall/test/WixToolsetTest.Firewall/WixToolsetTest.Firewall.csproj b/src/ext/Firewall/test/WixToolsetTest.Firewall/WixToolsetTest.Firewall.csproj index e46020a62..68acfe528 100644 --- a/src/ext/Firewall/test/WixToolsetTest.Firewall/WixToolsetTest.Firewall.csproj +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/WixToolsetTest.Firewall.csproj @@ -5,6 +5,8 @@ net6.0 true + false + false diff --git a/src/ext/Firewall/wixext/FirewallCompiler.cs b/src/ext/Firewall/wixext/FirewallCompiler.cs index ed49ba9c4..c4a5318ca 100644 --- a/src/ext/Firewall/wixext/FirewallCompiler.cs +++ b/src/ext/Firewall/wixext/FirewallCompiler.cs @@ -35,7 +35,7 @@ public override void ParseElement(Intermediate intermediate, IntermediateSection switch (element.Name.LocalName) { case "FirewallException": - this.ParseFirewallExceptionElement(intermediate, section, element, fileComponentId, fileId); + this.ParseFirewallExceptionElement(intermediate, section, parentElement, element, fileComponentId, fileId, null); break; default: this.ParseHelper.UnexpectedElement(parentElement, element); @@ -48,7 +48,35 @@ public override void ParseElement(Intermediate intermediate, IntermediateSection switch (element.Name.LocalName) { case "FirewallException": - this.ParseFirewallExceptionElement(intermediate, section, element, componentId, null); + this.ParseFirewallExceptionElement(intermediate, section, parentElement, element, componentId, null, null); + break; + default: + this.ParseHelper.UnexpectedElement(parentElement, element); + break; + } + break; + case "ServiceConfig": + var serviceConfigName = context["ServiceConfigServiceName"]; + var serviceConfigComponentId = context["ServiceConfigComponentId"]; + + switch (element.Name.LocalName) + { + case "FirewallException": + this.ParseFirewallExceptionElement(intermediate, section, parentElement, element, serviceConfigComponentId, null, serviceConfigName); + break; + default: + this.ParseHelper.UnexpectedElement(parentElement, element); + break; + } + break; + case "ServiceInstall": + var serviceInstallName = context["ServiceInstallName"]; + var serviceInstallComponentId = context["ServiceInstallComponentId"]; + + switch (element.Name.LocalName) + { + case "FirewallException": + this.ParseFirewallExceptionElement(intermediate, section, parentElement, element, serviceInstallComponentId, null, serviceInstallName); break; default: this.ParseHelper.UnexpectedElement(parentElement, element); @@ -64,10 +92,12 @@ public override void ParseElement(Intermediate intermediate, IntermediateSection /// /// Parses a FirewallException element. /// + /// The parent element of the one being parsed. /// The element to parse. /// Identifier of the component that owns this firewall exception. /// The file identifier of the parent element (null if nested under Component). - private void ParseFirewallExceptionElement(Intermediate intermediate, IntermediateSection section, XElement element, string componentId, string fileId) + /// The service name of the parent element (null if not nested under ServiceConfig or ServiceInstall). + private void ParseFirewallExceptionElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, string componentId, string fileId, string serviceName) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); Identifier id = null; @@ -76,12 +106,32 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia string file = null; string program = null; string port = null; - int? protocol = null; - int? profile = null; + string protocol = null; + string profile = null; string scope = null; string remoteAddresses = null; string description = null; int? direction = null; + string protocolValue = null; + string action = null; + string edgeTraversal = null; + string enabled = null; + string grouping = null; + string icmpTypesAndCodes = null; + string interfaces = null; + string interfaceValue = null; + string interfaceTypes = null; + string interfaceTypeValue = null; + string localScope = null; + string localAddresses = null; + string remotePort = null; + string service = null; + string localAppPackageId = null; + string localUserAuthorizedList = null; + string localUserOwner = null; + string remoteMachineAuthorizedList = null; + string remoteUserAuthorizedList = null; + string secureFlags = null; foreach (var attrib in element.Attributes()) { @@ -96,9 +146,9 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia name = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "File": - if (null != fileId) + if (fileId != null) { - this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "File", "File")); + this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "File", parentElement.Name.LocalName)); } else { @@ -106,15 +156,31 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia } break; case "IgnoreFailure": - if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + if (this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes) { attributes |= 0x1; // feaIgnoreFailures } break; + case "OnUpdate": + var onupdate = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (onupdate) + { + case "DoNothing": + attributes |= 0x2; // feaIgnoreUpdates + break; + case "EnableOnly": + attributes |= 0x4; // feaEnableOnUpdate + break; + + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "OnUpdate", onupdate, "EnableOnly", "DoNothing")); + break; + } + break; case "Program": - if (null != fileId) + if (fileId != null) { - this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "Program", "File")); + this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "Program", parentElement.Name.LocalName)); } else { @@ -125,22 +191,28 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia port = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "Protocol": - var protocolValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + protocolValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); switch (protocolValue) { + case FirewallConstants.IntegerNotSetString: + break; + case "tcp": - protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP; + protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP.ToString(); break; case "udp": - protocol = FirewallConstants.NET_FW_IP_PROTOCOL_UDP; + protocol = FirewallConstants.NET_FW_IP_PROTOCOL_UDP.ToString(); break; + default: - int parsedProtocol; - if (!Int32.TryParse(protocolValue, out parsedProtocol) || parsedProtocol > 255 || parsedProtocol < 0) + protocol = protocolValue; + if (!this.ParseHelper.ContainsProperty(protocolValue)) { - this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Protocol", protocolValue, "tcp", "udp", "0-255")); + if (!Int32.TryParse(protocolValue, out var parsedProtocol) || parsedProtocol > 255 || parsedProtocol < 0) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Protocol", protocolValue, "tcp", "udp", "0-255")); + } } - protocol = parsedProtocol; break; } break; @@ -167,7 +239,11 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia remoteAddresses = "DefaultGateway"; break; default: - this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Scope", scope, "any", "localSubnet", "DNS", "DHCP", "WINS", "defaultGateway")); + remoteAddresses = scope; + if (!this.ParseHelper.ContainsProperty(scope)) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Scope", scope, "any", "localSubnet", "DNS", "DHCP", "WINS", "defaultGateway")); + } break; } break; @@ -176,19 +252,23 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia switch (profileValue) { case "domain": - profile = FirewallConstants.NET_FW_PROFILE2_DOMAIN; + profile = FirewallConstants.NET_FW_PROFILE2_DOMAIN.ToString(); break; case "private": - profile = FirewallConstants.NET_FW_PROFILE2_PRIVATE; + profile = FirewallConstants.NET_FW_PROFILE2_PRIVATE.ToString(); break; case "public": - profile = FirewallConstants.NET_FW_PROFILE2_PUBLIC; + profile = FirewallConstants.NET_FW_PROFILE2_PUBLIC.ToString(); break; case "all": - profile = FirewallConstants.NET_FW_PROFILE2_ALL; + profile = FirewallConstants.NET_FW_PROFILE2_ALL.ToString(); break; default: - this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Profile", profileValue, "domain", "private", "public", "all")); + profile = profileValue; + if (!this.ParseHelper.ContainsProperty(profileValue)) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Profile", profileValue, "domain", "private", "public", "all")); + } break; } break; @@ -200,6 +280,196 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia ? FirewallConstants.NET_FW_RULE_DIR_OUT : FirewallConstants.NET_FW_RULE_DIR_IN; break; + case "Action": + action = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (action) + { + case "Block": + action = "0"; + break; + case "Allow": + action = "1"; + break; + + default: + if (!this.ParseHelper.ContainsProperty(action)) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Action", action, "Allow", "Block")); + } + break; + } + break; + case "EdgeTraversal": + edgeTraversal = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (edgeTraversal) + { + case "Deny": + edgeTraversal = FirewallConstants.NET_FW_EDGE_TRAVERSAL_TYPE_DENY.ToString(); + break; + case "Allow": + edgeTraversal = FirewallConstants.NET_FW_EDGE_TRAVERSAL_TYPE_ALLOW.ToString(); + break; + case "DeferToApp": + attributes |= 0x8; // feaAddINetFwRule2 + edgeTraversal = FirewallConstants.NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_APP.ToString(); + break; + case "DeferToUser": + attributes |= 0x8; // feaAddINetFwRule2 + edgeTraversal = FirewallConstants.NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_USER.ToString(); + break; + + default: + if (!this.ParseHelper.ContainsProperty(edgeTraversal)) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "EdgeTraversal", edgeTraversal, "Allow", "DeferToApp", "DeferToUser", "Deny")); + } + break; + } + break; + case "Enabled": + enabled = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + if (!this.ParseHelper.ContainsProperty(enabled)) + { + switch (this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + case YesNoType.Yes: + enabled = "1"; + break; + case YesNoType.No: + enabled = "0"; + break; + + default: + this.Messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, element.Name.LocalName, "Enabled", enabled)); + break; + } + } + break; + case "Grouping": + grouping = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "IcmpTypesAndCodes": + icmpTypesAndCodes = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Interface": + interfaceValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + interfaces = interfaceValue; + break; + case "InterfaceType": + interfaceTypeValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (interfaceTypeValue) + { + case "RemoteAccess": + case "Wireless": + case "Lan": + case "All": + break; + + default: + if (!this.ParseHelper.ContainsProperty(interfaceTypeValue)) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "InterfaceType", interfaceTypeValue, "RemoteAccess", "Wireless", "Lan", "All")); + } + break; + } + interfaceTypes = interfaceTypeValue; + break; + case "LocalScope": + localScope = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (localScope) + { + case "any": + localAddresses = "*"; + break; + case "localSubnet": + localAddresses = "LocalSubnet"; + break; + case "DNS": + localAddresses = "dns"; + break; + case "DHCP": + localAddresses = "dhcp"; + break; + case "WINS": + localAddresses = "wins"; + break; + case "defaultGateway": + localAddresses = "DefaultGateway"; + break; + + default: + if (!this.ParseHelper.ContainsProperty(localScope)) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "LocalScope", localScope, "any", "localSubnet", "DNS", "DHCP", "WINS", "defaultGateway")); + } + else + { + localAddresses = localScope; + } + break; + } + break; + case "RemotePort": + remotePort = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Service": + if (serviceName != null) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "Service", parentElement.Name.LocalName)); + } + else + { + service = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + } + break; + case "LocalAppPackageId": + localAppPackageId = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + attributes |= 0x10; // feaAddINetFwRule3 + break; + case "LocalUserAuthorizedList": + localUserAuthorizedList = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + attributes |= 0x10; // feaAddINetFwRule3 + break; + case "LocalUserOwner": + localUserOwner = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + attributes |= 0x10; // feaAddINetFwRule3 + break; + case "RemoteMachineAuthorizedList": + remoteMachineAuthorizedList = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + attributes |= 0x10; // feaAddINetFwRule3 + break; + case "RemoteUserAuthorizedList": + remoteUserAuthorizedList = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + attributes |= 0x10; // feaAddINetFwRule3 + break; + case "IPSecSecureFlags": + secureFlags = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + attributes |= 0x10; // feaAddINetFwRule3 + if (!this.ParseHelper.ContainsProperty(secureFlags)) + { + switch (secureFlags) + { + case "None": + secureFlags = "0"; + break; + case "NoEncapsulation": + secureFlags = "1"; + break; + case "WithIntegrity": + secureFlags = "2"; + break; + case "NegotiateEncryption": + secureFlags = "3"; + break; + case "Encrypt": + secureFlags = "4"; + break; + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "IPSecSecureFlags", secureFlags, "None", "NoEncapsulation", "WithIntegrity", "NegotiateEncryption", "Encrypt")); + break; + } + } + break; default: this.ParseHelper.UnexpectedAttribute(element, attrib); break; @@ -211,7 +481,7 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia } } - // parse RemoteAddress children + // parse children foreach (var child in element.Elements()) { if (this.Namespace == child.Name.Namespace) @@ -219,7 +489,7 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia switch (child.Name.LocalName) { case "RemoteAddress": - if (null != scope) + if (scope != null) { this.Messaging.Write(FirewallErrors.IllegalRemoteAddressWithScopeAttribute(sourceLineNumbers)); } @@ -228,6 +498,37 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia this.ParseRemoteAddressElement(intermediate, section, child, ref remoteAddresses); } break; + case "Interface": + if (interfaceValue != null) + { + this.Messaging.Write(FirewallErrors.IllegalInterfaceWithInterfaceAttribute(sourceLineNumbers)); + } + else + { + this.ParseInterfaceElement(intermediate, section, child, ref interfaces); + } + break; + case "InterfaceType": + if (interfaceTypeValue != null) + { + this.Messaging.Write(FirewallErrors.IllegalInterfaceTypeWithInterfaceTypeAttribute(sourceLineNumbers)); + } + else + { + this.ParseInterfaceTypeElement(intermediate, section, child, ref interfaceTypes); + } + break; + case "LocalAddress": + if (localScope != null) + { + this.Messaging.Write(FirewallErrors.IllegalLocalAddressWithLocalScopeAttribute(sourceLineNumbers)); + } + else + { + this.ParseLocalAddressElement(intermediate, section, child, ref localAddresses); + } + break; + default: this.ParseHelper.UnexpectedElement(element, child); break; @@ -239,54 +540,84 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia } } - if (null == id) + if (id == null) { - id = this.ParseHelper.CreateIdentifier("fex", name, remoteAddresses, componentId); + // firewall rule names are meant to be unique + id = this.ParseHelper.CreateIdentifier("fex", name, componentId); } // Name is required - if (null == name) + if (name == null) { this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name")); } - // Scope or child RemoteAddress(es) are required - if (null == remoteAddresses) + if (service == null) { - this.Messaging.Write(ErrorMessages.ExpectedAttributeOrElement(sourceLineNumbers, element.Name.LocalName, "Scope", "RemoteAddress")); + service = serviceName; } // can't have both Program and File - if (null != program && null != file) + if (program != null && file != null) { 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)) + // Defer to user edge traversal setting can only be used in a firewall rule where program path and TCP/UDP protocol are specified with no additional conditions. + if (edgeTraversal == FirewallConstants.NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_USER.ToString()) { - this.Messaging.Write(FirewallErrors.NoExceptionSpecified(sourceLineNumbers)); - } + if (protocol != null && !(protocol == FirewallConstants.NET_FW_IP_PROTOCOL_TCP.ToString() || protocol == FirewallConstants.NET_FW_IP_PROTOCOL_UDP.ToString())) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, element.Name.LocalName, "Protocol", protocolValue, "tcp,udp")); + } - // Ports can only be specified if the protocol is TCP or UDP. - if (!String.IsNullOrEmpty(port) && protocol.HasValue) - { - switch(protocol.Value) + if (String.IsNullOrEmpty(fileId) && String.IsNullOrEmpty(file) && String.IsNullOrEmpty(program)) { - case FirewallConstants.NET_FW_IP_PROTOCOL_TCP: - case FirewallConstants.NET_FW_IP_PROTOCOL_UDP: - break; + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Program", "EdgeTraversal", "DeferToUser")); + } - default: - this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "Port", "Protocol", protocol.Value.ToString())); - break; + if (port != null) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "Port", "EdgeTraversal", "DeferToUser")); + } + + if (remotePort != null) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "RemotePort", "EdgeTraversal", "DeferToUser")); + } + + if (localScope != null) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "LocalScope", "EdgeTraversal", "DeferToUser")); + } + + if (scope != null) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "Scope", "EdgeTraversal", "DeferToUser")); + } + + if (profile != null) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "Profile", "EdgeTraversal", "DeferToUser")); + } + + if (service != null) + { + if (serviceName != null) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValueWhenNested(sourceLineNumbers, element.Name.LocalName, "EdgeTraversal", "DeferToUser", parentElement.Name.LocalName)); + } + else + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "Service", "EdgeTraversal", "DeferToUser")); + } } } if (!this.Messaging.EncounteredError) { // at this point, File attribute and File parent element are treated the same - if (null != file) + if (file != null) { fileId = file; } @@ -295,28 +626,38 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia { Name = name, RemoteAddresses = remoteAddresses, - Profile = profile ?? FirewallConstants.NET_FW_PROFILE2_ALL, ComponentRef = componentId, Description = description, Direction = direction ?? FirewallConstants.NET_FW_RULE_DIR_IN, + Action = action ?? FirewallConstants.IntegerNotSetString, + EdgeTraversal = edgeTraversal ?? FirewallConstants.IntegerNotSetString, + Enabled = enabled ?? FirewallConstants.IntegerNotSetString, + Grouping = grouping, + IcmpTypesAndCodes = icmpTypesAndCodes, + Interfaces = interfaces, + InterfaceTypes = interfaceTypes, + LocalAddresses = localAddresses, + Port = port, + Profile = profile ?? FirewallConstants.IntegerNotSetString, + Protocol = protocol ?? FirewallConstants.IntegerNotSetString, + RemotePort = remotePort, + ServiceName = service, + LocalAppPackageId = localAppPackageId, + LocalUserAuthorizedList = localUserAuthorizedList, + LocalUserOwner = localUserOwner, + RemoteMachineAuthorizedList = remoteMachineAuthorizedList, + RemoteUserAuthorizedList = remoteUserAuthorizedList, + SecureFlags = secureFlags ?? FirewallConstants.IntegerNotSetString, }); - if (!String.IsNullOrEmpty(port)) + if (String.IsNullOrEmpty(protocol)) { - symbol.Port = port; - - if (!protocol.HasValue) + if (!String.IsNullOrEmpty(port) || !String.IsNullOrEmpty(remotePort)) { - // default protocol is "TCP" - protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP; + symbol.Protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP.ToString(); } } - if (protocol.HasValue) - { - symbol.Protocol = protocol.Value; - } - if (!String.IsNullOrEmpty(fileId)) { symbol.Program = $"[#{fileId}]"; @@ -327,7 +668,7 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia symbol.Program = program; } - if (CompilerConstants.IntegerNotSet != attributes) + if (attributes != CompilerConstants.IntegerNotSet) { symbol.Attributes = attributes; } @@ -382,5 +723,167 @@ private void ParseRemoteAddressElement(Intermediate intermediate, IntermediateSe } } } + + /// + /// Parses an Interface element + /// + /// The element to parse. + private void ParseInterfaceElement(Intermediate intermediate, IntermediateSection section, XElement element, ref string interfaces) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + string name = null; + + // no attributes + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + + if (String.IsNullOrEmpty(name)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name")); + } + else + { + if (String.IsNullOrEmpty(interfaces)) + { + interfaces = name; + } + else + { + interfaces = String.Concat(interfaces, FirewallConstants.FORBIDDEN_FIREWALL_CHAR, name); + } + } + } + + /// + /// Parses an InterfaceType element + /// + /// The element to parse. + private void ParseInterfaceTypeElement(Intermediate intermediate, IntermediateSection section, XElement element, ref string interfaceTypes) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + string value = null; + + // no attributes + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + + if (String.IsNullOrEmpty(value)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Value")); + } + else + { + switch (value) + { + case "RemoteAccess": + case "Wireless": + case "Lan": + case "All": + break; + + default: + if (!this.ParseHelper.ContainsProperty(value)) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Value", value, "RemoteAccess", "Wireless", "Lan", "All")); + value = null; + } + break; + } + + if (String.IsNullOrEmpty(interfaceTypes)) + { + interfaceTypes = value; + } + else if (interfaceTypes.Contains("All")) + { + if (value != "All") + { + this.Messaging.Write(FirewallErrors.IllegalInterfaceTypeWithInterfaceTypeAll(sourceLineNumbers)); + } + } + else if(!String.IsNullOrEmpty(value)) + { + interfaceTypes = String.Concat(interfaceTypes, ",", value); + } + } + } + + /// + /// Parses a RemoteAddress element + /// + /// The element to parse. + private void ParseLocalAddressElement(Intermediate intermediate, IntermediateSection section, XElement element, ref string localAddresses) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + string address = null; + + // no attributes + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + address = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + + if (String.IsNullOrEmpty(address)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Value")); + } + else + { + if (String.IsNullOrEmpty(localAddresses)) + { + localAddresses = address; + } + else + { + localAddresses = String.Concat(localAddresses, ",", address); + } + } + } } } diff --git a/src/ext/Firewall/wixext/FirewallConstants.cs b/src/ext/Firewall/wixext/FirewallConstants.cs index 5ecfe032e..b7ed8728b 100644 --- a/src/ext/Firewall/wixext/FirewallConstants.cs +++ b/src/ext/Firewall/wixext/FirewallConstants.cs @@ -9,6 +9,11 @@ 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"; + internal static readonly XName InterfaceName = Namespace + "Interface"; + internal static readonly XName InterfaceTypeName = Namespace + "InterfaceType"; + internal static readonly XName LocalAddressName = Namespace + "LocalAddress"; + + internal const string IntegerNotSetString = "-2147483648"; // from icftypes.h public const int NET_FW_RULE_DIR_IN = 1; @@ -21,5 +26,17 @@ static class FirewallConstants public const int NET_FW_PROFILE2_PRIVATE = 0x0002; public const int NET_FW_PROFILE2_PUBLIC = 0x0004; public const int NET_FW_PROFILE2_ALL = 0x7FFFFFFF; + + // from icftypes.h + public const int NET_FW_EDGE_TRAVERSAL_TYPE_DENY = 0; + public const int NET_FW_EDGE_TRAVERSAL_TYPE_ALLOW = 1; + public const int NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_APP = 2; + public const int NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_USER = 3; + + /// + /// Firewall rules are stored in the registry.
+ /// The pipe character is used to split firewall rule attributes, so is not permitted in any of them. + ///
+ public const char FORBIDDEN_FIREWALL_CHAR = '|'; } } diff --git a/src/ext/Firewall/wixext/FirewallDecompiler.cs b/src/ext/Firewall/wixext/FirewallDecompiler.cs index 9ddd8a9a3..7fafab175 100644 --- a/src/ext/Firewall/wixext/FirewallDecompiler.cs +++ b/src/ext/Firewall/wixext/FirewallDecompiler.cs @@ -8,6 +8,8 @@ namespace WixToolset.Firewall 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. @@ -16,6 +18,14 @@ 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. /// @@ -32,6 +42,8 @@ public override bool TryDecompileTable(Table table) { switch (table.Name) { + case "WixFirewallException": + case "Wix4FirewallException": case "Wix5FirewallException": this.DecompileWixFirewallExceptionTable(table); break; @@ -57,7 +69,7 @@ public override void PostDecompileTables(TableIndexedCollection tables) /// The table to decompile. private void DecompileWixFirewallExceptionTable(Table table) { - foreach (Row row in table.Rows) + foreach (var row in table.Rows) { var firewallException = new XElement(FirewallConstants.FirewallExceptionName, new XAttribute("Id", row.FieldAsString(0)), @@ -90,13 +102,20 @@ private void DecompileWixFirewallExceptionTable(Table table) firewallException.Add(new XAttribute("Scope", "defaultGateway")); break; default: - FirewallDecompiler.AddRemoteAddress(firewallException, addresses[0]); + if (this.ParseHelper.ContainsProperty(addresses[0])) + { + firewallException.Add(new XAttribute("Scope", addresses[0])); + } + else + { + FirewallDecompiler.AddRemoteAddress(firewallException, addresses[0]); + } break; } } else { - foreach (string address in addresses) + foreach (var address in addresses) { FirewallDecompiler.AddRemoteAddress(firewallException, address); } @@ -110,16 +129,19 @@ private void DecompileWixFirewallExceptionTable(Table table) if (!row.IsColumnEmpty(4)) { - switch (Convert.ToInt32(row[4])) + switch (row.FieldAsString(4)) { - case FirewallConstants.NET_FW_IP_PROTOCOL_TCP: + case FirewallConstants.IntegerNotSetString: + break; + case "6": firewallException.Add(new XAttribute("Protocol", "tcp")); break; - case FirewallConstants.NET_FW_IP_PROTOCOL_UDP: + case "17": firewallException.Add(new XAttribute("Protocol", "udp")); break; + default: - firewallException.Add(new XAttribute("Protocol", row[4])); + firewallException.Add(new XAttribute("Protocol", row.FieldAsString(4))); break; } } @@ -131,26 +153,44 @@ private void DecompileWixFirewallExceptionTable(Table table) if (!row.IsColumnEmpty(6)) { - var attr = Convert.ToInt32(row[6]); - AttributeIfNotNull("IgnoreFailure", (attr & 0x1) == 0x1); + 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 (Convert.ToInt32(row[7])) + switch (row.FieldAsString(7)) { - case FirewallConstants.NET_FW_PROFILE2_DOMAIN: + case FirewallConstants.IntegerNotSetString: + break; + case "1": firewallException.Add(new XAttribute("Profile", "domain")); break; - case FirewallConstants.NET_FW_PROFILE2_PRIVATE: + case "2": firewallException.Add(new XAttribute("Profile", "private")); break; - case FirewallConstants.NET_FW_PROFILE2_PUBLIC: + case "4": firewallException.Add(new XAttribute("Profile", "public")); break; - case FirewallConstants.NET_FW_PROFILE2_ALL: + case "2147483647": firewallException.Add(new XAttribute("Profile", "all")); break; + + default: + firewallException.Add(new XAttribute("Profile", row.FieldAsString(7))); + break; } } @@ -164,8 +204,6 @@ private void DecompileWixFirewallExceptionTable(Table table) switch (Convert.ToInt32(row[10])) { case FirewallConstants.NET_FW_RULE_DIR_IN: - - firewallException.Add(AttributeIfNotNull("Outbound", false)); break; case FirewallConstants.NET_FW_RULE_DIR_OUT: firewallException.Add(AttributeIfNotNull("Outbound", true)); @@ -173,6 +211,224 @@ private void DecompileWixFirewallExceptionTable(Table table) } } + // 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])); + } + else + { + foreach (var interfaceItem in interfaces) + { + FirewallDecompiler.AddInterface(firewallException, interfaceItem); + } + } + } + + if (!row.IsColumnEmpty(17)) + { + string[] interfaceTypes = row.FieldAsString(17).Split(','); + if (interfaceTypes.Length == 1) + { + firewallException.Add(new XAttribute("InterfaceType", interfaceTypes[0])); + } + else + { + foreach (var interfaceType in interfaceTypes) + { + FirewallDecompiler.AddInterfaceType(firewallException, interfaceType); + } + } + } + + 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); } } @@ -183,7 +439,34 @@ private static void AddRemoteAddress(XElement firewallException, string address) new XAttribute("Value", address) ); - firewallException.AddAfterSelf(remoteAddress); + 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) diff --git a/src/ext/Firewall/wixext/FirewallErrors.cs b/src/ext/Firewall/wixext/FirewallErrors.cs index b2dac782a..523398e5d 100644 --- a/src/ext/Firewall/wixext/FirewallErrors.cs +++ b/src/ext/Firewall/wixext/FirewallErrors.cs @@ -12,11 +12,6 @@ public static Message IllegalRemoteAddressWithScopeAttribute(SourceLineNumber so 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 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); @@ -27,10 +22,32 @@ private static Message Message(SourceLineNumber sourceLineNumber, Ids id, Resour return new Message(sourceLineNumber, MessageLevel.Error, (int)id, resourceManager, resourceName, args); } + public static Message IllegalInterfaceWithInterfaceAttribute(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IllegalInterfaceWithInterfaceAttribute, "The Interface element cannot be specified because its parent FirewallException already specified the Interface attribute. To use Interface elements, omit the Interface attribute."); + } + + public static Message IllegalInterfaceTypeWithInterfaceTypeAttribute(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IllegalInterfaceTypeWithInterfaceTypeAttribute, "The InterfaceType element cannot be specified because its parent FirewallException already specified the InterfaceType attribute. To use InterfaceType elements, omit the InterfaceType attribute."); + } + + public static Message IllegalInterfaceTypeWithInterfaceTypeAll(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IllegalInterfaceTypeWithInterfaceTypeAll, "The InterfaceType element cannot be specified because its parent FirewallException contains another InterfaceType element with value 'All'."); + } + public static Message IllegalLocalAddressWithLocalScopeAttribute(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IllegalLocalAddressWithLocalScopeAttribute, "The LocalAddress element cannot be specified because its parent FirewallException already specified the LocalScope attribute. To use LocalAddress elements, omit the LocalScope attribute."); + } + public enum Ids { IllegalRemoteAddressWithScopeAttribute = 6401, - NoExceptionSpecified = 6403, + IllegalInterfaceWithInterfaceAttribute = 6402, + IllegalInterfaceTypeWithInterfaceTypeAttribute = 6404, + IllegalInterfaceTypeWithInterfaceTypeAll = 6405, + IllegalLocalAddressWithLocalScopeAttribute = 6406, } } } diff --git a/src/ext/Firewall/wixext/FirewallTableDefinitions.cs b/src/ext/Firewall/wixext/FirewallTableDefinitions.cs index 26dedbf1d..7a35bb594 100644 --- a/src/ext/Firewall/wixext/FirewallTableDefinitions.cs +++ b/src/ext/Firewall/wixext/FirewallTableDefinitions.cs @@ -13,15 +13,31 @@ public static class FirewallTableDefinitions { new ColumnDefinition("Wix5FirewallException", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, description: "The primary key, a non-localized token.", modularizeType: ColumnModularizeType.Column), new ColumnDefinition("Name", ColumnType.Localized, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Localizable display name.", modularizeType: ColumnModularizeType.Property), - new ColumnDefinition("RemoteAddresses", ColumnType.String, 0, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "Remote address to accept incoming connections from.", modularizeType: ColumnModularizeType.Property), - new ColumnDefinition("Port", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 1, description: "Port number.", modularizeType: ColumnModularizeType.Property), - new ColumnDefinition("Protocol", ColumnType.Number, 1, primaryKey: false, nullable: true, ColumnCategory.Integer, minValue: 0, maxValue: 255, description: "Protocol (6=TCP; 17=UDP). https://www.iana.org/assignments/protocol-numbers"), + new ColumnDefinition("RemoteAddresses", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Remote address to accept incoming connections from.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Port", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 1, maxValue: 65535, description: "Local Port number.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Protocol", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 0, maxValue: 255, description: "Protocol (6=TCP; 17=UDP). https://www.iana.org/assignments/protocol-numbers", modularizeType: ColumnModularizeType.Property), new ColumnDefinition("Program", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Exception for a program (formatted path name).", modularizeType: ColumnModularizeType.Property), - new ColumnDefinition("Attributes", ColumnType.Number, 4, primaryKey: false, nullable: true, ColumnCategory.Unknown, description: "Vital=1"), - new ColumnDefinition("Profile", ColumnType.Number, 4, primaryKey: false, nullable: false, ColumnCategory.Integer, minValue: 1, maxValue: 2147483647, description: "Profile (1=domain; 2=private; 4=public; 2147483647=all)."), + new ColumnDefinition("Attributes", ColumnType.Number, 4, primaryKey: false, nullable: true, ColumnCategory.Unknown, description: "Vital=1; IgnoreUpdates=2; EnableOnChange=4; INetFwRule2=8; INetFwRule3=16"), + new ColumnDefinition("Profile", ColumnType.String, 4, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 1, maxValue: 2147483647, description: "Profile (1=domain; 2=private; 4=public; 2147483647=all).", modularizeType: ColumnModularizeType.Property), new ColumnDefinition("Component_", ColumnType.String, 72, primaryKey: false, nullable: false, ColumnCategory.Identifier, keyTable: "Component", keyColumn: 1, description: "Foreign key into the Component table referencing component that controls the firewall configuration.", modularizeType: ColumnModularizeType.Column), new ColumnDefinition("Description", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Description displayed in Windows Firewall manager for this firewall rule."), - new ColumnDefinition("Direction", ColumnType.Number, 1, primaryKey: false, nullable: true, ColumnCategory.Integer, minValue: 1, maxValue: 2, description: "Direction (1=in; 2=out)"), + new ColumnDefinition("Direction", ColumnType.Number, 1, primaryKey: false, nullable: false, ColumnCategory.Integer, minValue: 1, maxValue: 2, description: "Direction (1=in; 2=out)"), + new ColumnDefinition("Action", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 0, maxValue: 1, description: "Action (0=Block; 1=Allow).", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("EdgeTraversal", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 0, maxValue: 3, description: "Edge traversal (0=Deny; 1=Allow; 2=DeferToApp; 3=DeferToUser).", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Enabled", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 0, maxValue: 1, description: "Enabled (0=Disabled; 1=Enabled).", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Grouping", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "The group to which the rule belongs.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("IcmpTypesAndCodes", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Comma separated list of ICMP types and codes separated by colons.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Interfaces", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "A list of network interfaces separated by a pipe character.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("InterfaceTypes", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Comma separated list of interface types (combination of Wireless,Lan,RemoteAccess or All).", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("LocalAddresses", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Local address to accept incoming connections on.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("RemotePort", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 1, maxValue: 65535, description: "Remote Port number.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("ServiceName", ColumnType.String, 256, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Windows Service short name.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("LocalAppPackageId", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Package identifier or the app container identifier of a process.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("LocalUserAuthorizedList", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "List of authorized local users for an app container.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("LocalUserOwner", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "SID of the user who is the owner of the rule.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("RemoteMachineAuthorizedList", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "List of remote computers which are authorized to access an app container.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("RemoteUserAuthorizedList", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "List of remote users who are authorized to access an app container.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("SecureFlags", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, minValue: 0, maxValue: 1, description: "NET_FW_AUTHENTICATE_TYPE IPsec verification level.", modularizeType: ColumnModularizeType.Property), }, symbolIdIsPrimaryKey: true ); diff --git a/src/ext/Firewall/wixext/Symbols/WixFirewallExceptionSymbol.cs b/src/ext/Firewall/wixext/Symbols/WixFirewallExceptionSymbol.cs index 620de9693..d6a542558 100644 --- a/src/ext/Firewall/wixext/Symbols/WixFirewallExceptionSymbol.cs +++ b/src/ext/Firewall/wixext/Symbols/WixFirewallExceptionSymbol.cs @@ -14,13 +14,29 @@ public static partial class FirewallSymbolDefinitions new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Name), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.RemoteAddresses), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Port), IntermediateFieldType.String), - new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Protocol), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Protocol), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Program), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Attributes), IntermediateFieldType.Number), - new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Profile), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Profile), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.ComponentRef), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Description), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Direction), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Action), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.EdgeTraversal), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Enabled), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Grouping), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.IcmpTypesAndCodes), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.Interfaces), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.InterfaceTypes), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.LocalAddresses), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.RemotePort), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.ServiceName), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.LocalAppPackageId), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.LocalUserAuthorizedList), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.LocalUserOwner), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.RemoteMachineAuthorizedList), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.RemoteUserAuthorizedList), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixFirewallExceptionSymbolFields.SecureFlags), IntermediateFieldType.String), }, typeof(WixFirewallExceptionSymbol)); } @@ -42,6 +58,22 @@ public enum WixFirewallExceptionSymbolFields ComponentRef, Description, Direction, + Action, + EdgeTraversal, + Enabled, + Grouping, + IcmpTypesAndCodes, + Interfaces, + InterfaceTypes, + LocalAddresses, + RemotePort, + ServiceName, + LocalAppPackageId, + LocalUserAuthorizedList, + LocalUserOwner, + RemoteMachineAuthorizedList, + RemoteUserAuthorizedList, + SecureFlags, } public class WixFirewallExceptionSymbol : IntermediateSymbol @@ -74,9 +106,9 @@ public string Port set => this.Set((int)WixFirewallExceptionSymbolFields.Port, value); } - public int? Protocol + public string Protocol { - get => this.Fields[(int)WixFirewallExceptionSymbolFields.Protocol].AsNullableNumber(); + get => this.Fields[(int)WixFirewallExceptionSymbolFields.Protocol].AsString(); set => this.Set((int)WixFirewallExceptionSymbolFields.Protocol, value); } @@ -92,9 +124,9 @@ public int Attributes set => this.Set((int)WixFirewallExceptionSymbolFields.Attributes, value); } - public int Profile + public string Profile { - get => this.Fields[(int)WixFirewallExceptionSymbolFields.Profile].AsNumber(); + get => this.Fields[(int)WixFirewallExceptionSymbolFields.Profile].AsString(); set => this.Set((int)WixFirewallExceptionSymbolFields.Profile, value); } @@ -115,5 +147,101 @@ public int Direction get => this.Fields[(int)WixFirewallExceptionSymbolFields.Direction].AsNumber(); set => this.Set((int)WixFirewallExceptionSymbolFields.Direction, value); } + + public string Action + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.Action].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.Action, value); + } + + public string EdgeTraversal + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.EdgeTraversal].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.EdgeTraversal, value); + } + + public string Enabled + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.Enabled].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.Enabled, value); + } + + public string Grouping + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.Grouping].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.Grouping, value); + } + + public string IcmpTypesAndCodes + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.IcmpTypesAndCodes].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.IcmpTypesAndCodes, value); + } + + public string Interfaces + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.Interfaces].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.Interfaces, value); + } + + public string InterfaceTypes + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.InterfaceTypes].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.InterfaceTypes, value); + } + + public string LocalAddresses + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.LocalAddresses].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.LocalAddresses, value); + } + + public string RemotePort + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.RemotePort].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.RemotePort, value); + } + + public string ServiceName + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.ServiceName].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.ServiceName, value); + } + + public string LocalAppPackageId + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.LocalAppPackageId].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.LocalAppPackageId, value); + } + + public string LocalUserAuthorizedList + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.LocalUserAuthorizedList].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.LocalUserAuthorizedList, value); + } + + public string LocalUserOwner + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.LocalUserOwner].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.LocalUserOwner, value); + } + + public string RemoteMachineAuthorizedList + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.RemoteMachineAuthorizedList].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.RemoteMachineAuthorizedList, value); + } + + public string RemoteUserAuthorizedList + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.RemoteUserAuthorizedList].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.RemoteUserAuthorizedList, value); + } + + public string SecureFlags + { + get => this.Fields[(int)WixFirewallExceptionSymbolFields.SecureFlags].AsString(); + set => this.Set((int)WixFirewallExceptionSymbolFields.SecureFlags, value); + } } -} \ No newline at end of file +} diff --git a/src/ext/Firewall/wixext/WixToolset.Firewall.wixext.csproj b/src/ext/Firewall/wixext/WixToolset.Firewall.wixext.csproj index 8d1dc77e5..098b121cd 100644 --- a/src/ext/Firewall/wixext/WixToolset.Firewall.wixext.csproj +++ b/src/ext/Firewall/wixext/WixToolset.Firewall.wixext.csproj @@ -5,9 +5,11 @@ netstandard2.0 WixToolset.Firewall - WiX Toolset Firewallity Extension + WiX Toolset Firewall Extension WiX Toolset Firewall Extension embedded + false + false diff --git a/src/ext/Util/wixext/UtilCompiler.cs b/src/ext/Util/wixext/UtilCompiler.cs index 5fefed916..f7bb0614d 100644 --- a/src/ext/Util/wixext/UtilCompiler.cs +++ b/src/ext/Util/wixext/UtilCompiler.cs @@ -3123,8 +3123,14 @@ private void ParseServiceConfigElement(Intermediate intermediate, IntermediateSe // if this element is a child of ServiceInstall then ignore the service name provided. if ("ServiceInstall" == parentTableName) { - // TODO: the ServiceName attribute should not be allowed in this case (the overwriting behavior may confuse users) - serviceName = parentTableServiceName; + if (null == serviceName || parentTableServiceName == serviceName) + { + serviceName = parentTableServiceName; + } + else + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "ServiceName", parentTableName)); + } newService = true; } else @@ -3136,7 +3142,8 @@ private void ParseServiceConfigElement(Intermediate intermediate, IntermediateSe } } - this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + var context = new Dictionary() { { "ServiceConfigComponentId", componentId }, { "ServiceConfigServiceName", serviceName } }; + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element, context); if (!this.Messaging.EncounteredError) { diff --git a/src/internal/SetBuildNumber/Directory.Packages.props.pp b/src/internal/SetBuildNumber/Directory.Packages.props.pp index b39eeb787..ead262998 100644 --- a/src/internal/SetBuildNumber/Directory.Packages.props.pp +++ b/src/internal/SetBuildNumber/Directory.Packages.props.pp @@ -47,6 +47,7 @@ + diff --git a/src/test/burn/WixTestTools/Firewall/RuleDetails.cs b/src/test/burn/WixTestTools/Firewall/RuleDetails.cs index d1e53de4f..8c8cdda32 100644 --- a/src/test/burn/WixTestTools/Firewall/RuleDetails.cs +++ b/src/test/burn/WixTestTools/Firewall/RuleDetails.cs @@ -146,7 +146,9 @@ public RuleDetails(INetFwRule3 rule) /// This parameter allows the specification of an array of interface LUIDs (locally unique identifiers) supplied as strings.
/// This is commonly used by USB RNDIS (Remote Network Driver Interface Specification) devices to restrict traffic to a specific non-routable interface.
/// Use netsh trace show interfaces to show a list of local interfaces and their LUIDs.
- /// Example: new object[] { "Wi-Fi", "Local Area Connection* 14" } + /// The interfaces are stored in the registry as GUIDs, but need to be passed to the API as text. eg from the registry
+ /// v2.30|Action=Allow|Active=TRUE|Dir=In|Protocol=6|LPort=23456|IF={423411CD-E627-4A1A-9E1F-C5BE6CD2CC99}|IF={49A98AD0-8379-4079-A445-77066C52E338}|Name=WiXToolset401 Test - 0002|Desc=WiX Toolset firewall exception rule integration test - minimal port properties|
+ /// Example API value: new object[] { "Wi-Fi", "Local Area Connection* 14" } ///
public object[] Interfaces { get; set; } @@ -227,13 +229,13 @@ public RuleDetails(INetFwRule3 rule) public string LocalUserOwner { get; set; } /// - /// This property is optional. It specifies a list of authorized local users for an app container.
+ /// This property is optional. It specifies a list of authorized local users for an app container (using SDDL).
/// Example: "O:LSD:(A;;CC;;;S-1-5-84-0-0-0-0-0)" ///
public string LocalUserAuthorizedList { get; set; } /// - /// This property is optional. It specifies a list of remote users who are authorized to access an app container.
+ /// This property is optional. It specifies a list of remote users who are authorized to access an app container (using SDDL).
///
public string RemoteUserAuthorizedList { get; set; } diff --git a/src/test/msi/TestData/FirewallExtensionTests/CrossVersionMerge/CrossVersionMerge.wixproj b/src/test/msi/TestData/FirewallExtensionTests/CrossVersionMerge/CrossVersionMerge.wixproj new file mode 100644 index 000000000..f1c71d3d7 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/CrossVersionMerge/CrossVersionMerge.wixproj @@ -0,0 +1,13 @@ + + + + 1055;1056 + + + + + + + + + diff --git a/src/test/msi/TestData/FirewallExtensionTests/CrossVersionMerge/Module401.msm b/src/test/msi/TestData/FirewallExtensionTests/CrossVersionMerge/Module401.msm new file mode 100644 index 0000000000000000000000000000000000000000..4dd5bd09872d629b4ccdee9ab67849f2890cf516 GIT binary patch literal 192512 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

_xPn8 z7{2c5+Kto@@k7gzv{|YTMJ|5m%iI6(PoAa|{Lp8OX;|`Kuph#gDYcK_>x}A$WB)Js zA?F?ldMaU}eqv2|CuyV4LJRh!O;A0Ruu=U;zO7LGC{tLn&kQ%|$sV1!3bGAyLK4vv zSpt<9QW*o4lF@w%mCR8+tuH}O`X1Gj|AIb>uWM>Q9PLfmuhaWdIRS0ERQ^Ptv!?P4 z!Jh2xNSh|I7NV!pMJl7B5=<(CA*WCYvL7l*q_Qh2-K6_)+K#E7{HL-`H|!G0_(J6? zL-_KcGLm6@yP~oRnjS&MKushJk!ua(^Gsza!}#%r%8sa{lFF2*^peUIXnUoyAS#`t zG7uUo-8oSyA(0)rp-xEurh9tQ;|2Y2zK*HPhx`|0aa2-EwQ{E+rU z`vaBX(0fri9+mV`Io=>Xk5o@3zrr4#Jf}%6!Ax8K4>N7Kf((#Ki>VBd{3m-LGGfvD zQW*hle^gJ?N%iDE@lWJeRJu&%c;u;Ey5}S@336((2PI}2in|(-3*$k8-5X3DYvAGE*vbr!q%c-&B4|rSMd)N9%*iOvxJ&R5nk>8!|6YITHO} zKF{Ut%jbd0Nk^f)u{J=z71|ly1Czc;Mjzq#4PquO_=lN$8A?yX#1>)J3#@XxPf-p{KGQV!l5$azcxV~-eu=~Qm78mODtC-7 zdNtc-}3%G1i+ z%8Dq)9KYidWx7ha31T6X(UX?y3sZ{iOn%aHsV3c%vQ)?sgEmAI-;PM4V`K1@YaAX$ z{0$6^;Tv%L2y)_$aFudCkYEv6U3W;Y$iB>UB@jpgdS@9L(p zAYYogy=O(wS)2v?`K$FXjR6nb+};+O#?`+&-!`#K3wPsO_Zv&Oy0Ou}U96`xFuA?n zU)!3&t->c1y0)wb_s~=CKds@ow-a9uQdzH|=sk zUe8PTLYC)oH@R#p*Ut^*s>y}pbr4^NIirW| z<;Fy3)F?4?Lih)rD__asCi!>Hf5;#|p3aT4<@-5h&moG~mnwwc5VU9E0q%6?QO^CV zwlH9$D{l7WAb0kx`=k~ZpX!;6sH555hq=d{=De=<5MSB~8M?z_&Jk|I=)J4g&Bd3v zB>I!fhUao^KHdFm&O{k0&n?C(N4agKlh3x9*}%XQB-G+^j&gY3sT5Q^JE-bEY689MWH6uGPp~VSK(edC( z>+_^MzJ7B4{RG!F;!kfMiMxSet!s=NbdqzcvuW3}I|$Fbt6TTgNv`HVqg%Zf9L%Y; zSI;@c6)ag)=9>0gkDYT=u;es%F359GrUYLt3vl|jF#9xjQa(SzRfPOyoFBZJ%i~ry znLh34DT#q8?O|IrKaabUabZ>VeUw+hqZfC2o#7r9e;Xr~I3qom+BJN9hLbqIimEC{ z`|vcIwKn>9ZbF|{`?frlBYd}$dzrIb{H8Gf$TzrO?8mWxrkv&CG7=j%`G)fHbl-o! zQa(5KQ1^4S&XV$rDqJx?pNre_XIk4mRSisP&4Hh*pW`Yo+BW~DqOO6l`?9&$%5z+N z)YXGcenI*|#&W^;QlIdg%s%*7r8Dy&rN?hG4<_JxTmI#3T}9TV^ZrpdAi*a z_rKXNdC3JXV$rMj!kiSHPXBv2Wjkp=ghpje1C@ z7jWBN9~pOJAkvpJwEl=w1)S_CJEy~AHO=94=j{Sud?{%uR77GHr&t~~1K zAD6fzR@DyQexgHrIIv-t?PX4OZI9!UU(uicZu~DdfA9lDSJQlAVrj_@e&iEaoJ`FE za~o2#z>LG!dH({F`%$yN05fV9DA{<8_b;%xJ2eY*`F53e7Z|dgngym0pk{#u)u>ru z^6@Ktc!8N?s99ivl$r&`KKq0BFVJoSH4AhJr)GhkO{iI5!Gl6Ryugt4)GRP!7&Qz0 z;7ZK`6W?Cu!wXE_Pt5|E$pGYsaas|gY&$9fgvlYSs>G!ngyoTqh^8Z zm2-S}fw_yRSztgfY8F^rmzo8J+|TF33oKnr%>rYGQ?o#(H8l%Net(t^FObQl=I>;R zIeU~xh`E@T)rEm|VGaU`tS}gC2z?p458uo`#`_GHyl3|fGR zuqQIT5}0d%&p-)S3fh8iz{+45SPL|-D&}R0wxA=-_8^g6JAn?MGe~5zt{{=cD!|5| z9&7@7f@DqP3pNG)z-C|oh-DQKBQRK2@k|&<%{WqkuD3uL+{yBbEWL5BLRs338T{Aq>!Ux=RsDMI{z7c|RsKjQWmH=ZviG4ugYzBOxMQF1XpL!#s;J1rpy&F>!6N7-GM5Da-1jPzk4T67AL>K zFQ)~F=R|ceHW3W7xF5dz2K54}ReyXl7|H=HupU$cs76qOp`xLNq8It2?RSP60Y$z+ zG4>qJ7ljJH0Cy2Y)>RWR8=!XLQ2O0amX0FE2C5&_0H{?^ zYoK00y@Cp@FJdC0hBgv0F;MkAMT`V$CDbaYjZmART7-!hSE#*EhoQ&|qh=C}Pu=kQ z1KLeJ{1BwXK(!D*q*R9aQwZ{d$|}YPAu)$M5qWxyV3T7RW@}C4X9~u~AdD;5G4{n^ z9P<+~AsC>>^%XI%F(3veh?sK-H!(@XVC&8toGs$pt{uu*j(EBS`UW}{uYUMz|FfMZ zUoyYt^2#Ee)TeLxvzZ2sjEoBVe$u-;PG%w}zjwpehw)*~zKI%D-@!Pd$vrk~5JFpJ4l36Ma&l5+#R1ONf_v?B*8Ei;A=Us zW7*KbL-5-X%*YPo?$a&EIoQXG_{phg*PGZKtYFuk_m>MhB*PT8y$|tMP|+VBwy=Vo zKOYwTv)q0Cd4EL}>;iay#jxYk%cn0OnB1eJqQ79`@mWPXKEBclc0qi6Zz|gH{xB{w zRwCqM$l%B@^dV+}h{?qdSnl|!3psl{x0;ymKh*4u)`58tc157cjvwJM-r~DW-|gZP zX>c-5G7%(xK0fDBF;NM_!{CpMg_&rRCi^jC#)J(*l`zk0iD}uyC4@S+_u|uCTEVV2 zpYAv1?SiPk4;AdFKa7hsz65#>0aN>j*%|l4nEBn^xi<}C1-onLH|~5UoY9!jXX+un zpnEvYSc;RF|J}jVzGTD?Q(w&Y2a}zgSlPp_Tsu?{yW9q1ri{c-+op=xF|3$LMP<92 z$_?Wu|Lttv>>NkNN&dT+CNrrirUZ7GbrEI|{bmglB4#pR?;aN)7wXJPiJuJE;UlMl z9Ze^3_=oGOX{;_VQ84=i@y(qP%s;zZcc<?T7Ie}a4Y{1N+c`LvA5eMtI+`;j~*{KNf7TFUz+=FETL z7tY`BU%nj@`~8LgoW5sQ@u~+@`S3rd|HF-c?OvnkU%Q9i{MYU|Ke_*${;_u}hX2_; z=-t0|_qC$y;D4$gH?xZFKga*EW<~cLR80BuN~>Mb{paxg?JK(fT)yw>RCLe&DgM7y&U zmoJ||WAF?R6uHme)n6~{r=s{h74h-oB7KLLnqlrDeFxJoudHCUhmt^*w!r+<8uJ}g zYnaZ# zCKX3pKw;^UoB_2GN&qC#oGJS)neHc^Cyc5ndiS$lNz$c4(Muvs^L=22$npG0C zK{4OmnTuxrKfZf}yWe1FNt0~!WM+s+D)>7xZAfm2bwCU`(hnaXXPAyc>dAR}OgACr z{1F48W`5xVW4(Bnp}f9ZI5iF7P4pmwC|*x$f?>{)+?cXK!eY(D-@h|9hcrE>1Rp3a za!M$}%==ODv`mnP`)2+z1My?dARP3N)Fu%&!_E1s3t^>H__7Ogs`A#;h=FAMYDJXygzabQpky z;dB<}4~y>65qvd;wMh)cM=sIo5DhO!OCx3ZkIqj>ngSCB;>ed+(oTCa>kz-GU}ECh zq9@>N8sR>!<#J&1!(>|onLb4YZ>Dd=&qT^`b5ztYjOBL_}b!TgD6NB`4IHnj|yXyxYLY`0(%Lji+ry311Yw7 z#QbASxk5E`U3U)uQn0|Jb z{It&`dsEEk44tjDdN0X8 zUjO}R`9BfkKOb~JQ}fRs;{QL9+W)zG{$n$+qxbyBW+3^WOVj^>nEzki97JOB#TUpO zAtVTylr*W4sR-BKp_*0eC?~EgE+ab6@yR@iO#kSb8*3!g`9J)h_?23Dnz;Ml^zUWz zzX$#=tbVY%F7t48$?87;9RK)Le2A_V4Ab>S{~Ho!z>T3d{-gMN;^k|%i$CH!TjOcU z1bIpb=7W&H|6ENlU19Ja2LJ!0+>uhE{=ahlXQxJpf4a;}mymx_&gJL$k?Dd9b$R}Y;3HsJgDZ=N_u)zgzH@BXjc$WfpJ@L}~Bg!#YMzgq&3lfkP5vK9wWW0JZ2 z2u)6j;-6_q{$WRSL`F#7HK8(^A8sOsd}l2AhlNQ;?NebQzf(;?-xe;HFflW(TmzY6 zMt(Z$fiE-+;NPq#XT`VYpA{a!fANBT&4XbMnZH24CjoK8?__WSJ-vu6Nn`oz9xOzN zk37v!p86kxH}C1!> zQ!(7nx%!c(R*VX{AZie5PmLOrGBQws|DmYeL2#2Iol=;j0d1p$6KN&)W@-`Zn z1|m#2Oyt^zuOC3-4D%4!NAWEnmVc*&lmmSmgS3i@b`nS>LvaqF1SQ=WF$6)^7QVD~rQhgygIeI_vl% z%_8(1@|K=ZJ3m@Co;~CHOAuVioynU)KzZG`k+a_PCm;ZZpJmKTQ_noHf;=8|wC zO@Sc6(7bz4TQ^N}a@ulq*ubcm=x{^xr11FWoUw|H9w)BiM3$%|2_lG&H#EnIzq)w* zUNC%Myc-Uyjf;to8Jyr28Z%rsFn+lEC~0#EWFC=&aVWD%_)71Fs3a0SPF#*pAOaFo zY>oeu*yN4*a0nS69y$_o(Ik@@HgVx2aM(>kcvwJOs^JTxu_Q~$bp2^Hln_o^?cKdCJ=bv07WKuw%xs%E1mSCg-~t$Cv<(}=ZIv~sPdcCa>HyHcB_ zJ*K^{E!8^eTIssz`sya=X6t^_W$E_m&g$;zKI&XO3?2a<2_92D(mjrNT=ID6@x{YJ zUs+#U@1k$7@1Y;7pP}EaKc+9$f6=!x=nb6>35H3AG{XTyp5dk8o59A|(AdYAX#CZf zVazmcGoCU2Y5ZvH$whNhxqVzAcb|L4y&{v11kVTpRagm|#HO;l*)u3r8>w9CDGimz zN+(GdNHe8Jq>rSZrM5D?EKD{+Hd&S?+bFw_GIf+UlXsB!m&c++ugM?DKgul?O%!s) z7-dV6Yql=?3Um>UZeR=$8;fU8fYt0yb|0I|o?@@CMeIHHHOojXq>ZG( z$YF$Zv~-+wu5^X;g!HoXmGpyDENd)tlZ}(5$}(gdWZPt?WtU||vU{>uvJbKvau0c1 zc`x}Od8T}k;x|R6Vz=VB;*#RI!dcl;$tnGnLCR=l0@}+mRj3jz=HPMSXZm zJ4>UbBc?QV%ZAW8nn9YvfZ)+vZJV#bEp?a zK3M)M+Fq{wr2Mx0zPwZ}Q|J`K6{8hX6&Z>gMSW#!-J#v9J)}LZJ)^y#{X_di`%PO(XRB+VbJ3;hR_KoE z3UwJCxgNiJ-1m6q@zTRw@1U=*XZ2n6(fZ~3b^0v*Uj6U-Yx>*z2l^-Im+#Os%?*_e zH4KghiJ`fnwLxv*3>^(YhF*pNhDbxaVX|R?VU1yn;h^D^;esL2IMF!6c+z;mc+Geh zE%}?#ol|q3TxYH)m&7G=3%EbH5{?NW?|@>j%Sxneq|Ib$@(sxQW%)#mQAd;~lzGa0 zWr4DpY8cvCf+|rpR@G5^S!=1QrfZ~g*9D-?d_2y2)G{1!C|J>-w1*0YX!mZ=*j!ZK7wUngef;3jI3z?^au4X(NEeMHW}fbif4g<2I&7x27PWhTc5B0OJB)gOY)P&cNxY`YAzZFB>53A#nP z!@BDFmU>Q~iZ-)BpRGTxzp5|Rf6#x^S2i>;xEK@$t)Uaf@ihjO(O~Ri3^wjXo4Ae9 z`-Sm?(VDBv>A1mM7Iy^ii1BaH6yRAs-B1O zXr21B`hj`@@{@znxQb4pGa?tmbieAB>vrobJv1J19;ZE2dVjR`vxZBCPX>EqD~wZJ zjT4M>G4Jd$oQ{<`g-{jlm$K^NVU*r;no5DlUUJ;}iiurw-V!mR7;*O%a(p5QFxk9-e?axd# zL^WDvjFp6mgcagP-Cuj*ACUb(l*p}!fZFlV~EFal&(_mZb&p_843($ z#<3V5rWqF*m!V{{jJq-8-7|hhi>Spl;*==g{+JPFbE~*b+%ELmGSbfRv|lk`i+~S`az0ba4zo0K$N*hb1QoYnm+EyASjYqGZEKQMalu>tIKb}rjb5vGh%u2F7MhH9UX@SY+D&vG!8*&1vMg!e}b15h57 zq(`MUrHy2)%!s)tR5n|-0Q1fs%sBUD4dhMbBe8Kj{g!Jh z>23AH^dt03urhnD|D<;_cpLl;hYXhu4UBHc&o9OzV>PZX`Y&0;M&aI8>^Zikw7YbI zbhUJkR3jUTwbgoAmh6)3wJbzFO}|x>K^Fq(Ib7ZY8>RT39E;8`o8*b%uT;y&cO5WxR(+uyT?*LS+s1t?6rKd zG8M5sQod2SXu~`rJvMsm@Yv^Z)Z>iDBaaszUG@Etk8xOKZba{{ixmiG=wj$&7-|@Y z)nuu$Io4RgTqwy2<_8hJbHp%Ob}ZUeZRsM+OKoL6WSeDMt2Q&`ri#xw1!nj~N~%9zIwf7U)HWUd9F7Hnfr(Sa~qX zBJ744=BE6%qN?%=;(d;u-Vi;#NYhZ;P3w&?SM;@w%Z>YtSB$UG-gJ0ZmA;FyMVgCM zVV*Q!S}c7aEs-)=BIX0)N>!#|b(f>e#cW%kyrwKxmY`NXC>fQN$_{gx1o>c98kMKY zPZfZ@LWn8?vvHy-87se3Rk|uem5J3~jw)A`$B)0osuEQx*2#?83iF7AT7nfJtJbJJ z)qd&#)N_bB0^>&_=9d(8syZDbbEY~=orCpDp1MGNOh@KHq1K}mv5EvmvVMv_ML!!eUz$E0vtNdOtv*w~1?#44eGcaDT>S}s z9%ktReIZs+MVJ*I=u7m^(E8qBek{{726Kaz!Pa1Bus1juoUo>H=I2Wdc0Zm5UxOcd zS^!p8!G^xrAA}hq3{i$y?1~Z%V-3lMDTWloY(pw$)^x*iLxy3kArmXFEJL;-2kWm~ z!wKx0@-fa98m<|N48?da8PDz^&z_ir{G|TU0BI2R3w@;_7#$;|QP>$JNE4-F`PNY& zEtFcxY-M&bdzpjGNhXmw%Uoox7(Wy;4faf)GGCdW%wHBD3z7xP`pWPTED}AlR=P!+ zEj@@fmX8>U(8iug-$=`(=7`H4@i-$61;YCxY!JeQ$)aQlm~p0HL`uVoYpraHEL(O^ zc0!gfE5sh<0rt6XWMwjQ%!l@JC%LoSRj!ch<-T%%tZMpVMH3}Yz?eNnJ{$AZa`{^M z7I`*$=n0Hwh4Lc#1Nk$|UuAN0)T=$}l+>sKwdt$yH`Qen>T#@MieffK`sIqXiYX>Oyr9);iDBZ`5UKbB(RWUgLzB$yKA!=rz6?e@&34 zuO>_rrAg3?)lAXM)}(2cYt~|yl&v|aVM=Lz=F0M91+r_hVp)l-6uU)6ZY8&qJIEz+ z7db1}$UWtLSYHNXZ5e?bbD}(1o`Tg%x;z8B$t-yeb}@PK0_>xUm)o-X&4E zpl4~&tNauJieN?Y_gMv|7DeaUF zN{P}%$zrVaRQf3cl)=gnWrQ*ol8t0#3g&=xWd>H!|9=?IJhgt>0Bx`~L>r-v)h23_ zwJF+EZMrr?n+bVL4p!=U+5+u0Es?yFxp}|*Fr-AMdqmL6t9#^c? z^%!;hG2-;aXcL8zW-LaT*%)D#V|3Yqk>w!vbYqSA#scikVoWu6nG$^c>gB=VAxosFq6%}8uA)j0*Q*Pl?HRWVv$H%jjdA2I%JxkDBR3kEV5wOzLjiRRT7ztCaJ|D^Nh|| z=hZ1^Q;oGY6EPX)tHsF-EBz^~xuoMCd(;np>vJWbqeWliN1HQGTeWF^Cit9Lr^S{Y zqhmhT$(U60Av?(;jGbiWpJ9e0~KKa@pvsioA>Q37JW4Dc+JgxrMX0835x6XPpdBDblhCXGL)A1l6`|RG= zXC;b0gZm%xxazvT>9^UvcHMb$V{D7-Z3E)0>PKI!XLGCJxg|3`RjU?%;_mYLBW34e z7T;(&bm7jZGu8!9D*rOBPNnusUR=3doEKQQ?bPU((+)Io%E`Uc>;2qu7hg>Jv)QcP zn%&<<{;~6V7wgyRlz zc)p#!wnj~hhCb0(y9TV(WJ{jcv92*Mf1l&_mFEseR=>QxyWQ|Q z(~=f-JGGNp@o>8)OZg^c*u1LdhOvKbKX-YkU58WWrv2X7u&P7f&sD#r-03@LL+-u? z*SAEC*QmdaUeNT|)R|p&t1I=cG|~R$`Lq>F=0B13K0m||Z9aIbZ5OvS?(ctlo4De= z&6n}>?t9PdzUbh~m1?)L-v%uFI@_UM+1xK3Q@39=i>ML0<=%n~tIwK0>=Jg~?9(ax zmDgvLbSCQrBl+0L7xMimu~OAWdDkh+j5TG5~DAP)_7watBswRLssowBiMcDpWYRpMMtB>HAiP?fR1Io8DbQ-iW z5%X)=UpeBWI^wS58l7xEWW(B*na-N0wh>Qydke*YncI?ey?nM@+OgnD&#O)c7hE~zd3RTgWB0u^ zuRipVuid@*?2V(+uqp{xT%RAG6aDnScE@%D2fF3HvDg=rSfqRJ?HL|8|Im^u3w{k+ zzfs+*)v~ioP8~G7H=k0PCa+&@Qg7{9_cq5}9M|kzmY!E89Tqb0-Q#I42aEON68hDi z(I?XRwB4)|9S^9w6!zkdXRV#TpkT4yzH9Qa<8>E zxWsL8oKSt-&^{{{J{qt$t;kqxK6;)k-f4)<`iajbOLz~q**VlLVDZ8|+D z?DbmzqpLn_8204r6J1%-&8qW?PQ5-;71W-~$M?^9gY!4`v3&Vr*_f)kg6^h$sp@)a!PV|xMx-lqtUG)=)xyQ~T{pLz z_71-`zgK(w?B5Fq9-cIP{?l|dcVs}n(VSvSJ&n)ih`1%P2D-YA>(*%en_OMWfs56 zNo6ccT+C{=_{l|rr`d;whbF{J$b;9Cz{nwZQDlh3$4iFc^1FYNGrvz=uPq(XX5RbDUs}$5U$|%Xu;aJ7wHvYR*3;#Eoci27vMOU{ z;KZG2pT8BYXmzi&{lw0lAMJh_CW_)p_P72NUnlU%l=Cr{bDYaIhQDj{{_Ole^7c+` z>DAhLlYYqaqqC;zCYu!*A1NPqwBJ^i-a-4w;qt& z%!;NtL?k4{>RPvs35}0+55r0)6e}Hf{Ep80eD@`urZ)Ks9Y~zxD|l=zEQ5RkO^YD1 z25P5t4~dkTcztK{Cw^c-r~cF<)Ox7H(DJZzj*qX zRjHmaS#>7$9x=YNOWeWyf_3SgNA0@vc=)Bet6v6Jdl?~_9&ORu>E$WOz7AU)Usx8t zs1ovgRz}tFJBA{kGOlm~&&f-!CnCO*aItUD|KZ!oGc5&p%i1>eaA(&tISa`P5K((&b0n z+?O4fOYU`94$je zcs+YD|8R?8#m~-H&kh?}a`9>Z*pR{<1q*ARts+?*HM7nFSNZvlHV2#rzdQ48V$qwg zUtR9KE@*HttTgY_tl@=E8vc@#Ry*iJ$KLjCacVugB<+U#h%|PRC0YNUGtEij&kL6B z@n{&oW&V_xt=BSHKQ$Tle{_~0TLBrXU{&9@0x~6xN?|K7@u{>XTfI_c({4yF7Mbxo zkb10Rc}sCko1O#XBQWG<@6{kXSPsCtVAzC|r1!!(r)3RH5*zlO>FljRk7yOJ_zh`8yBq$~Z&!!|u zTz;A)TL}KlYW{0~|LL50V$9jIHT<8C35z}RaLS^LS{XyC44oWNaBFPmx2G;_^**{! z{A=Oc_@EOtkGkIrOGw_iE;HBj#>PI~dagUbUas5u*UVqq)H)wN^+^1&`i;D!&wqQq zi^Qh+3TTIwDhRAW?=q z`&22gjbn)>|MR8!i32Qd&Y$u8^O8fckE*gMQzJ{eCzf5Fi8EsUYS^=yaqX&e$)oD; zZc(r25{Y9|58GGnZtuCL7k?SvQ+{0CeWZJ*UAwL)*!5IfwVknxv#WmaYKq_6uiObLXf^ zE63OJtak9$wL=g6A9(j|a;xdrLYDx)BTVtD)G5l_{?%=hzxF(O&G%O4{vn4Noyxs1 zf7qbmd%MmWm-P7gh=b8yk-buMBW#;x?O8u5dxyDF)9I0ApVSk-bQ{+v`AqE>#oJef z_$=5cS^jw2xkBgJVcwh1Kij$Vwok3?5sJ+%A3p7p?$K|4?!KEPPRZh0ElhK!xCUBE z)d~~f-_DRf_A`zf%Q}85T)eJoVcU@ze;nHXLe{E3s~{6cb#phiRYnV8KC?2*K<}}2 z>>lXW(<{)gedl&TT{^p!>n9a=L6*tv-_EKNmyx&|nonH7PE2LnX82}!PxJi8*$u1B zAB#<4MM)Nvru8J2lbA50{$o6finHXu8;^k`PGYXYKqC`o%oc2Op$x>L+VxCn9X)z< z#as9U1hNyS3O=!BNWxPg|K6IRix7$xPxBGV;_ucB|6Vo{SX+_}e*Y=PQ?unHHrPq< z6aA!zNi&($6gF$4Of&SuFYn{x`)#s6HYoP};i%n(Gxq9y;~NF!-X0yaSt@(|rK`o_ zsFm&3b?dux;ei~dfZ=5eJtlSwskZjdMhof;&Dt>I_6e7BmAiI{KJ{VVwz;YCBYJH} z?Pq=5IwrDt&p<1eqT+2O-j_;`+WR-T9(|?f#n>Bn>K%2SnVn-^P(>aSw9Imz_vZ!K zhj+aU`Sz%$CLlsuTD``ZpjWo5H$JO%?)vFDvjcCxeWH8Wc*604L-ssb@U>P0)!Owp z#96oJzw#-on)OGD>dD1Qr`s*6S9eVK5`CwReG)FO4;bb=wyfikMQaA^tv z)XZB;SNX5~ln{FOmH8zu>h!dHYo{f#_qj`>w@yk*?|C@ud4lEhh3)G(9bMVZefOx- z_u85{Kdc&bV9;z!%}FgAcX7<66D`879&eDeD|v6}rdF)K%GZ;s%d4_=bnHv;4ZP`Sj4cGj3MjR%A8a z@mbM1mFGzH6#I)UBGa~QPi=c*{`O;jTY9iPk5BM?`aH(!Vyj?tt=yTlIdS}14 zm%GjBpnEc{(zMBAn|@t?KgQo>Q!}m7?PM_5M>?r4HlKB8L&V6&{EyD#_=3*gtRl)u zG&3gd#EdP-6s^sqw&moRkR+EgRhCvo)BYjTv) zL{QKJNQamZdPE6LP?{7`6fqX;56|Q0z#IDzd3RY|sy`o^l zt62U$=bS`P{4Vc*?|o~1>#1uv$($*B_UzehCbMU(xE!b0H{UmRhf~?-gli`6s8^^y zHik zbMs`K+av0}RL7UtnO{`jk=vXf_O+mOLEOR_I&Lc+!>Wfj>O0;nJf3@+^2tEc!*;#e zJNNV5Osa>`)qmR&YP9xMrQTKj2W!_KylJEvz2d6Dv$bRP@6BT`Z^&|NZi_Y=>0f?l zT~>tO`L(aOy!-*rRY$LRp1s|z`|umjW{f_v`eG*Qc+1EWl~D>Gmjt+Nn_{P>!djPh z{p5y_W~`;Gc~)3%q=u>1wE54DSY7+P+t}opl3&)Cd9ywkY?~cWeZczGMaQX0bF&U) zESw){dN?JmVe8n88fmRY?A1}X=AW!T{?6+3cvW@R=9*CtBtshZp4yvat9tzMSiaqy z6a`AZ9fuqm2O1u2E%wB`&#vF9a6?Ub)P&2UAI=QFR629oAzf));N@eCMOlykcra^a zuBae)NbT5lSFDz)WU^_6xTUO5>h&leAX`%6P#J6$PRTK9CQMIrm>D0Mw8*v_ScX6*p(8wVOS z68!$E;E$>Re^eo<`)da9M5A&(4z(KTW~rLt7WthNp|U%w#Xt4?GpH^gjKD!;Co_1l zFm#Tb%fUw2%l-eMzDzp@M-1KD0Jkad;no4IYyh8* zK=j5Pui&Oqll9fvwgc(}4$JeCFu;NxW($vh@b z^!{eMuNs689O%eDx*}YY^)^pE#jWpZwIP!^+dg-nty{Bvg|h)oX?2^ZWQ}Y1@AM|t z!e<2*cbBmz99t0j(A9Qvmd75mZFNUdQx8qmeKh7w#bHk95i8xsVBaJ2d7AUjygL4H z$P7lwh0tvG*#jFdiu^1F`ezKBJEn2&i`T59yIfUgv=#~KWA8tn@NUS9>>B~A=EOhA zxR{W3t*X~@AIs;%%wF$Xe98Y%?71Vmf}MY>Uz`)AA6foxn!7-#Texn+suA<%rVZ1a z7`wQCkHN<`?5f&O_C83Eddgz>Rs*fz?oNl*ZaWn0A9yEqtf9-(zx|i@TAd#7=zeAK z_VmAP2MlVm_R-s_-|F)I-HD(nlm*JYw~RWHTmCVqZkl6rcdxqeua1A!Pp37$sSG*Q zpgS!xeY>gnZbiacBU^U;xOV|VmJKpLP*tQ;?;M*oT5WP$sHe+S&c`uV9=`3-pu^{+ z9?AWjb5d;dW=JIktC_X?`YY8NkB;4r8YQ6RST1T{ZsX=ujIFPySS~9#z~X$ojV0=h zo^-fimX^9{=&N$Zvm(`=392qJ`sb_X&eXo0opvSWbkn10hyIMyR$X@@^~IU_=k^Nh z(#~p(o1J~;R@B&#OUy<8TyvvlmkCT{pBq-n3G;}`SA_c6q!eA z5qPAse$aC}YFo%tYG{gl|LxsLYE+C+E%mAWz-QI0=SobzRVD1pHW%9Zq z(!~kb()@G6c5bhjg|$~%pO(^eUaZl${Hig2)!*oY(6Vf$t1$;1`xS<7TPDrmN|W>aTnbWyh*E2{^(t-lPO&U9PZlI&ZH|?Gqis^> zv%}SWTogX>Zd_Pz*|v3f#d(GG+J<{`7u-vIeQEmKOQCtc$2?}mZq>2!&KjBPVIBNw z!2WzSs`|$|+Zy!+X&=-Z=3kyM;9$a*^(R6nsXyI%Ft4U~kio>xLsa&y2RZuMhHi z?Rc(mIC|vL{;nbW{wi1Z>+9e+Zk<+GRq244=@Zq9lfxED_!p|W#aSmm|C(EBw#r$5 zzL!pYk8xWjHyRJ~GO8MGP_h2Sbl=ijD>Rn}4UNtDJxVF`dH&=4y6c1HDRMR_R6dK8 zNPlyCls3QIKyl4^eUpqD$H?f_x+QGQGd}K&%TW$D#trjar?=_7YP zO}CTd9+<~i*D}wC9{OtEE3VbdE_d3O2_8AKax(LF_gHjhRNs-;bC-EfqGvobsr_4d zTdVho`pXBEJw`lbI!;L`{=DVs8c87K&OpP(k>>`kyII+C#<(zO{jpmH>qeg%vaU6H zhxwYrt0{9MDke3ij+vRzT)Ar9sNPEFGB=#>-);KgDfJv5Ps1Y{oLR058idDm1@6^|~HmtvwZu95Y ze*OUpTW(b=_B^=gWy3>J_yQYqeO7CV;$o0tdq9S*CS{naqtk)GVbgDo*!|fe)@tJT z??{Fk(@}hYpA?Cz9@?5RT0~pdG+41HTSL3o_ckBR2)DFBxarAj$b=iQqis)$Fgl1^ zxd?Nj((UYIBJAHHNB>E_!9GNHzdug6iTp-~pEXrJ^?JF}&NZVqS69uxmhZoXziIEE zvp+<(_TKE(*CTq{{5SC*XS~_4Kx1a!pnNU*pkQqcGx2Kv-w}mMGd_Fnn{%i4D{;vm zFRe4?EHu`8@cfS3seUty^sR?^+>2ZC*J!tv>M=JOEo`4(FWE0`9Mqkgonv|Zb`$Tx z?PXa$3e)a-F&EsMo)9s$#VKuL)b5K<=WzCgyiOkRo;6$le)PUD4WoF0lhG0P-O;7B zBWGNG=h7#xx#xCvVqL*$!%L38O}N=Gpzs7o;=ZFoboy}MppyM#1Jb_KEETWZZR7HI zyKi;NUYhpK2+P&xly_MLO9PYphMOhjn$6tQe+1_F#FL#dO)4-qU6$+W-20E#*8B#> z_@-I4#yYn%taknTEdtQDpa$@ z_K4qD=JQ9M>(-mf56fG&487oSPtSLzj{TJ?|DxvgZeMyH z`0eO7j(!1)uqvAfhcxTLH{O$unq-fCe~(qW@{3Ewhf?Zx|C(pHBgYQAt~~$CA4{)K z9F^Q>^vd&#H5ua<8W)~0Q~RLPtB>-GXp@Oz6&N$K!`>d8$-Q9jbgleUgk zSF}**IVbGpyew%gE!=UOh^$0PQQ( z(97j|ZD)g{9*?qYOIrBbE=%4g-ia%Qs?UZ^I(JK>e!}<~e%8l1n5jK2?owHw)xuN3 z{xOD{YMF()$99gn(pQ|VXl#0U6>s>6ls1t>r{-%>&_bK3MRPBCM!%R;wkv3PY`#tY z!-c!+KQBJYG0RF^aV&58)ubNNn+uOhgAPZ&-`T4)>e*L|umvqQ7fwm$-ubgdwAJKe zg?Xmep0(x!iUtofJ8_`a>UnE^N!r?3TgS{R>fwF5 zM#JlVYsXzkYH=#hRd|5u2AQ`_iqqX3d-#5Mg{$Sfy}6sLU#wytaJ|_#m$olPU8~2j zS+}*nlyAHCCgS!{m5-UVb`Mk*v?yGPs{C-(WTt{fU9ytd?G|pbM}cLQ`UvZWnf#N)N+cvQfLI;aw-^{+q2^UsO;31iLNSAhD#R#Ok?T zZ^?(NwvEkqgCDE8>vgcY&}l2~e^+94atw^+t3iLkU?ki`)*G^zHw)S?znK#@9`^> zDoYPb*CvSVa<29A>P2~w`9^h4gM`7K^4L~$z)S4rdCZV)X?wY8V&J)ze;cvZ+duDi zW@6L${zspmzjA4}y?b=Y)Yz9P=X?ueBO*42ja{N?eIRHD_4b&T7p6r$DLx-GvEHGP zcJa>2iT9Q)&A3yxJlB0$+R4UZ`$+WAv zt0J}%ELWUv^jMX+ED`=d$RX5+-P30!ZqLRo4LI#Ui#MQ6*>E=Sp4Vo+ovClQc?JGhQX?nOWzxnmi9hyWajIei<4}alXjJ`^}JT~ zPmW1%&Dh)alv6;zwm|jKu>%dB+w=|=q$obp%F#43EDf_XU65GcGi3A#$#JvYWlU-9 z)ZNk9!_ObR&g|W1e0{Ir=d{xoM7qZ652pJ|hqIfEqEBI?Ckso8<}C`UtoDx1!#-^u zc)DWgmXh=yW^3Qo@rL9Y+P=HJv(&JDfJ|n&f=%Z5$fr zNwXcjXvYqtyf^%FS{;o%IMVfRIt@TBMn5kkSa93x|LZiskIf|F`DGo3=^@xRvSFyf2SXJ+^N5<}910xf6HxKVtlMR)1cY z@r!NqQ~1aF>%7$-IdzEb0q5qoUo#HkKh!`t@|q-@=y z(up^&T=Vo=?`iaWwP>^NzO@(gf_8LQ)=n|^y3siE>A1p{@*x@p&tBUPZF;YKHmk78 z-(BCqCxI8X_u%F+OPaRbbbDHI@3X-1bvr%~AE|%>u$v{c@ zBDQz;*iAj%*{5@jPn3>(7e4ulc~N5WWub}f+w>_3CX#~H*6PpW zuT9vkcQRn3_<-WhjS;&hT0B0KL(5c7hU2dy5HBU3*mPc7>HRX$I8(R5%!;qVp!pIA9(FI!@+nBNG$-&H83}u7CL1JI*`jGPIU*{_`rsnFiykpQLMtDHU$nv9N4|(v^xG z!}pC2bc8UF6GT4x8$J|MBhq4pD2^o%^2EuG}ZKTR&$k%gIfdT38@hb77E?{jh#o ztPS-;y}oqUSYo)R);cJ&TF<`h){LpsHy&&5w`PZsvG?@Fmbnu?a^Jt7w|4etdc?i2 z{Vpt4EAS{e(ydNioVIz9d+AP%cU0CQHDjmamge`tDPFC=IsWm}N`vNGucHnY(}Rcf zsXXhc$Y(w7-Lk3M!Khhh$7~w>Fna1xy}uLR=yi|y6!5@W+4PcA?{bT?_sjlB4KY7| zYFoK?>2|+s=L$y8JvJ`kR9nP(%Po6+j@+ZIwV&FiKBu^96z6X7r2+eXe^mds741gv zp5rHI228B+e!f@PU|K=GLuHdy!kSC}*f-bj#?Tp_-xA;XCo}XMa(hpS}B* zk3JFKzLh6>>-dJ~9l_LHp&Nd~lq%d|rJ>Lp;_LKP&{gOMF^MSf90FI7pxc~u;NB+q znI+strK7-rxH(J(54e%Z7v9hyA3X!0P9XeMQHWF+55GVRfO7Po{*8q@oFM)dyr;u2 zmiSOs0(UPZ!@XCjaL3a$=oxM&gWou(!_^716fzZNE6joReTx;A`WAoPsF1CYr?~2C zp2~8SS-#m{;j?csJeR_^Je@qHRq!k}%k#}vn)@}+HxJ(BeT8~T+p%o;3{Plhqryh` zG#B1LE6^s?`q^Kl?WimSv?x|umMQN%yDf2y|2@1f8gYAPw9rv@Z- z`0NCA^dX_>R}S#k7Lo(BG9I3?cPM|tci&~@Sl{Kxz;_jZ3c-VtD)fWkRa5AR(~K3s zk8UR;o_zrh1dkHr^~GT`gLlsG9X*HP`5JKV9V&zBDMKzQs|e#)hc;D!mXx8MB9u{u zGN`v+c)l6r8$ekLcv`~K8j>j_ODKyzo8x(kaCZj6gc6=K;Youw20|GOk}kBb1K+hE zX+zS5)EiPaNC=NZp-gx9kLJMuT0-MMbA`sV91?2pHzLx$ zf<9}s!yzh*@{}QIL+SzTDZ)DIpqhJ@y`FPR+SIT+GANLr8(Z0MaR zjtgfqc_R2pf{ze_Y>HsxH%Tahi{eGx6joxQ{33cf#ooit(~;)k6Yb#Q;}C7{K=+7t zarUN1)8Rm<51a?}W>Tqes*^m_X-}nLVX&bL?NOROyaFzxPlCJN1d^`RYJUnfmK-uBzrzy~NfWskiWLzj!cW3t5@JLUbQHZo zV+<5Z!s59B+|CfmN=o_xkM>;}&Cl>i>xjp$H5T>1J4%9F67lFBC~gRvGZYSkyeiM1 zC5V$kfPqkSwH?H*h=tcQii8M=LF*DWP!&5_m99;!oDSq4{5W1XnF~c}(Qx@c2cokG z(Y^k`EJ^${I|nBp7dvOVEsaix`(*6sp0-T5CCAps+Z%o!?&0Z0ck-Fh$s~6P{Au>i zOgk?JC%P?_>E&SyR716Ow)dvnx;T2$y&RpXbZ-~xjGq!xm->*8P5vP|m?eUB;Khw) zK`a%>4r7Ll@DB6x3!#Jxr6M*LvWPW<4vpdgIf$=*Uid(XFI}4;xOx%&C#uLXE@rY3 zj>91MiR=?UDG^SWNAObZe-eL*49^rj*4YszDV7%p7bL==mk5aMAxsftvgPzIT98L@ zLT#XOA;QGO7#4f7tgf7;!EK3f;3Y8;6-0$lN2uTryULX=L2MN_&fn|>7|^J;CbPGGQPs(Bm>;&T1Ulj-YyEmu<>;KuRZ?PADQ z#Fiy6&O{-pL%4ilGR`x_B*P30!-lzG@cRxdC$-`fMfzW?9C08%2tSTPW< z3J4nyrUZnZ1V0r_Lv$zMlA*J>;$Toz5jXyUvm+5phe{G5TbS5E^X!`Tfa=?KsMj@3 z3)QphP_Jw96F>zGli#6c*K}mmBaJDMh)_0AreuhGqju`A(WB&tc zedpu*W&e@E?ta;SU9ob1*?<4w6!~TU{j&es*{;9rzfQt3|FZwMzwAGWm^28Z1EqtfqoqDj`3o7ToJN2Fkvi~L|Pd(m^2B@4K^6+7%vus8ww-{7Az(WOJqp} z5GO>02}M{Ugy`jpD9F790%t)W#O4rT0vHgg#N{VR(kS3Dh9W!! z95$JoW`nUXfly!zk!|sS(H!tQOy&wG<9u+q;YVTq#2Xw84o?hTv@}crU{eT-2xAe^ zgpt33#YWl-c&*t&@R`KPTp6JEi?LXdkWWEY6Trid0pL+UT|DA1VJcx3j&5O|!3>P% z{t4ML$PG|~vcENE^VA)mz)Ffcne z7%sY4f`Qq)B}rovd2Gm}**n=g(;e-d+`tQGNrBOF!4-)xf}HL`Oaf?0g?ek zU_fWEm4QM;1ANG)6WhSJa4@)u0I?VYQ~|;`9njVuJMA#n33gxz$XkPm2tDx`#H4s< z&^wQCUEym6Kn7}y;R3;v0iEj>jnrTAU8?cH;0Rje$ORA`03M zKuZh_1dTNYB72K53%nS2j^92%<1^1DmfPrBii7dfnd{U$WSXd|$ zy6i0lMYBe|aJX!k1qh2nZarXr9Kasnlh3jhbCX!GnS&-h7UKf~5$y;tXhpUdWJ5v} z7Q>TZv5BlWF(%@o$w2c3+4wM6a;qD&Na4XEK;$axnqUP03zr2Gjhaakit#N6G<*O7 zT8fAE#FjGuC5E>FeI${NN7Ni2_U2d^3Euv7uGU2q}-llp%gZIGlNw3V5(zgBS`@!NJ2gYSdj+#F4zFb0t=A|2p~ zc=hk#g%KfV6YK_(p_VWq?nbdew1TJ%;%R#W8)YJTA$F61lDlnp;ta5h!>DuaBMK3 zG`v&f#fCLYE@Upe5pm;KA`UPTF@hd-ZBF>1$#yZ8SS)1o@UmSJ z0=(j56>}3~2hXo+M_f;bAX>w<)y zOiCkgLC=6Q3&gIgd@+#uWGirEB}^qQ20+KbX`0Y4ImFg0Sxn9b+lv_+f|%tah3}|O zoT-Bf%@To@A^?_4?EI5*Kq|m_5UCI%sBnS-R>~Fe*ccn%la!+lwg8am7KnlG;Cvet za(w$5A1Gq;=rvrWiK468$d)0&stM6eA$B>*{Y_X-9TiDABOse42?@1{tG`5?Jw&)L zsjy*Z1Cy2nyQ9EG2_}I$Mw8tEa3<2Qe=;6el`t*9+3;56z@sJC9&Xvv!ngNe9|H`b zP6&&KNBo0<6D*h{a;_i_cnfL~aR8uE;QJ!X%isSSop4ie?Gnk!lz1KpP4Q%2l3dQB zY!FZ#1QFEmLBRwmAb(J^?OG70ffkrb@L-T#Jm6}02?7jUAOY&Y9s%isBoX4UXf}CV zu?LI8#KkodCzL?kE+H_4m^P||#2|DhM%pEz0>LN=m{C4KpumujE{Cr)f>vRnVVFOc zl?;{}t~A0+F<5p;rzR`?k6;6dOK8A; z_|CZng+j3eTL6X4q~JLu=fT&jjQf1w4lH}HhGB}JCmG8UiDhCIHvtLjNG`{~Izsj` z;#WAskdkIS(&gK~Bd7@%Wdi5~jB_A=(0nktrcI0tZN$sj451?6)CMdIRg?1@~?EeBG&0{$Hjo8)6yBA!qR?1b1i$60+l z+XAs@sspPY~2-(=E?;4$MEff4asAZUVj5SeIY zBNqnJW5FB4<4gIZf)31Hn8M|NKjz0w00WVN3^6vCtS5p4!+IoEF)o5|vj)!KOeXv| zX#R+|?OH&B0G-PaBc}@kng{s?)6WPaMI6IZriwF=MZ*XNC4)hll?;E<8f1L){4j_k z8;o$|@9_kBVEDseh)-w&k*bC_B4#iJ_(?HF1TT@}3DhKi4f-tJO?xTB02c_pVa@O) zjCw{WvW-EMd;>bv%gY}h2D$1OUPADTaAn`nWh1~5xr}cIxJWM(8*)hx7ZMS;rHMEs a$P~l^p + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/FirewallRulesInterfaces.wixproj b/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/FirewallRulesInterfaces.wixproj new file mode 100644 index 000000000..3c6ef5cf9 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/FirewallRulesInterfaces.wixproj @@ -0,0 +1,13 @@ + + + + {F153C27F-0236-4A0F-ADB3-50BFC73B4FEA} + true + + + + + + + + \ No newline at end of file diff --git a/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/product.wxs b/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/product.wxs new file mode 100644 index 000000000..142c8f68d --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/product.wxs @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesProperties/FirewallRulesProperties.wixproj b/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesProperties/FirewallRulesProperties.wixproj new file mode 100644 index 000000000..38d94265a --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesProperties/FirewallRulesProperties.wixproj @@ -0,0 +1,13 @@ + + + + {DC2C3CDC-112F-40A8-A7B4-2C7B758F4F94} + true + + + + + + + + \ No newline at end of file diff --git a/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesProperties/product.wxs b/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesProperties/product.wxs new file mode 100644 index 000000000..1f9935d59 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/FirewallRulesProperties/product.wxs @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/msi/TestData/FirewallExtensionTests/Module401/Module401.wixproj b/src/test/msi/TestData/FirewallExtensionTests/Module401/Module401.wixproj new file mode 100644 index 000000000..5f65e657d --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/Module401/Module401.wixproj @@ -0,0 +1,10 @@ + + + + Module + 1072 + + + + + \ No newline at end of file diff --git a/src/test/msi/TestData/FirewallExtensionTests/Module401/data/test.txt b/src/test/msi/TestData/FirewallExtensionTests/Module401/data/test.txt new file mode 100644 index 000000000..cd0db0e19 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/Module401/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/msi/TestData/FirewallExtensionTests/Module401/module.wxs b/src/test/msi/TestData/FirewallExtensionTests/Module401/module.wxs new file mode 100644 index 000000000..872743c7e --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/Module401/module.wxs @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/ModuleCurrent.wixproj b/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/ModuleCurrent.wixproj new file mode 100644 index 000000000..8a84280c7 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/ModuleCurrent.wixproj @@ -0,0 +1,10 @@ + + + + Module + 1072 + + + + + \ No newline at end of file diff --git a/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/data/test.txt b/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/data/test.txt new file mode 100644 index 000000000..cd0db0e19 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/module.wxs b/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/module.wxs new file mode 100644 index 000000000..53097acd4 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/ModuleCurrent/module.wxs @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/msi/TestData/FirewallExtensionTests/NestedService/NestedService.wixproj b/src/test/msi/TestData/FirewallExtensionTests/NestedService/NestedService.wixproj new file mode 100644 index 000000000..4a9c9dbd2 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/NestedService/NestedService.wixproj @@ -0,0 +1,14 @@ + + + + {80635798-F904-4C4E-81D8-27A5106F3998} + true + + + + + + + + + \ No newline at end of file diff --git a/src/test/msi/TestData/FirewallExtensionTests/NestedService/product.wxs b/src/test/msi/TestData/FirewallExtensionTests/NestedService/product.wxs new file mode 100644 index 000000000..24559a332 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/NestedService/product.wxs @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs index ce55aa142..380e6f4cc 100644 --- a/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs +++ b/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs @@ -4,6 +4,8 @@ namespace WixToolsetTest.MsiE2E { using System; using System.IO; + using System.Linq; + using System.Net.NetworkInformation; using NetFwTypeLib; using WixTestTools; using WixTestTools.Firewall; @@ -37,8 +39,8 @@ public void CanInstallAndUninstallFirewallRulesWithMinimalProperties() ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("FirewallRules", "product.wxs")), Description = "WiX Toolset firewall exception rule integration test - minimal app properties", Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, - EdgeTraversal = true, - EdgeTraversalOptions = 1, + EdgeTraversal = false, + EdgeTraversalOptions = 0, Enabled = true, InterfaceTypes = "All", LocalAddresses = "*", @@ -124,8 +126,8 @@ public void DisabledApplicationFirewallRuleIsEnabledAfterRepair() ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("FirewallRules", "product.wxs")), Description = "WiX Toolset firewall exception rule integration test - minimal app properties", Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, - EdgeTraversal = true, - EdgeTraversalOptions = 1, + EdgeTraversal = false, + EdgeTraversalOptions = 0, Enabled = true, InterfaceTypes = "All", LocalAddresses = "*", @@ -187,8 +189,8 @@ public void MissingApplicationFirewallRuleIsAddedAfterRepair() ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("FirewallRules", "product.wxs")), Description = "WiX Toolset firewall exception rule integration test - minimal app properties", Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, - EdgeTraversal = true, - EdgeTraversalOptions = 1, + EdgeTraversal = false, + EdgeTraversalOptions = 0, Enabled = true, InterfaceTypes = "All", LocalAddresses = "*", @@ -213,8 +215,8 @@ public void FirewallRulesUseFormattedStringProperties() ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("DynamicFirewallRules", "product.wxs")), Description = "WiX Toolset firewall exception rule integration test - dynamic app description 9999", Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, - EdgeTraversal = true, - EdgeTraversalOptions = 1, + EdgeTraversal = false, + EdgeTraversalOptions = 0, Enabled = true, InterfaceTypes = "All", LocalAddresses = "*", @@ -255,8 +257,8 @@ public void FirewallRulesUseFormattedStringProperties() ApplicationName = Path.Combine(Environment.GetEnvironmentVariable("windir"), "system32", "9999.exe"), Description = "WiX Toolset firewall exception rule integration test - dynamic Name 9999", Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, - EdgeTraversal = true, - EdgeTraversalOptions = 1, + EdgeTraversal = false, + EdgeTraversalOptions = 0, Enabled = true, InterfaceTypes = "All", LocalAddresses = "*", @@ -285,10 +287,10 @@ public void SucceedWhenIgnoreOnFailureIsSet() var log1 = product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0006 pipe")); - Assert.True(LogVerifier.MessageInLogFile(log1, "failed to add app to the authorized apps list")); + Assert.True(LogVerifier.MessageInLogFile(log1, "failed to add firewall exception 'WiXToolset401 Test - 0006 pipe' to the list")); Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0007 pipe")); - Assert.True(LogVerifier.MessageInLogFile(log1, "failed to add app to the authorized ports list")); + Assert.True(LogVerifier.MessageInLogFile(log1, "failed to add firewall exception 'WiXToolset401 Test - 0007 pipe' to the list")); var expected = new RuleDetails("WiXToolset401 Test - 0008 removal") { @@ -296,8 +298,8 @@ public void SucceedWhenIgnoreOnFailureIsSet() ApplicationName = "test.exe", Description = "WiX Toolset firewall exception rule integration test - removal test", Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, - EdgeTraversal = true, - EdgeTraversalOptions = 1, + EdgeTraversal = false, + EdgeTraversalOptions = 0, Enabled = true, InterfaceTypes = "All", LocalPorts = "52390", @@ -313,7 +315,7 @@ public void SucceedWhenIgnoreOnFailureIsSet() Verifier.RemoveFirewallRuleByName("WiXToolset401 Test - 0008 removal"); var log2 = product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, "NORULENAME=1"); - Assert.True(LogVerifier.MessageInLogFile(log2, "failed to remove firewall rule")); + Assert.True(LogVerifier.MessageInLogFile(log2, "failed to remove firewall exception for name")); } [RuntimeFact] @@ -370,8 +372,8 @@ public void VarietyOfProtocolValuesCanBeUsed() ApplicationName = "test.exe", Description = "WiX Toolset firewall exception rule integration test - ports can only be specified if protocol is TCP or UDP", Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, - EdgeTraversal = true, - EdgeTraversalOptions = 1, + EdgeTraversal = false, + EdgeTraversalOptions = 0, Enabled = true, InterfaceTypes = "All", LocalAddresses = "*", @@ -532,5 +534,609 @@ public void FullSetOfScopeValuesCanBeUsed() Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0016")); Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0017")); } + + [RuntimeFact] + public void CanInstallAndUninstallFirewallRulesWithInterfaces() + { + var names = NetworkInterface.GetAllNetworkInterfaces() + .Take(3) + .Select(ni => ni.Name); + + var props = names.Select((name, idx) => $"INTERFACE{idx + 1}=\"{name}\"") + .Concat(new[] { "INTERFACETYPE=Lan" }).ToArray(); + + var product = this.CreatePackageInstaller("FirewallRulesInterfaces"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, props); + + var expected1 = new RuleDetails("WiXToolset500 Test - 0028") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("FirewallRulesInterfaces", "product.wxs")), + Description = "WiX Toolset firewall exception rule integration test - three interfaces", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "Lan,Wireless,RemoteAccess", + Interfaces = names.ToArray(), + LocalAddresses = "*", + Profiles = Int32.MaxValue, + Protocol = 256, + RemoteAddresses = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset500 Test - 0028", expected1); + + var expected2 = new RuleDetails("WiXToolset500 Test - 0029") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - one interface", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "Lan", + Interfaces = names.Take(1).ToArray(), + LocalAddresses = "*", + LocalPorts = "29292", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset500 Test - 0029", expected2); + + props = names.Take(1).Select((name, idx) => $"INTERFACE{idx + 2}=\"{name}\"").ToArray(); + + product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, props); + + var expected3 = new RuleDetails("WiXToolset500 Test - 0028") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("FirewallRulesInterfaces", "product.wxs")), + Description = "WiX Toolset firewall exception rule integration test - three interfaces", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "Lan,Wireless,RemoteAccess", + Interfaces = names.Take(1).ToArray(), + LocalAddresses = "*", + Profiles = Int32.MaxValue, + Protocol = 256, + RemoteAddresses = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset500 Test - 0028", expected3); + + var expected4 = new RuleDetails("WiXToolset500 Test - 0029") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - one interface", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "29292", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset500 Test - 0029", expected4); + + product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // verify the firewall exceptions have been removed. + Assert.False(Verifier.FirewallRuleExists("WiXToolset500 Test - 0028")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset500 Test - 0029")); + } + + [RuntimeFact] + public void CanInstallAndUninstallFirewallRulesPackagedByDifferentModules() + { + var product = this.CreatePackageInstaller("CrossVersionMerge"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // Validate new firewall exception details. + var expected1 = new RuleDetails("WiXToolset401 Test - 0018") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles(x86)"), "MsiPackage", "file1.txt"), + Description = "WiX Toolset firewall exception rule integration test - module 401 MergeRedirectFolder - app", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = true, + EdgeTraversalOptions = 1, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "40101", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0018", expected1); + + var expected2 = new RuleDetails("WiXToolset401 Test - 0019") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - module 401 MergeRedirectFolder - port", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "40102", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0019", expected2); + + var expected3 = new RuleDetails("WiXToolset401 Test - 0020") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles(x86)"), "MsiPackage", "file2.txt"), + Description = "WiX Toolset firewall exception rule integration test - module 401 NotTheMergeRedirectFolder - app", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = true, + EdgeTraversalOptions = 1, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "40103", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0020", expected3); + + var expected4 = new RuleDetails("WiXToolset401 Test - 0021") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - module 401 NotTheMergeRedirectFolder - port", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "40104", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0021", expected4); + + var expected5 = new RuleDetails("WiXToolset Test - 0022") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles(x86)"), "MsiPackage", "file1.txt"), + Description = "WiX Toolset firewall exception rule integration test - module MergeRedirectFolder - app", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "50001", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0022", expected5); + + var expected6 = new RuleDetails("WiXToolset Test - 0023") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - module MergeRedirectFolder - port", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "50002", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0023", expected6); + + var expected7 = new RuleDetails("WiXToolset Test - 0024") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles(x86)"), "MsiPackage", "file2.txt"), + Description = "WiX Toolset firewall exception rule integration test - module NotTheMergeRedirectFolder - app", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "50003", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0024", expected7); + + var expected8 = new RuleDetails("WiXToolset Test - 0025") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - module NotTheMergeRedirectFolder - port", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "50004", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0025", expected8); + + var expected9 = new RuleDetails("WiXToolset Test - 0026") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles(x86)"), "MsiPackage", "package.wxs"), + Description = "WiX Toolset firewall exception rule integration test - package app", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "20001", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0026", expected9); + + var expected10 = new RuleDetails("WiXToolset Test - 0027") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - package port", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "20002", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0027", expected10); + + product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // verify the firewall exceptions have been removed. + Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0018")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0019")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0020")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0021")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0022")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0023")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0024")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0025")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0026")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0027")); + } + + [RuntimeFact] + public void ServiceNameIsPassedIntoNestedRules() + { + var product = this.CreatePackageInstaller("NestedService"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + var expected1 = new RuleDetails("WiXToolset Test - 0031") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "*", + RemoteAddresses = "*", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + Description = "WiX Toolset firewall exception rule integration test - service property", + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + Profiles = Int32.MaxValue, + SecureFlags = 0, + ServiceName = "Spooler", + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0031", expected1); + + var expected2 = new RuleDetails("WiXToolset Test - 0032") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "*", + RemoteAddresses = "*", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + Description = "WiX Toolset firewall exception rule integration test - ServiceConfig", + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + Profiles = Int32.MaxValue, + SecureFlags = 0, + ServiceName = "Spooler", + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0032", expected2); + + var expected3 = new RuleDetails("WiXToolset Test - 0033") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "*", + RemoteAddresses = "*", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + Description = "WiX Toolset firewall exception rule integration test - ServiceInstall", + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + Profiles = Int32.MaxValue, + SecureFlags = 0, + ServiceName = "WixTestFirewallSrv", + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0033", expected3); + + product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // verify the firewall exceptions have been removed. + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0031")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0032")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0033")); + } + + [RuntimeFact] + public void SucceedWhenEnableOnlyFlagIsSet() + { + var product = this.CreatePackageInstaller("FirewallRulesProperties"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + var expected1 = new RuleDetails("WiXToolset Test - 0028") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "*", + RemoteAddresses = "*", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + Profiles = Int32.MaxValue, + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0028", expected1); + + Verifier.DisableFirewallRule("WiXToolset Test - 0028"); + + var args = new[] + { + "LOCALPORT=3456", + "PROTOCOL=6", + "PROGRAM=ShouldBeUnchanged", + "PROFILE=2", + "DESCRIPTION=ShouldBeUnchanged", + "REMOTESCOPE=ShouldBeUnchanged", + "EDGETRAVERSAL=3", + "ENABLED=1", + "GROUPING=ShouldBeUnchanged", + "ICMPTYPES=ShouldBeUnchanged", + "INTERFACE=ShouldBeUnchanged", + "INTERFACETYPE=ShouldBeUnchanged", + "LOCALSCOPE=ShouldBeUnchanged", + "REMOTEPORT=60000", + "SERVICE=ShouldBeUnchanged", + "PACKAGEID=ShouldBeUnchanged", + "LOCALUSERS=ShouldBeUnchanged", + "LOCALOWNER=ShouldBeUnchanged", + "REMOTEMACHINES=ShouldBeUnchanged", + "REMOTEUSERS=ShouldBeUnchanged", + "SECUREFLAGS=15", + "REMOTEADDRESS=ShouldBeUnchanged", + "LOCALADDRESS=ShouldBeUnchanged", + }; + + product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, args); + + var expected2 = new RuleDetails("WiXToolset Test - 0028") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "*", + RemoteAddresses = "*", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + Profiles = Int32.MaxValue, + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0028", expected2); + + product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // verify the firewall exceptions have been removed. + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0028")); + } + + [RuntimeFact] + public void SucceedWhenDoNothingFlagIsSet() + { + var product = this.CreatePackageInstaller("FirewallRulesProperties"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + var expected1 = new RuleDetails("WiXToolset Test - 0029") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "*", + RemoteAddresses = "*", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + Profiles = Int32.MaxValue, + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0029", expected1); + Verifier.DisableFirewallRule("WiXToolset Test - 0029"); + + var args = new[] + { + "INTERFACE=ShouldBeUnchanged", + "INTERFACETYPE=ShouldBeUnchanged", + "REMOTEADDRESS=ShouldBeUnchanged", + "LOCALADDRESS=ShouldBeUnchanged", + }; + + product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, args); + + var expected2 = new RuleDetails("WiXToolset Test - 0029") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "*", + RemoteAddresses = "*", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = false, // remains as disabled after the repair + InterfaceTypes = "All", + Profiles = Int32.MaxValue, + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0029", expected2); + + product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // verify the firewall exceptions have been removed. + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0029")); + } + + [RuntimeFact] + public void SucceedWhenNoFlagIsSet() + { + var product = this.CreatePackageInstaller("FirewallRulesProperties"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + var expected1 = new RuleDetails("WiXToolset Test - 0030") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "*", + RemoteAddresses = "*", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + Profiles = Int32.MaxValue, + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0030", expected1); + Verifier.DisableFirewallRule("WiXToolset Test - 0030"); + + var names = NetworkInterface.GetAllNetworkInterfaces() + .Take(2) + .Select(ni => ni.Name); + + var args = names.Select((name, idx) => $"INTERFACE{idx + 1}=\"{name}\"") + .Concat(new[] + { + "INTERFACETYPE1=Wireless", + "INTERFACETYPE2=Lan", + "REMOTEADDRESS1=DHCP", + "REMOTEADDRESS2=LocalSubnet", + "LOCALADDRESS1=127.0.0.1", + "LOCALADDRESS2=192.168.1.1", + }) + .ToArray(); + + product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, args); + + var expected2 = new RuleDetails("WiXToolset Test - 0030") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Protocol = 256, + LocalAddresses = "127.0.0.1/255.255.255.255,192.168.1.1/255.255.255.255", + RemoteAddresses = "LocalSubnet,DHCP", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + Description = "", + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + Interfaces = names.ToArray(), + InterfaceTypes = "Lan,Wireless", + Profiles = Int32.MaxValue, + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset Test - 0030", expected2); + + product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // verify the firewall exceptions have been removed. + Assert.False(Verifier.FirewallRuleExists("WiXToolset Test - 0030")); + } } } diff --git a/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj b/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj index a5536de43..7d4695d33 100644 --- a/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj +++ b/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj @@ -29,6 +29,7 @@ +