22using System . Collections . Generic ;
33using System . IO ;
44using System . Linq ;
5+ using System . Text ;
56using System . Threading . Tasks ;
67using log4net ;
78using NETworkManager . Models . Network ;
@@ -20,7 +21,7 @@ public class Firewall
2021 /// <summary>
2122 /// The Logger.
2223 /// </summary>
23- private readonly ILog _logger = LogManager . GetLogger ( typeof ( Firewall ) ) ;
24+ private readonly ILog Log = LogManager . GetLogger ( typeof ( Firewall ) ) ;
2425
2526 #endregion
2627
@@ -33,79 +34,85 @@ public class Firewall
3334 /// <returns>Returns <c>true</c> if the rules were successfully applied; otherwise, <c>false</c>.</returns>
3435 private bool ApplyRules ( List < FirewallRule > rules )
3536 {
36- string command = GetClearAllRulesCommand ( ) ;
37+ // If there are no rules to apply, return true as there is nothing to do.
3738 if ( rules . Count is 0 )
3839 return true ;
39- command += "; " ;
40- foreach ( FirewallRule rule in rules )
40+
41+ // Start by clearing all existing rules for the current profile to ensure a clean state.
42+ var sb = new StringBuilder ( GetClearAllRulesCommand ( ) ) ;
43+ sb . Append ( "; " ) ;
44+
45+ foreach ( var rule in rules )
4146 {
42- string nextRule = string . Empty ;
4347 try
4448 {
45- nextRule += $ "New-NetFirewallRule -DisplayName '{ SanitizeStringArguments ( rule . Name ) } '";
49+ var ruleSb = new StringBuilder ( $ "New-NetFirewallRule -DisplayName '{ SanitizeStringArguments ( rule . Name ) } '") ;
50+
4651 if ( ! string . IsNullOrEmpty ( rule . Description ) )
47- nextRule += $ " -Description '{ SanitizeStringArguments ( rule . Description ) } '";
48- nextRule += $ " -Direction { Enum . GetName ( rule . Direction ) } ";
49- if ( rule . LocalPorts . Count > 0
50- && rule . Protocol is FirewallProtocol . TCP or FirewallProtocol . UDP )
51- {
52- nextRule += $ " -LocalPort { FirewallRule . PortsToString ( rule . LocalPorts , ',' , false ) } ";
53- }
52+ ruleSb . Append ( $ " -Description '{ SanitizeStringArguments ( rule . Description ) } '") ;
53+
54+ ruleSb . Append ( $ " -Direction { Enum . GetName ( rule . Direction ) } ") ;
55+
56+ if ( rule . LocalPorts . Count > 0 && rule . Protocol is FirewallProtocol . TCP or FirewallProtocol . UDP )
57+ ruleSb . Append ( $ " -LocalPort { FirewallRule . PortsToString ( rule . LocalPorts , ',' , false ) } ") ;
58+
59+ if ( rule . RemotePorts . Count > 0 && rule . Protocol is FirewallProtocol . TCP or FirewallProtocol . UDP )
60+ ruleSb . Append ( $ " -RemotePort { FirewallRule . PortsToString ( rule . RemotePorts , ',' , false ) } ") ;
61+
62+ ruleSb . Append ( rule . Protocol is FirewallProtocol . Any
63+ ? " -Protocol Any"
64+ : $ " -Protocol { ( int ) rule . Protocol } ") ;
5465
55- if ( rule . RemotePorts . Count > 0
56- && rule . Protocol is FirewallProtocol . TCP or FirewallProtocol . UDP )
57- nextRule += $ " -RemotePort { FirewallRule . PortsToString ( rule . RemotePorts , ',' , false ) } ";
58- if ( rule . Protocol is FirewallProtocol . Any )
59- nextRule += $ " -Protocol Any";
60- else
61- nextRule += $ " -Protocol { ( int ) rule . Protocol } ";
6266 if ( ! string . IsNullOrWhiteSpace ( rule . Program ? . Name ) )
6367 {
64- try
65- {
66- if ( File . Exists ( rule . Program . Name ) )
67- nextRule += $ " -Program '{ SanitizeStringArguments ( rule . Program . Name ) } '";
68- else
69- continue ;
70- }
71- catch
68+ if ( File . Exists ( rule . Program . Name ) )
69+ ruleSb . Append ( $ " -Program '{ SanitizeStringArguments ( rule . Program . Name ) } '") ;
70+ else
7271 {
72+ Log . Warn ( $ "Program path '{ rule . Program . Name } ' in rule '{ rule . Name } ' does not exist. Skipping rule.") ;
7373 continue ;
7474 }
7575 }
7676
7777 if ( rule . InterfaceType is not FirewallInterfaceType . Any )
78- nextRule += $ " -InterfaceType { Enum . GetName ( rule . InterfaceType ) } ";
78+ ruleSb . Append ( $ " -InterfaceType { Enum . GetName ( rule . InterfaceType ) } ") ;
79+
80+ // If not all network profiles are enabled, specify the ones that are.
7981 if ( ! rule . NetworkProfiles . All ( x => x ) )
8082 {
81- nextRule += $ " -Profile ";
82- for ( int i = 0 ; i < rule . NetworkProfiles . Length ; i ++ )
83- {
84- if ( rule . NetworkProfiles [ i ] )
85- nextRule += $ "{ Enum . GetName ( typeof ( NetworkProfiles ) , i ) } ,";
86- }
87- nextRule = nextRule [ ..^ 1 ] ;
83+ var profiles = Enumerable . Range ( 0 , rule . NetworkProfiles . Length )
84+ . Where ( i => rule . NetworkProfiles [ i ] )
85+ . Select ( i => Enum . GetName ( typeof ( NetworkProfiles ) , i ) ) ;
86+
87+ ruleSb . Append ( $ " -Profile { string . Join ( ',' , profiles ) } ") ;
8888 }
89- nextRule += $ " -Action { Enum . GetName ( rule . Action ) } ; ";
90- command += nextRule ;
89+
90+ ruleSb . Append ( $ " -Action { Enum . GetName ( rule . Action ) } ; ") ;
91+
92+ sb . Append ( ruleSb ) ;
9193 }
92- catch ( ArgumentException )
94+ catch ( ArgumentException ex )
9395 {
96+ Log . Warn ( $ "Failed to build firewall rule '{ rule . Name } ': { ex . Message } ") ;
9497 }
9598 }
9699
97- command = command [ ..^ 2 ] ;
100+ // Remove the trailing "; " from the last command.
101+ sb . Length -= 2 ;
102+
103+ var command = sb . ToString ( ) ;
104+
105+ Log . Debug ( $ "Applying rules:{ Environment . NewLine } { command } ") ;
106+
98107 try
99108 {
100- _logger . Info ( $ "[Firewall] Applying rules:{ Environment . NewLine } { command } ") ;
101109 PowerShellHelper . ExecuteCommand ( command , true ) ;
110+ return true ;
102111 }
103112 catch ( Exception )
104113 {
105114 return false ;
106115 }
107-
108- return true ;
109116 }
110117
111118 /// <summary>
@@ -118,14 +125,23 @@ private bool ApplyRules(List<FirewallRule> rules)
118125 /// </remarks>
119126 public static void ClearAllRules ( )
120127 {
121- PowerShellHelper . ExecuteCommand ( $ " { GetClearAllRulesCommand ( ) } " , true ) ;
128+ PowerShellHelper . ExecuteCommand ( GetClearAllRulesCommand ( ) , true ) ;
122129 }
123130
131+ /// <summary>
132+ /// Generates a command string that removes all Windows Firewall rules with a display name starting with 'NwM_'.
133+ /// </summary>
134+ /// <returns>A command string that can be executed in PowerShell to remove the specified firewall rules.</returns>
124135 private static string GetClearAllRulesCommand ( )
125136 {
126- return $ "Remove-NetFirewallRule -DisplayName 'NwM_*'";
137+ return "Remove-NetFirewallRule -DisplayName 'NwM_*'" ;
127138 }
128139
140+ /// <summary>
141+ /// Sanitizes string arguments by replacing single quotes with double single quotes to prevent issues in PowerShell command execution.
142+ /// </summary>
143+ /// <param name="value">The input string to be sanitized.</param>
144+ /// <returns>A sanitized string with single quotes escaped.</returns>
129145 private static string SanitizeStringArguments ( string value )
130146 {
131147 return value . Replace ( "'" , "''" ) ;
@@ -136,9 +152,9 @@ private static string SanitizeStringArguments(string value)
136152 /// </summary>
137153 /// <param name="rules">A list of firewall rules to apply.</param>
138154 /// <returns>A task representing the asynchronous operation. The task result contains a boolean indicating whether the rules were successfully applied.</returns>
139- public async Task < bool > ApplyRulesAsync ( List < FirewallRule > rules )
155+ public async Task ApplyRulesAsync ( List < FirewallRule > rules )
140156 {
141- return await Task . Run ( ( ) => ApplyRules ( rules ) ) ;
157+ await Task . Run ( ( ) => ApplyRules ( rules ) ) ;
142158 }
143159 #endregion
144- }
160+ }
0 commit comments