From 51808f8249eca70c098b828f411749598acf3937 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 | 449 +++++++- .../UsingFirewall/PackageComponents.wxs | 35 + .../UsingProperties/Package.en-us.wxl | 9 + .../TestData/UsingProperties/Package.wxs | 15 + .../UsingProperties/PackageComponents.wxs | 53 + .../TestData/UsingProperties/example.txt | 1 + src/ext/Firewall/wixext/FirewallCompiler.cs | 619 +++++++++-- src/ext/Firewall/wixext/FirewallConstants.cs | 17 + src/ext/Firewall/wixext/FirewallDecompiler.cs | 318 +++++- src/ext/Firewall/wixext/FirewallErrors.cs | 29 +- .../wixext/FirewallTableDefinitions.cs | 28 +- .../Symbols/WixFirewallExceptionSymbol.cs | 142 ++- src/ext/Util/wixext/UtilCompiler.cs | 13 +- .../Directory.Packages.props.pp | 1 + .../burn/WixTestTools/Firewall/RuleDetails.cs | 8 +- .../FirewallRulesInterfaces.wixproj | 13 + .../FirewallRulesInterfaces/product.wxs | 32 + .../FirewallExtensionTests.cs | 141 ++- .../WixToolsetTest.MsiE2E.csproj | 1 + 20 files changed, 2560 insertions(+), 344 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/FirewallRulesInterfaces/FirewallRulesInterfaces.wixproj create mode 100644 src/test/msi/TestData/FirewallExtensionTests/FirewallRulesInterfaces/product.wxs diff --git a/src/ext/Firewall/ca/firewall.cpp b/src/ext/Firewall/ca/firewall.cpp index eed6f9df9..3a4ff08fc 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, feaEnableOnChange = 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 fEnableOnChange = feaEnableOnChange == (attrs.iAttributes & feaEnableOnChange); + 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 (!fIgnoreUpdates) + { + // overwrite attributes of the existing firewall rule + hr = UpdateFwRuleObject(pNetFwRule, TRUE, attrs); + ExitOnFailure(hr, "failed to update INetFwRule firewall exception '%ls'", attrs.pwzName); + + if (fAddINetFwRule2) + { + hr = UpdateFwRule2Object(pNetFwRule, TRUE, attrs); + ExitOnFailure(hr, "failed to update INetFwRule2 firewall exception '%ls'", attrs.pwzName); + } - // enable it (just in case it was disabled) - pNetFwRule->put_Enabled(VARIANT_TRUE); + if (fAddINetFwRule3) + { + hr = UpdateFwRule3Object(pNetFwRule, TRUE, attrs); + ExitOnFailure(hr, "failed to update INetFwRule3 firewall exception '%ls'", attrs.pwzName); + } + } + else if (fEnableOnChange) + { + hr = pNetFwRule->put_Enabled(VARIANT_TRUE); + ExitOnFailure(hr, "failed to enable existing 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..2ecb9c064 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]\t0\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\t0\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]\t0\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\t0\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,7 @@ public void RoundtripAttributesAreCorrectForApp() "Port=42", "Protocol=tcp", "Program=[#filNdJBJmq3UCUIwmXS8x21aAsvqzk]", - "Profile=all", "Description=An app-based firewall exception", - "Outbound=no", "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", }, actual.Attributes); } @@ -111,12 +235,11 @@ 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", "Description=A port-based firewall exception", "Outbound=yes", "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall", @@ -129,14 +252,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 +268,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 +285,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 +302,288 @@ 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); + } + + [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); + } + + [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 +605,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 +622,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..4ea2a2d72 100644 --- a/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs +++ b/src/ext/Firewall/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs @@ -16,6 +16,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/wixext/FirewallCompiler.cs b/src/ext/Firewall/wixext/FirewallCompiler.cs index ed49ba9c4..a0a65be10 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,27 @@ 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 "IgnoreUpdates": + if (this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes) + { + attributes |= 0x2; // feaIgnoreUpdates + } + break; + case "EnableOnChange": + if (this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes) + { + attributes |= 0x4; // feaEnableOnChange + } + 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 +187,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 +235,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 +248,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 +276,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 +477,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 +485,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 +494,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 +536,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 +622,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 +664,7 @@ private void ParseFirewallExceptionElement(Intermediate intermediate, Intermedia symbol.Program = program; } - if (CompilerConstants.IntegerNotSet != attributes) + if (attributes != CompilerConstants.IntegerNotSet) { symbol.Attributes = attributes; } @@ -382,5 +719,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..eb5e34d68 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; + protected IParseHelper ParseHelper { get; private 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,45 @@ 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) + { + AttributeIfNotNull("IgnoreUpdates", true); + } + + if ((attr & 0x4) == 0x4) + { + AttributeIfNotNull("EnableOnChange", true); + } } 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 +205,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 +212,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 +440,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/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/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/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs index ce55aa142..49b31319a 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,110 @@ 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")); + } } } 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 @@ +