sub get_firewall_configuration()

in managementnode/lib/VCL/Module/OS/Windows/Version_6.pm [1380:1532]


sub get_firewall_configuration {
	my $self = shift;
	if (ref($self) !~ /windows/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	return $self->{firewall_configuration} if $self->{firewall_configuration};
	
	my $computer_node_name = $self->data->get_computer_node_name();
	my $system32_path = $self->get_system32_path() || return;
	
	my $firewall_configuration;
	
	my $command = "$system32_path/netsh.exe advfirewall firewall show rule name=all verbose";
	my ($exit_status, $output) = $self->execute($command, 0);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to run command to show firewall rules on $computer_node_name");
		return;
	}
	elsif (!grep(/Rule Name:/i, @$output)) {
		notify($ERRORS{'WARNING'}, 0, "unexpected output returned from command to show firewall rules on $computer_node_name, command: '$command', exit status: $exit_status, output:\n" . join("\n", @$output));
		return;
	}
	
	# Execute the netsh.exe command to retrieve firewall rules
	#   Rule Name:                            VCL: allow RDP port 3389
	#   ----------------------------------------------------------------------
	#   Enabled:                              Yes
	#   Direction:                            In
	#   Profiles:                             Domain,Private,Public
	#   Grouping:
	#   LocalIP:                              Any
	#   RemoteIP:                             152.14.53.0/26,10.10.1.2-10.10.2.22
	#   Protocol:                             TCP
	#   LocalPort:                            3389
	#   RemotePort:                           Any
	#   Edge traversal:                       No
	#   Action:                               Allow
	#   Rule Name:                            VCL: allow ping to/from any address
	#   ----------------------------------------------------------------------
	#   Enabled:                              Yes
	#   Direction:                            In
	#   Profiles:                             Domain,Private,Public
	#   Grouping:
	#   LocalIP:                              Any
	#   RemoteIP:                             Any
	#   Protocol:                             ICMPv4
	#                                         Type    Code
	#                                         8       Any
	#   Edge traversal:                       No
	#   Action:                               Allow
	
	# Split the output into rule sections
	my @rule_sections = split(/Rule Name:\s*/, join("\n", @$output));
	
	RULE: for my $rule_section (@rule_sections) {
		my @lines = split(/\n+/, $rule_section);
		
		my $rule_name = shift(@lines);
		
		# The first rule section will probably be blank because of the way split works
		next RULE if (!$rule_name);
		
		my $rule_info;
		for my $line (@lines) {
			if (my ($parameter, $value) = $line =~ /^(\w+):\s*(.*)/g) {
				$rule_info->{$parameter} = $value;
			}
			elsif ($rule_info->{Protocol} && $rule_info->{Protocol} =~ /icmp/i) {
				if (my ($icmp_type, $icmp_code) = $line =~ /^\s*(\d+)\s+(.*)/g) {
					push @{$rule_info->{ICMPTypes}{$icmp_type}}, $icmp_code;
				}
			}
		}
		
		if (!defined($rule_info->{Enabled}) || $rule_info->{Enabled} !~ /yes/i) {
			#notify($ERRORS{'DEBUG'}, 0, "ignoring disabled rule: '$rule_name'");
			next RULE;
		}
		if (!defined($rule_info->{Direction}) || $rule_info->{Direction} !~ /in/i) {
			#notify($ERRORS{'DEBUG'}, 0, "ignoring outgoing rule: '$rule_name'");
			next RULE;
		}
		elsif (!defined($rule_info->{Action}) || $rule_info->{Action} !~ /allow/i) {
			#notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', Action is NOT allow");
			next RULE;
		}
		elsif (!defined($rule_info->{Protocol})) {
			#notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', Protocol is not defined:\n$rule_section");
			next RULE;
		}
		elsif ($rule_info->{Protocol} =~ /v6/i) {
			# Skip IPv6 rules for now
			next RULE;
		}
		
		my @ports;
		
		if ($rule_info->{Protocol} =~ /icmp/i) {
			if (!defined($rule_info->{ICMPTypes})) {
				notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', ICMP type could not be determined:\n$rule_section");
				next RULE;
			}
			@ports = sort keys(%{$rule_info->{ICMPTypes}})
		}
		else {
			if (!defined($rule_info->{LocalPort})) {
				#notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', LocalPort is not defined");
				next RULE;
			}
			elsif ($rule_info->{LocalPort} !~ /^\d+$/) {
				#notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', LocalPort is not an integer");
				next RULE;
			}
			
			@ports = split(",", $rule_info->{LocalPort});
		}
		
		if (!@ports) {
			notify($ERRORS{'WARNING'}, 0, "ignoring rule: '$rule_name', no ports defined:\n" . format_data($rule_info) . "\n$rule_section");
			next RULE;
		}
		
		for my $port (@ports) {
			$firewall_configuration->{$rule_info->{Protocol}}{$port}{name} = $rule_name;
			$firewall_configuration->{$rule_info->{Protocol}}{$port}{description} = $rule_info->{Description};
			$firewall_configuration->{$rule_info->{Protocol}}{$port}{scope} = $rule_info->{RemoteIP};
			$firewall_configuration->{$rule_info->{Protocol}}{$port}{local_ip} = $rule_info->{LocalIP};
		}
	}
	
	# Assemble a string containing all the rule info (don't print_data because it outputs too much to vcld.log)
	my $rules_string;
	for my $protocol (keys %$firewall_configuration) {
		for my $port (sort keys %{$firewall_configuration->{$protocol}}) {
			my $name = $firewall_configuration->{$protocol}{$port}{name};
			my $scope = $firewall_configuration->{$protocol}{$port}{scope};
			my $local_ip = $firewall_configuration->{$protocol}{$port}{local_ip};
			$rules_string .= "$protocol:$port '$name' - local IP: $local_ip, scope: $scope\n";
		}
	}
	
	# Copy the ICMPv4 key to one named ICMP for compatibility
	if (defined($firewall_configuration->{ICMPv4})) {
		$firewall_configuration->{ICMP} = $firewall_configuration->{ICMPv4};
	}
	
	$self->{firewall_configuration} = $firewall_configuration;
	
	notify($ERRORS{'DEBUG'}, 0, "retrieved firewall info from $computer_node_name:\n$rules_string");
	return $firewall_configuration;
}