You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2014/06/16 21:51:54 UTC

svn commit: r1602979 [1/2] - in /vcl/trunk/managementnode/lib/VCL: Module/OS.pm Module/OS/Linux.pm Module/OS/Windows.pm new.pm utils.pm

Author: arkurth
Date: Mon Jun 16 19:51:54 2014
New Revision: 1602979

URL: http://svn.apache.org/r1602979
Log:
VCL-702
Reworked new.pm::reserve_computer to not include OS-specific code. Moved some of the old functionality to a new OS.pm::reserve subroutine. Updated reserve subroutine in Linux.pm and Windows.pm.

Removed new.pm::confirm_public_ip_address. The functionality exists in OS.pm::update_public_ip_address.

VCL-753
Added OS.pm::get_connect_method_remote_ip_addresses. Added Linux.pm: get_user_remote_ip_addresses, get_port_connection_info.

VCL-564
Added OS.pm::run_scripts, which was formerly in Windows.pm. This isn't called from anywhere yet.

Added $SOURCE_CONFIGURATION_DIRECTORY variable to Linux.pm.

VCL-253
Added Windows.pm::check_rdp_port_configuration. This is called from Windows.pm::post_load. If the port number for the RDP connect method doesn't match the registry it is configured.

Modified:
    vcl/trunk/managementnode/lib/VCL/Module/OS.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
    vcl/trunk/managementnode/lib/VCL/new.pm
    vcl/trunk/managementnode/lib/VCL/utils.pm

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS.pm?rev=1602979&r1=1602978&r2=1602979&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS.pm Mon Jun 16 19:51:54 2014
@@ -128,6 +128,32 @@ sub pre_capture {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 reserve
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Performs common OS steps to reserve the computer for a user.
+
+=cut
+
+sub reserve {
+ 	my $self = shift;
+	if (ref($self) !~ /VCL::Module/) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	# Make sure the public IP address assigned to the computer matches the database
+	if (!$self->update_public_ip_address()) {
+		notify($ERRORS{'WARNING'}, 0, "unable to reserve computer, failed to update IP address");
+		return;
+	}
+	
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 get_source_configuration_directories
 
  Parameters  : None
@@ -945,7 +971,6 @@ sub server_request_set_fixedIP {
 					-- future; good to check with upstream network switch or control
 
 =cut
-#/////////////////////////////////////////////////////////////////////////////
 
 sub confirm_fixedIP_is_available {
 	my $self = shift;
@@ -1007,7 +1032,7 @@ sub update_public_ip_address {
 	my $image_os_type = $self->data->get_image_os_type() || return;
 	my $computer_ip_address = $self->data->get_computer_ip_address();
 	my $public_ip_configuration = $self->data->get_management_node_public_ip_configuration() || return;
-
+	
 	if ($public_ip_configuration =~ /dhcp/i) {
 		notify($ERRORS{'DEBUG'}, 0, "IP configuration is set to $public_ip_configuration, attempting to retrieve dynamic public IP address from $computer_node_name");
 		
@@ -1046,7 +1071,6 @@ sub update_public_ip_address {
 		}
 		
 	}
-	
 	elsif ($public_ip_configuration =~ /static/i) {
 		notify($ERRORS{'DEBUG'}, 0, "IP configuration is set to $public_ip_configuration, attempting to set public IP address");
 		
@@ -1066,7 +1090,6 @@ sub update_public_ip_address {
 			notify($ERRORS{'WARNING'}, 0, "unable to set static public IP address on $computer_node_name, " . ref($self) . " module does not implement a set_static_public_address subroutine");
 		}
 	}
-	
 	else {
 		notify($ERRORS{'DEBUG'}, 0, "IP configuration is set to $public_ip_configuration, no public IP address updates necessary");
 	}
@@ -1218,7 +1241,7 @@ sub get_public_interface_name {
 	my $no_cache = shift;
 	
 	if (defined $self->{public_interface_name} && !$no_cache) {
-		notify($ERRORS{'DEBUG'}, 0, "returning public interface name previously retrieved: $self->{public_interface_name}");
+		#notify($ERRORS{'DEBUG'}, 0, "returning public interface name previously retrieved: $self->{public_interface_name}");
 		return $self->{public_interface_name};
 	}
 	
@@ -1306,7 +1329,7 @@ sub get_public_interface_name {
 	
 	if ($public_interface_name) {
 		$self->{public_interface_name} = $public_interface_name;
-		notify($ERRORS{'OK'}, 0, "determined the public interface name: '$self->{public_interface_name}'\n" . format_data($network_configuration->{$self->{public_interface_name}}));
+		notify($ERRORS{'OK'}, 0, "determined the public interface name: '$self->{public_interface_name}'");
 		return $self->{public_interface_name};
 	}
 	else {
@@ -1560,9 +1583,11 @@ sub get_public_mac_address {
 
 =head2 get_ip_address
 
- Parameters  : 
- Returns     : 
- Description : 
+ Parameters  : $network_type (optional), $ignore_error (optional)
+ Returns     : string
+ Description : Returns the IP address of the computer. The $network_type
+               argument may either be 'public' or 'private'. If not supplied,
+               the default is to return the public IP address.
 
 =cut
 
@@ -1580,7 +1605,9 @@ sub get_ip_address {
 		notify($ERRORS{'WARNING'}, 0, "network type argument can only be 'public' or 'private'");
 		return;
 	}
-
+	
+	my $ignore_error = shift;
+	
 	# Get the public or private network configuration
 	# Use 'eval' to construct the appropriate subroutine name
 	my $network_configuration = eval "\$self->get_$network_type\_network_configuration()";
@@ -1599,14 +1626,16 @@ sub get_ip_address {
 	my $ip_address;
 	my @ip_addresses = keys %$ip_address_info;
 	if (!@ip_addresses) {
-		notify($ERRORS{'WARNING'}, 0, "unable to determine $network_type IP address, 'ip_address' value is not set in the network configuration info: \n" . format_data($network_configuration));
+		if (!$ignore_error) {
+			notify($ERRORS{'WARNING'}, 0, "unable to determine $network_type IP address, 'ip_address' value is not set in the network configuration info: \n" . format_data($network_configuration));
+		}
 		return;
 	}
 	
 	# Interface has multiple IP addresses, try to find a valid one
 	for $ip_address (@ip_addresses) {
 		if ($ip_address !~ /(0\.0\.0\.0|169\.254\.)/) {
-			notify($ERRORS{'DEBUG'}, 0, "returning $network_type IP address: $ip_address");
+			#notify($ERRORS{'DEBUG'}, 0, "returning $network_type IP address: $ip_address");
 			return $ip_address;
 		}
 		else {
@@ -1622,9 +1651,9 @@ sub get_ip_address {
 
 =head2 get_private_ip_address
 
- Parameters  : 
- Returns     : 
- Description : 
+ Parameters  : $ignore_error (optional)
+ Returns     : string
+ Description : Returns the computer's private IP address.
 
 =cut
 
@@ -1635,16 +1664,17 @@ sub get_private_ip_address {
 		return;
 	}
 	
-	return $self->get_ip_address('private');
+	my $ignore_error = shift;
+	return $self->get_ip_address('private', $ignore_error);
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
 =head2 get_public_ip_address
 
- Parameters  : 
- Returns     : 
- Description : 
+ Parameters  : $ignore_error (optional)
+ Returns     : string
+ Description : Returns the computer's public IP address.
 
 =cut
 
@@ -1654,8 +1684,8 @@ sub get_public_ip_address {
 		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
 		return;
 	}
-	
-	return $self->get_ip_address('public');
+	my $ignore_error = shift;
+	return $self->get_ip_address('public', $ignore_error);
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -2148,7 +2178,7 @@ sub execute {
 	my ($exit_status, $output) = run_ssh_command($arguments);
 	if (defined($exit_status) && defined($output)) {
 		if ($display_output) {
-			notify($ERRORS{'OK'}, 0, "executed command: '$command', exit status: $exit_status, output:\n" . join("\n", @$output));
+			notify($ERRORS{'DEBUG'}, 0, "executed command: '$command', exit status: $exit_status, output:\n" . join("\n", @$output));
 		}
 		return ($exit_status, $output);
 	}
@@ -2626,11 +2656,12 @@ sub manage_server_access {
 	
 	return 1;
 }
+
 #/////////////////////////////////////////////////////////////////////////////
 
 =head2 process_connect_methods
 
- Parameters  : none
+ Parameters  : $remote_ip (optional), $overwrite
  Returns     : boolean
  Description : Processes the connect methods configured for the image revision.
 
@@ -2644,12 +2675,12 @@ sub process_connect_methods {
 	}
 	
 	my $computer_node_name = $self->data->get_computer_node_name();
-	my $imagerevision_id   = $self->data->get_imagerevision_id();
+	my $imagerevision_id = $self->data->get_imagerevision_id();
 	
 	# Retrieve the connect method info hash
 	my $connect_method_info = get_connect_method_info($imagerevision_id);
 	if (!$connect_method_info) {
-		notify($ERRORS{'WARNING'}, 0, "no connect methods are configured for image revision $imagerevision_id");
+		notify($ERRORS{'WARNING'}, 0, "failed to retrieve connect method info for image revision $imagerevision_id");
 		return;
 	}
 
@@ -3357,6 +3388,253 @@ sub get_timings {
 
 }
 
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 run_scripts
+
+ Parameters  : $stage
+ Returns     : boolean
+ Description : Runs scripts on the computer intended for the state specified by
+               the argument. The stage argument may be any of the following:
+               -pre_capture
+               -post_load
+               -post_reserve
+               
+               Scripts are stored in various directories under tools matching
+               the OS of the image being loaded. For example, scripts residing
+               in any of the following directories would be executed if the
+               stage argument is 'post_load' and the OS of the image being
+               loaded is Windows XP 32-bit:
+               -tools/Windows/Scripts/post_load
+               -tools/Windows/Scripts/post_load/x86
+               -tools/Windows_Version_5/Scripts/post_load
+               -tools/Windows_Version_5/Scripts/post_load/x86
+               -tools/Windows_XP/Scripts/post_load
+               -tools/Windows_XP/Scripts/post_load/x86
+               
+               The order the scripts are executed is determined by the script
+               file names. The directory where the script resides has no affect
+               on the order. Script files can be named beginning with a number.
+               The scripts sorted numerically and processed from the lowest
+               number to the highest:
+               -1.cmd
+               -50.cmd
+               -100.cmd
+               
+               Scripts which do not begin with a number are sorted
+               alphabetically and processed after any scripts which begin with a
+               number:
+               -1.cmd
+               -50.cmd
+               -100.cmd
+               -Blah.cmd
+               -foo.cmd
+
+=cut
+
+sub run_scripts {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	# Get the stage argument
+	my $stage = shift;
+	if (!$stage) {
+		notify($ERRORS{'WARNING'}, 0, "unable to run scripts, stage argument was not supplied");
+		return;
+	}
+	elsif ($stage !~ /(pre_capture|post_load|post_reserve)/) {
+		notify($ERRORS{'WARNING'}, 0, "invalid stage argument was supplied: $stage");
+		return;
+	}
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	my @computer_tools_files = $self->get_tools_file_paths("/Scripts/$stage/");
+	
+	my @failed_file_paths;
+	
+	# Loop through all tools files on the computer
+	for my $computer_tools_file_path (@computer_tools_files) {
+		notify($ERRORS{'DEBUG'}, 0, "executing script on $computer_node_name: $computer_tools_file_path");
+		if (!$self->run_script($computer_tools_file_path)) {
+			push @failed_file_paths, $computer_tools_file_path;
+		}
+	}
+	
+	# Check if any scripts failed
+	if (@failed_file_paths) {
+		notify($ERRORS{'CRITICAL'}, 0, "failed to run the following scripts on $computer_node_name, stage: $stage\n" . join("\n", @failed_file_paths));
+		return;
+	}
+	
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 check_reservation_password
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Checks if a reservation password has already been generated. If
+               not, a password is generated, the reservation table is updated,
+               and the DataStructure is updated.
+
+=cut
+
+sub check_reservation_password {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	if ($self->data->get_reservation_password(0)) {
+		notify($ERRORS{'DEBUG'}, 0, "reservation password has already been generated");
+		return 1;
+	}
+	
+	my $reservation_id = $self->data->get_reservation_id();
+	
+	# Create a random password for the reservation
+	my $reservation_password = getpw();
+	
+	# Update the password in the reservation table
+	if (!update_reservation_password($reservation_id, $reservation_password)) {
+		$self->reservation_failed("failed to update password in the reservation table");
+		return;
+	}
+	
+	# Set the password in the DataStructure object
+	$self->data->set_reservation_password($reservation_password);
+	
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_connect_method_remote_ip_addresses
+
+ Parameters  : none
+ Returns     : array
+ Description : Retrieves the current connection information from the computer
+               and compares it to the connect methods configured for the
+               reservation image revision. An array is returned containing the
+               remote IP addresses for connections which match any of the
+               protocols and ports configured for any connect method.
+               
+               Remote connections which match the management node's private or
+               public IP address are ignored.
+               
+               The ignored_remote_ip_addresses variable may be configured in the
+               database. This list should contain IP addresses or regular
+               expressions and may be deliminated by commas, semicolons, or
+               spaces. Any remote connections from an IP address in this list
+               will also be ignored. This may be used to exclude hosts other
+               than those a user may connect from which may have periodic
+               or a persistent connection -- such as a monitoring host.
+
+=cut
+
+sub get_connect_method_remote_ip_addresses {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	# Make sure a get_connection_info subroutine is implemented
+	if (!$self->can('get_port_connection_info')) {
+		notify($ERRORS{'WARNING'}, 0, "OS module does not implement a get_port_connection_info subroutine");
+		return;
+	}
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	my $imagerevision_id = $self->data->get_imagerevision_id();
+	
+	# Get the management node's IP addresses - these will be ignored
+	my $mn_private_ip_address = $self->mn_os->get_private_ip_address();
+	my $mn_public_ip_address = $self->mn_os->get_public_ip_address();
+	
+	# Get the ignored remote IP address variable from the database if it is configured
+	my $ignored_remote_ip_address_string = $self->data->get_variable('ignored_remote_ip_addresses') || '';
+	my @ignored_remote_ip_addresses = split(/[,; ]+/, $ignored_remote_ip_address_string);
+	notify($ERRORS{'DEBUG'}, 0, "connections to $computer_node_name from any of the following IP addresses will be ignored: " . join(', ', @ignored_remote_ip_addresses)) if (@ignored_remote_ip_addresses);
+	
+	my $connection_info = $self->get_port_connection_info();
+	if (!defined($connection_info)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to retrieve connection info from $computer_node_name");
+		return;
+	}
+	
+	my @remote_ip_addresses = ();
+	
+	my $connect_method_info = get_connect_method_info($imagerevision_id);
+	foreach my $connect_method_id (keys %$connect_method_info) {
+		my $connect_method_name = $connect_method_info->{$connect_method_id}{name};
+		my $connect_method_protocol = $connect_method_info->{$connect_method_id}{protocol} || 'any';
+		my $connect_method_port = $connect_method_info->{$connect_method_id}{port};
+		
+		notify($ERRORS{'DEBUG'}, 0, "checking connect method: '$connect_method_name', protocol: $connect_method_protocol, port: $connect_method_port");
+		
+		CONNECTION_PROTOCOL: for my $connection_protocol (keys %$connection_info) {
+			# Check if the protocol defined for the connect method matches the established connection
+			if (!$connect_method_protocol || $connect_method_protocol =~ /(\*|any|all)/i) {
+				#notify($ERRORS{'DEBUG'}, 0, "skipping validation of connect method protocol: $connect_method_protocol");
+			}
+			else {
+				if ($connect_method_protocol =~ /$connection_protocol/i || $connection_protocol =~ /$connect_method_protocol/i) {
+					notify($ERRORS{'DEBUG'}, 0, "connect method protocol matches established connection protocol: $connection_protocol");
+				}
+				else {
+					notify($ERRORS{'DEBUG'}, 0, "connect method protocol $connect_method_protocol does NOT match established connection protocol $connection_protocol");
+					next CONNECTION_PROTOCOL;
+				}
+			}
+			
+			CONNECTION_PORT: for my $connection_port (keys %{$connection_info->{$connection_protocol}}) {
+				# Check if the port defined for the connect method matches the established connection
+				if ($connect_method_port eq $connection_port) {
+					notify($ERRORS{'DEBUG'}, 0, "connect method port matches established connection port: $connection_port");
+					
+					for my $connection (@{$connection_info->{$connection_protocol}{$connection_port}}) {
+						my $remote_ip_address = $connection->{remote_ip};
+						if (!$remote_ip_address) {
+							notify($ERRORS{'WARNING'}, 0, "connection does NOT contain remote IP address (remote_ip) key:\n" . format_data($connection));
+						}
+						elsif ($remote_ip_address eq $mn_private_ip_address || $remote_ip_address eq $mn_public_ip_address) {
+							notify($ERRORS{'DEBUG'}, 0, "ignoring connection to port $connection_port from management node: $remote_ip_address");
+						}
+						elsif (my ($ignored_remote_ip_address) = grep { $remote_ip_address =~ /($_)/ } @ignored_remote_ip_addresses) {
+							notify($ERRORS{'DEBUG'}, 0, "ignoring connection to port $connection_port from ignored remote IP address ($ignored_remote_ip_address): $remote_ip_address");
+						}
+						else {
+							push @remote_ip_addresses, $remote_ip_address;
+						}
+					}
+				}
+				else {
+					notify($ERRORS{'DEBUG'}, 0, "connect method port $connect_method_port does NOT match established connection port $connection_port");
+					next CONNECTION_PORT;
+				}
+			}
+		}
+	}
+	
+	if (@remote_ip_addresses) {
+		@remote_ip_addresses = remove_array_duplicates(@remote_ip_addresses);
+		notify($ERRORS{'OK'}, 0, "detected connection to $computer_node_name using the ports and protocols configured for the connect methods, remote IP address(es): " . join(', ', @remote_ip_addresses));
+		return @remote_ip_addresses;
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "connection NOT established to $computer_node_name using the ports and protocols configured for the connect methods");
+		return ();
+	}
+}
+
 #///////////////////////////////////////////////////////////////////////////
 
 1;

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm?rev=1602979&r1=1602978&r2=1602979&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm Mon Jun 16 19:51:54 2014
@@ -64,6 +64,21 @@ use File::Basename;
 
 =cut
 
+=head2 $SOURCE_CONFIGURATION_DIRECTORY
+
+ Data type   : String
+ Description : Location on the management node of the files specific to this OS
+               module which are needed to configure the loaded OS on a computer.
+               This is normally the directory under 'tools' named after this OS
+               module.
+               
+               Example:
+               /usr/local/vcl/tools/Linux
+
+=cut
+
+our $SOURCE_CONFIGURATION_DIRECTORY = "$TOOLS/Linux";
+
 =head2 $NODE_CONFIGURATION_DIRECTORY
 
  Data type   : String
@@ -306,8 +321,8 @@ sub pre_capture {
 
 =head2 post_load
 
- Parameters  :
- Returns     :
+ Parameters  : none
+ Returns     : boolean
  Description :
 
 =cut
@@ -319,23 +334,27 @@ sub post_load {
 		return;
 	}
 	
-	my $management_node_keys  = $self->data->get_management_node_keys();
 	my $image_name            = $self->data->get_image_name();
-	my $computer_short_name   = $self->data->get_computer_short_name();
 	my $computer_node_name    = $self->data->get_computer_node_name();
 	my $image_os_install_type = $self->data->get_image_os_install_type();
-	my $management_node_ip    = $self->data->get_management_node_ipaddress();
 	my $mn_private_ip         = $self->mn_os->get_private_ip_address();
 	
-	notify($ERRORS{'OK'}, 0, "initiating Linux post_load: $image_name on $computer_short_name");
+	notify($ERRORS{'OK'}, 0, "beginning Linux post_load tasks, image: $image_name, computer: $computer_node_name");
 
 	# Wait for computer to respond to SSH
 	if (!$self->wait_for_response(5, 600, 10)) {
 		notify($ERRORS{'WARNING'}, 0, "$computer_node_name never responded to SSH");
-		return 0;
+		return;
 	}
-
+	
+	# Make sure the public IP address assigned to the computer matches the database
+	if (!$self->update_public_ip_address()) {
+		notify($ERRORS{'WARNING'}, 0, "failed to update public IP address");
+		return;
+	}
+	
 	# Configure sshd to only listen on the private interface and add ext_sshd service listening on the public interface
+	# This locks down sshd so that it isn't listening on the public interface -- ext_sshd isn't started yet
 	if (!$self->configure_ext_sshd()) {
 		notify($ERRORS{'WARNING'}, 0, "failed to configure ext_sshd on $computer_node_name");
 		return 0;
@@ -343,14 +362,12 @@ sub post_load {
 	
 	# Remove commands from rc.local added by previous versions of VCL
 	$self->configure_rc_local();
-
+	
+	# Kickstart installations likely won't have currentimage.txt, generate it
 	if ($image_os_install_type eq "kickstart") {
-		notify($ERRORS{'OK'}, 0, "detected kickstart install on $computer_short_name, writing current_image.txt");
-		if (write_currentimage_txt($self->data)) {
-			notify($ERRORS{'OK'}, 0, "wrote current_image.txt on $computer_short_name");
-		}
-		else {
-			notify($ERRORS{'WARNING'}, 0, "failed to write current_image.txt on $computer_short_name");
+		notify($ERRORS{'OK'}, 0, "detected kickstart install on $computer_node_name, writing current_image.txt");
+		if (!write_currentimage_txt($self->data)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to write current_image.txt on $computer_node_name");
 		}
 	}
 	
@@ -360,35 +377,32 @@ sub post_load {
 	}
 	
 	# Change password
-	if ($self->changepasswd($computer_node_name, "root")) {
-		notify($ERRORS{'OK'}, 0, "successfully changed root password on $computer_node_name");
-	}
-	else {
+	if (!$self->changepasswd($computer_node_name, "root")) {
 		notify($ERRORS{'OK'}, 0, "failed to edit root password on $computer_node_name");
 	}
 	
-	notify($ERRORS{'DEBUG'}, 0, "calling clear_private_keys");
 	# Clear ssh idenity keys from /root/.ssh
-	if ($self->clear_private_keys()) {
-		notify($ERRORS{'OK'}, 0, "cleared known identity keys");
+	if (!$self->clear_private_keys()) {
+		notify($ERRORS{'WARNING'}, 0, "failed to clear known identity keys");
+	}
+	
+	# Allow all traffic from the management node's private IP address
+	# Remove existing rules which allow traffic from any IP address
+	if (!$self->enable_firewall_port("tcp", "any", $mn_private_ip, 1)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to allow all traffic from management node private IP address");
 	}
 	
-	if ($self->enable_firewall_port("tcp", "any", $mn_private_ip, 1)) {
-		notify($ERRORS{'OK'}, 0, "added MN_Priv_IP $mn_private_ip to firewall on $computer_short_name");
+	# Allow all traffic on port 22 from the management node's private IP address
+	# Remove existing rules which allow traffic from any IP address
+	if (!$self->enable_firewall_port("tcp", 22, $mn_private_ip, 1)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to allow SSH traffic from management node private IP address");
 	}
 	
 	# Attempt to generate ifcfg-eth* files and ifup any interfaces which the file does not exist
 	$self->activate_interfaces();
 	
-	if (!$self->update_public_ip_address()) {
-		notify($ERRORS{'WARNING'}, 0, "failed to update IP address for $computer_node_name");
-		return 0;
-	}
-	
 	# Update Hostname to match Public assigned name
-	if ($self->update_public_hostname()) {
-		notify($ERRORS{'OK'}, 0, "Updated hostname");
-	}
+	$self->update_public_hostname();
 	
 	# Run the vcl_post_load script if it exists in the image
 	my @post_load_script_paths = ('/usr/local/vcl/vcl_post_load', '/etc/init.d/vcl_post_load');	
@@ -408,13 +422,11 @@ sub post_load {
 			}
 		}
 	}
-
 	
 	# Add a line to currentimage.txt indicating post_load has run
 	$self->set_vcld_post_load_status();
 	
 	return 1;
-
 } ## end sub post_load
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -500,7 +512,7 @@ sub post_reservation {
 	else {
 		notify($ERRORS{'DEBUG'}, 0, "ran $script_path");
 	}
-
+	
 	return 1;
 }
 
@@ -710,7 +722,12 @@ sub set_static_public_address {
 	}
 	
 	# Get the IP configuration
-	my $interface_name  = $self->get_public_interface_name()                        || '<undefined>';
+	my $interface_name  = $self->get_public_interface_name();
+	if (!$interface_name) {
+		notify($ERRORS{'WARNING'}, 0, "unable to set static public IP address, public interface name could not be determined");
+		return;
+	}
+	
 	my $ip_address      = $self->data->get_computer_ip_address()                    || '<undefined>';
 	my $subnet_mask     = $self->data->get_management_node_public_subnet_mask()     || '<undefined>';
 	my $default_gateway = $self->data->get_management_node_public_default_gateway() || '<undefined>';
@@ -725,15 +742,31 @@ sub set_static_public_address {
 	
 	# Assemble a string containing the static IP configuration
 	my $configuration_info_string = <<EOF;
-public interface name: $interface_name
 public IP address: $ip_address
 public subnet mask: $subnet_mask
 public default gateway: $default_gateway
 public DNS server(s): @dns_servers
 EOF
 	
+	# Get the current public IP address being used by the computer
+	# Pass the $ignore_error flag to prevent warnings if not defined
+	my $current_public_ip_address = $self->get_public_ip_address(1);
+	if ($current_public_ip_address) {
+		if ($current_public_ip_address eq $ip_address) {
+			notify($ERRORS{'DEBUG'}, 0, "static public IP address does not need to be set, $computer_name is already configured to use $current_public_ip_address");
+			return 1;
+		}
+		else {
+			notify($ERRORS{'DEBUG'}, 0, "static public IP address needs to be set, public IP address currently being used by $computer_name $current_public_ip_address does NOT match correct public IP address: $ip_address");
+		}
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "static public IP address needs to be set, unable to determine public IP address currently in use on $computer_name");
+	}
+	
+	
 	# Make sure required info was retrieved
-	if ("$interface_name $ip_address $subnet_mask $default_gateway" =~ /undefined/) {
+	if ("$ip_address $subnet_mask $default_gateway" =~ /undefined/) {
 		notify($ERRORS{'WARNING'}, 0, "failed to retrieve required network configuration for $computer_name:\n$configuration_info_string");
 		return;
 	}
@@ -877,6 +910,9 @@ EOF
 		notify($ERRORS{'DEBUG'}, 0, "$resolv_conf_path not updated  on $computer_name because DNS server address is not configured for the management node");
 	}
 	
+	# Delete cached network configuration info - forces next call to get_network_configuration to retrieve changed network info from computer
+	delete $self->{network_configuration};
+	
 	notify($ERRORS{'OK'}, 0, "successfully set static public IP address on $computer_name");
 	return 1;
 }
@@ -982,9 +1018,13 @@ sub logoff_user {
 
 =head2 reserve
 
- Parameters  : called as an object
- Returns     : 1 - success , 0 - failure
- Description : adds user 
+ Parameters  : none
+ Returns     : boolean
+ Description : Performs the steps necessary to reserve a computer for a user.
+               If the user "standalone" flag = 1, a random reservation password
+               is generated. Existing "AllowUsers" lines are removed from
+               /etc/ssh/external_sshd_config. A "vcl" user group is added to the
+               computer -The user account is created
 
 =cut
 
@@ -995,19 +1035,35 @@ sub reserve {
 		return 0;
 	}
 	
-	my $computer_node_name   = $self->data->get_computer_node_name();
+	# Call OS.pm's reserve subroutine
+	$self->SUPER::reserve() || return;
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	my $user_standalone = $self->data->get_user_standalone();
+
+	# Generate a reservation password if "standalone" (not using Kerberos authentication)
+	if ($user_standalone) {
+		# Generate a reservation password
+		if (!$self->check_reservation_password()) {
+			notify($ERRORS{'WARNING'}, 0, "failed to generate a reservation password");
+			return;
+		}
+	}
 	
-	# Remove AllowUsers lines from external_sshd_config
-	if (!$self->remove_lines_from_file('/etc/ssh/external_sshd_config', 'AllowUsers')) {
-		notify($ERRORS{'WARNING'}, 0, "failed to remove AllowUsers lines from external_sshd_config on $computer_node_name");
-	} 
+	# Configure sshd to only listen on the private interface and add ext_sshd service listening on the public interface
+	# This needs to be done after update_public_ip_address is called
+	if (!$self->configure_ext_sshd()) {
+		notify($ERRORS{'WARNING'}, 0, "failed to configure ext_sshd on $computer_node_name");
+		return 0;
+	}
 	
 	if (!$self->add_vcl_usergroup()) {
 		notify($ERRORS{'WARNING'}, 0, "failed to add vcl user group to $computer_node_name");
 	}
 	
 	if (!$self->create_user()) {
-		notify($ERRORS{'WARNING'}, 0, "failed to add user to $computer_node_name");
+		notify($ERRORS{'CRITICAL'}, 0, "failed to add user to $computer_node_name");
+		return;
 	}
 	
 	return 1;
@@ -1091,7 +1147,7 @@ sub synchronize_time {
 	# Ubuntu doesn't accept multiple servers in a single command
 	my $rdate_command;
 	for my $time_source (@time_sources) {
-		$rdate_command .= "rdate -s $time_source || ";
+		$rdate_command .= "rdate -t 3 -s $time_source || ";
 	}
 	$rdate_command =~ s/[ \|]+$//g;
 	my ($rdate_exit_status, $rdate_output) = $self->execute($rdate_command, 0, 180);
@@ -1387,7 +1443,7 @@ sub file_exists {
 	# Check if the file or directory exists
 	# Do not enclose the path in quotes or else wildcards won't work
 	my $command = "stat $escaped_path";
-	my ($exit_status, $output) = $self->execute($command);
+	my ($exit_status, $output) = $self->execute($command, 0);
 	if (!defined($output)) {
 		notify($ERRORS{'DEBUG'}, 0, "failed to run command to determine if file or directory exists on $computer_short_name:\npath: '$path'\ncommand: '$command'");
 		return 0;
@@ -1516,7 +1572,7 @@ sub create_directory {
 	my $computer_short_name = $self->data->get_computer_short_name();
 	
 	# Attempt to create the directory
-	my $command = "ls -d --color=never \"$directory_path\" 2>&1 || mkdir -p \"$directory_path\" 2>&1 && ls -d --color=never \"$directory_path\"";
+	my $command = "ls -d --color=never \"$directory_path\" 2>/dev/null || (mkdir -p \"$directory_path\" 2>&1 && ls -d --color=never \"$directory_path\")";
 	my ($exit_status, $output) = $self->execute($command);
 	if (!defined($output)) {
 		notify($ERRORS{'WARNING'}, 0, "failed to run command to create directory on $computer_short_name:\npath: '$directory_path'\ncommand: '$command'");
@@ -2130,7 +2186,7 @@ EOF
 
 =head2 get_network_configuration
 
- Parameters  : 
+ Parameters  : $no_cache (optional)
  Returns     : hash reference
  Description : Retrieves the network configuration on the Linux computer and
                constructs a hash. The hash reference returned is formatted as
@@ -2151,6 +2207,11 @@ sub get_network_configuration {
 		return;
 	}
 	
+	my $no_cache = shift;
+	
+	# Delete previously retrieved data if $no_cache was specified
+	delete $self->{network_configuration} if $no_cache;
+	
 	# Check if the network configuration has already been retrieved and saved in this object
 	return $self->{network_configuration} if ($self->{network_configuration});
 	
@@ -2161,6 +2222,7 @@ sub get_network_configuration {
 		notify($ERRORS{'WARNING'}, 0, "failed to run command to retrieve network configuration: $ifconfig_command");
 		return;
 	}
+	#notify($ERRORS{'DEBUG'}, 0, "ifconfig output:\n" . join("\n", @$ifconfig_output));
 	
 	# Loop through the ifconfig output lines
 	my $network_configuration;
@@ -2229,7 +2291,7 @@ sub get_network_configuration {
 	
 	$self->{network_configuration} = $network_configuration;
 	#can produce large output, if you need to monitor the configuration setting uncomment the below output statement
-	#notify($ERRORS{'DEBUG'}, 0, "retrieved network configuration:\n" . format_data($self->{network_configuration}));
+	notify($ERRORS{'DEBUG'}, 0, "retrieved network configuration:\n" . format_data($self->{network_configuration}));
 	return $self->{network_configuration};
 }
 
@@ -2470,38 +2532,38 @@ sub create_user {
 	my $home_directory_on_local_disk = $self->is_file_on_local_disk($home_directory_root);
 	if($home_directory_on_local_disk ) {
 
-		my $useradd_command = "/usr/sbin/useradd -m -d /home/$user_login_id -g vcl";
-		$useradd_command .= " -u $uid" if ($uid);
-		$useradd_command .= " $user_login_id";
-		my ($useradd_exit_status, $useradd_output) = $self->execute($useradd_command);
-	
-		# Check if the output indicates that the user already exists
-		# useradd: warning: the home directory already exists
-		# useradd: user ibuser exists
-	
-		if ($useradd_output && grep(/ exists(\s|$)/i, @$useradd_output)) {
-			if (!$self->delete_user($user_login_id)) {
-				notify($ERRORS{'WARNING'}, 0, "failed to add user '$user_login_id' to $computer_node_name, user with same name already exists and could not be deleted");
-				return;
-			}
-			($useradd_exit_status, $useradd_output) = $self->execute($useradd_command);
-		}
-	
-		if (!defined($useradd_output)) {
-			notify($ERRORS{'WARNING'}, 0, "failed to execute command to add user '$user_login_id' to $computer_node_name: '$useradd_command'");
+	my $useradd_command = "/usr/sbin/useradd -m -d /home/$user_login_id -g vcl";
+	$useradd_command .= " -u $uid" if ($uid);
+	$useradd_command .= " $user_login_id";
+	my ($useradd_exit_status, $useradd_output) = $self->execute($useradd_command);
+	
+	# Check if the output indicates that the user already exists
+	# useradd: warning: the home directory already exists
+	# useradd: user ibuser exists
+	
+	if ($useradd_output && grep(/ exists(\s|$)/i, @$useradd_output)) {
+		if (!$self->delete_user($user_login_id)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to add user '$user_login_id' to $computer_node_name, user with same name already exists and could not be deleted");
 			return;
 		}
-		elsif (grep(/^useradd: /, @$useradd_output)) {
-			notify($ERRORS{'WARNING'}, 0, "warning on add user '$user_login_id' to $computer_node_name\ncommand: '$useradd_command'\noutput:\n" . join("\n", @$useradd_output));
-		}
-		else {
-			notify($ERRORS{'OK'}, 0, "added user '$user_login_id' to $computer_node_name");
-		}
+		($useradd_exit_status, $useradd_output) = $self->execute($useradd_command);
+	}
+	
+	if (!defined($useradd_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command to add user '$user_login_id' to $computer_node_name: '$useradd_command'");
+		return;
+	}
+	elsif (grep(/^useradd: /, @$useradd_output)) {
+		notify($ERRORS{'WARNING'}, 0, "warning on add user '$user_login_id' to $computer_node_name\ncommand: '$useradd_command'\noutput:\n" . join("\n", @$useradd_output));
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "added user '$user_login_id' to $computer_node_name");
+	}
 	}
 	else {
 		notify($ERRORS{'OK'}, 0, "$home_directory_path is NOT on local disk, skipping useradd attempt");	
 	}
-
+	
 	if ($user_standalone) {
 		# Set password
 		if (!$self->changepasswd($computer_node_name, $user_login_id, $password)) {
@@ -2570,7 +2632,7 @@ sub create_user {
 			if (!$self->set_file_owner($home_directory_path, $user_login_id, 'vcl', 1)) {
 				notify($ERRORS{'WARNING'}, 0, "failed to set owner of user's home directory: $home_directory_path");
 				return;
-			}
+		}
 		}
 		else {
 			notify($ERRORS{'DEBUG'}, 0, "skipping adding user's public keys to $authorized_keys_file_path, home directory is on a network share");
@@ -2873,6 +2935,21 @@ sub service_exists {
 		return;
 	}
 	
+	# Check if this service has already been checked
+	if (defined($self->{service_init_module}{$service_name})) {
+		my $init_module_index = $self->{service_init_module}{$service_name}{init_module_index};
+		my $init_module_name = $self->{service_init_module}{$service_name}{init_module_name};
+		
+		if (defined($init_module_index)) {
+			#notify($ERRORS{'DEBUG'}, 0, "'$service_name' service has already been checked and exists, contolled by $init_module_name init module ($init_module_index)");
+			return (wantarray) ? ($init_module_index) : 1;
+		}
+		else {
+			#notify($ERRORS{'DEBUG'}, 0, "'$service_name' service has already been checked and does NOT exist");
+			return (wantarray) ? () : 0;
+		}
+	}
+	
 	my $computer_node_name = $self->data->get_computer_node_name();
 	
 	my @init_modules = $self->get_init_modules();
@@ -2892,6 +2969,12 @@ sub service_exists {
 		
 		if (grep(/^$service_name$/, @service_names)) {
 			notify($ERRORS{'DEBUG'}, 0, "'$service_name' service exists on $computer_node_name, controlled by $init_module_name init module ($init_module_index)");
+			
+			$self->{service_init_module}{$service_name} = {
+				init_module_index => $init_module_index,
+				init_module_name => $init_module_name,
+			};
+			
 			return (wantarray) ? ($init_module_index) : 1;
 		}
 		else {
@@ -2901,6 +2984,7 @@ sub service_exists {
 	}
 	
 	notify($ERRORS{'DEBUG'}, 0, "'$service_name' service does not exist on $computer_node_name");
+	$self->{service_init_module}{$service_name} = {};
 	return (wantarray) ? () : 0;
 }
 
@@ -3642,30 +3726,12 @@ sub generate_vclcontrol_sample_files {
 
 =head2 get_firewall_configuration
 
- Parameters  : none
+ Parameters  : $chain (optional)
  Returns     : hash reference
  Description : Retrieves information about the open firewall ports on the
                computer and constructs a hash. The hash keys are protocol names.
                Each protocol key contains a hash reference. The keys are either
                port numbers or ICMP types.
-               Example:
-               
-                  "ICMP" => {
-                    8 => {
-                      "description" => "Allow inbound echo request"
-                    }
-                  },
-                  "TCP" => {
-                    22 => {
-                      "interface_names" => [
-                        "Local Area Connection 3"
-                      ],
-                      "name" => "sshd"
-                    },
-                    3389 => {
-                      "name" => "Remote Desktop",
-                      "scope" => "192.168.53.54/255.255.255.255"
-                    },
 
 =cut
 
@@ -3676,41 +3742,30 @@ sub get_firewall_configuration {
 		return;
 	}
 	
-	my $computer_node_name     = $self->data->get_computer_node_name();
-	my $firewall_configuration = {};
+	my $chain = shift;
 	
-	# Check to see if this distro has iptables
-	# If not return 1 so it does not fail
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	# Check to see if iptables service exists
 	if (!($self->service_exists("iptables"))) {
 		notify($ERRORS{'WARNING'}, 0, "iptables does not exist on this OS");
 		return 1;
 	}
 	
-	my $port_command = "iptables -L --line-number -n";
-	my ($iptables_exit_status, $output_iptables) = $self->execute($port_command);
-	if (!defined($output_iptables)) {
+	my $port_command = "iptables --line-number -n -L";
+	$port_command .= " $chain" if $chain;
+	my ($exit_status, $output) = $self->execute($port_command);
+	if (!defined($output)) {
 		notify($ERRORS{'WARNING'}, 0, "failed to run command to show open firewall ports on $computer_node_name");
 		return;
 	}
 	
-	#notify($ERRORS{'DEBUG'}, 0, "output from iptables:\n" . join("\n", @$output_iptables));
-	
+	#notify($ERRORS{'DEBUG'}, 0, "iptables output:\n" . join("\n", @$output));
 	
 	# Execute the iptables -L --line-number -n command to retrieve firewall port openings
 	# Expected output:
 	# Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 	# num  target     prot opt source               destination
-	# 1    RH-Firewall-1-INPUT  all  --  0.0.0.0/0            0.0.0.0/0
-	
-	# Chain FORWARD (policy ACCEPT)
-	# num  target     prot opt source               destination
-	# 1    RH-Firewall-1-INPUT  all  --  0.0.0.0/0            0.0.0.0/0
-	
-	# Chain OUTPUT (policy ACCEPT)
-	# num  target     prot opt source               destination
-	
-	# Chain RH-Firewall-1-INPUT (2 references)
-	# num  target     prot opt source               destination
 	# 1    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
 	# 2    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
 	# 3    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 255
@@ -3721,78 +3776,55 @@ sub get_firewall_configuration {
 	# 8    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:3389
 	# 9    REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited
 	
-	
-	my $chain;
-	my $previous_protocol;
-	my $previous_port;
-	
-	for my $line (@$output_iptables) {
+	my $firewall_configuration = {};
+	LINE: for my $line (@$output) {
 		if ($line =~ /^Chain\s+(\S+)\s+(.*)/ig) {
 			$chain = $1;
-			notify($ERRORS{'DEBUG'}, 0, "output Chain = $chain");
+			#notify($ERRORS{'DEBUG'}, 0, "processing chain: $chain");
+			next LINE;
 		}
+		
+		#                  1       2          3          4      5       6       7
 		elsif ($line =~ /^(\d+)\s+([A-Z]*)\s+([a-z]*)\s+(--)\s+(\S+)\s+(\S+)\s+(.*)/ig) {
+			my $rule_number      = $1;
+			my $target           = $2;
+			my $protocol         = $3;
+			my $option           = $4;
+			my $scope            = $5;
+			my $destination      = $6;
+			my $other_parameters = $7 || '';
 			
-			my $num         = $1;
-			my $target      = $2;
-			my $protocol    = $3;
-			my $scope       = $5;
-			my $destination = $6;
-			my $port_string = $7 if (defined($7));
-			my $port        = '';
-			my $name;
+			# Check if line was parsed properly
+			if (!$destination) {
+				next LINE;
+			}
 			
+			my ($state) = $other_parameters =~ /state\s+([\w,]+)/i;
+			$state = '' if !defined($state);
 			
-			if (defined($port_string) && ($port_string =~ /([\s(a-zA-Z)]*)(dpt:)(\d+)/ig)) {
-				$port = $3;
-				notify($ERRORS{'DEBUG'}, 0, "output rule: $num, $target, $protocol, $scope, $destination, $port ");
+			my $port;
+			if ($protocol =~ /icmp/i) {
+				($port) = $other_parameters =~ /icmp\s+type\s+(\d+)/;
+				$port = '255' if !defined($port);
 			}
-			
-			if (!$port) {
-				$port = "any";
+			else {
+				($port) = $other_parameters =~ /dpt:(\d+)/;
+				$port = 'any' if !defined($port);
 			}
 			
-			#my $services_cmd = "cat /etc/services";
-			#my ($services_status, $service_output) = $self->execute($services_cmd, 0);
-			#if (!defined($service_output)) {
-			#	notify($ERRORS{'DEBUG'}, 0, "failed to get /etc/services");
-			#}
-			#else {
-			#	for my $sline (@$service_output) {
-			#		if ($sline =~ /(^[_-a-zA-Z1-9]+)\s+($port\/$protocol)\s+(.*) /ig) {
-			#			$name = $1;
-			#		}
-			#	}
-			#	
-			#}
-			
-			$name = $port if (!$name);
-			
-			$firewall_configuration->{$chain}->{$num}{$protocol}{$port}{name}        = $name;
-			$firewall_configuration->{$chain}->{$num}{$protocol}{$port}{number}      = $num;
-			$firewall_configuration->{$chain}->{$num}{$protocol}{$port}{scope}       = $scope;
-			$firewall_configuration->{$chain}->{$num}{$protocol}{$port}{target}      = $target;
-			$firewall_configuration->{$chain}->{$num}{$protocol}{$port}{destination} = $destination;
-			
-			
-			if (!defined($previous_protocol) ||
-             !defined($previous_port) ||
-             !defined($firewall_configuration->{$previous_protocol}) ||
-             !defined($firewall_configuration->{$previous_protocol}{$previous_port})
-             ) {
-         	next;
-      	}
-			elsif ($scope !~ /0.0.0.0\/0/) {
-				$firewall_configuration->{$previous_protocol}{$previous_port}{scope} = $scope;
-			}
+			$firewall_configuration->{$chain}->{$rule_number}{$protocol}{$port} = {
+				target => $target,
+				scope => $scope,
+				destination  => $destination,
+				state  => $state,
+				port  => $port,
+			};
 		}
 	}
 	
 	#The below notify statement can produce large amounts of output over time, if needed to debug a reservation, uncomment the line
 	#notify($ERRORS{'DEBUG'}, 0, "retrieved firewall configuration from $computer_node_name:\n" . format_data($firewall_configuration));
 	return $firewall_configuration;
-	
-	
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -3821,7 +3853,7 @@ sub parse_firewall_scope {
 	}
 	
 	my @scope_strings = @_;
-	if (!@scope_strings) {
+	if (!@scope_strings || !defined($scope_strings[0])) {
 		notify($ERRORS{'WARNING'}, 0, "scope array argument was not supplied");
 		return;
 	}
@@ -3829,6 +3861,11 @@ sub parse_firewall_scope {
 	my @netmask_objects;
 	
 	for my $scope_string (@scope_strings) {
+		if (!$scope_string) {
+			notify($ERRORS{'WARNING'}, 0, "invalid scope string:\n" . join("\n", @scope_strings));
+			return;
+		}
+		
 		if ($scope_string =~ /(\*|Any)/i) {
 			my $netmask_object = new Net::Netmask('any');
 			push @netmask_objects, $netmask_object;
@@ -3921,11 +3958,11 @@ sub parse_firewall_scope {
 		$scope_result_string =~ s/,+$//;
 		my $argument_string = join(",", @scope_strings);
 		if ($argument_string ne $scope_result_string) {
-			notify($ERRORS{'DEBUG'}, 0, "parsed firewall scope:\n" .
-				"argument: '$argument_string'\n" .
-				"result: '$scope_result_string'\n" .
-				"IP address ranges:\n" . join(", ", @ip_address_ranges)
-			);
+			#notify($ERRORS{'DEBUG'}, 0, "parsed firewall scope:\n" .
+			#	"argument: '$argument_string'\n" .
+			#	"result: '$scope_result_string'\n" .
+			#	"IP address ranges:\n" . join(", ", @ip_address_ranges)
+			#);
 		}
 		return $scope_result_string;
 	}
@@ -4036,8 +4073,8 @@ sub firewall_compare_update {
 =head2 clean_iptables
 
  Parameters  : 
- Returns     : 0 , 1
- Description : Deletes rules with any leftover -s addresses 
+ Returns     : 
+ Description : 
 
 =cut
 
@@ -4048,77 +4085,79 @@ sub clean_iptables {
 		return;
 	}
 	
-	# Check to see if this distro has iptables
-	# If not return 1 so it does not fail
-	if (!($self->service_exists("iptables"))) {
+	# Check if iptables service exists
+	if (!$self->service_exists("iptables")) {
 		notify($ERRORS{'WARNING'}, 0, "iptables does not exist on this OS");
 		return 1;
 	}
 	
-	my $computer_node_name   = $self->data->get_computer_node_name();
-	my $reservation_id       = $self->data->get_reservation_id();
-	my $management_node_keys = $self->data->get_management_node_keys();
+	my $computer_node_name = $self->data->get_computer_node_name();
 	
-	# Retrieve the firewall configuration
-	my $INPUT_CHAIN = "INPUT";
+	# Open SSH port 22 to any address before cleaning existing rules to prevent SSH access from being cut off
+	if (!$self->enable_firewall_port("tcp", 22, 'any', 1)) {
+		notify($ERRORS{'WARNING'}, 0, "iptables firewall rules not sanitized because SSH port 22 could not be opened from any address");
+		return;
+	}
 	
-	# Retrieve the iptables file to work on locally
-	my $tmpfile          = "/tmp/" . $reservation_id . "_iptables";
-	my $source_file_path = "/etc/sysconfig/iptables";
-	if (run_scp_command("$computer_node_name:\"$source_file_path\"", $tmpfile, $management_node_keys)) {
-		my @lines;
-		if (open(IPTAB_TMPFILE, $tmpfile)) {
-			@lines = <IPTAB_TMPFILE>;
-			close(IPTAB_TMPFILE);
-		}
-		foreach my $line (@lines) {
-			if ($line =~ s/-A INPUT -s .*\n//) {
-			}
-		}
-		
-		# Rewrite array to tmpfile
-		if (open(IPTAB_TMPFILE, ">$tmpfile")) {
-			print IPTAB_TMPFILE @lines;
-			close(IPTAB_TMPFILE);
-		}
+	my $chain = "INPUT";
+	my @commands;
+	my $firewall_configuration = $self->get_firewall_configuration() || return;
+	RULE: for my $rule_number (reverse sort keys %{$firewall_configuration->{$chain}}) {
+		my $rule = $firewall_configuration->{$chain}{$rule_number};
 		
-		# Copy iptables file back to node
-		if (run_scp_command($tmpfile, "$computer_node_name:\"$source_file_path\"", $management_node_keys)) {
-			notify($ERRORS{'DEBUG'}, 0, "copied $tmpfile to $computer_node_name $source_file_path");
+		for my $protocol (keys %$rule) {
+			for my $port (keys %{$rule->{$protocol}}) {
+				my $scope = $rule->{$protocol}{$port}{scope} || '';
+				my $target = $rule->{$protocol}{$port}{target} || '';
+				my $state = $rule->{$protocol}{$port}{state} || '';
+				
+				if (!$scope || $scope =~ /0\.0\.0\.0/) {
+					#notify($ERRORS{'DEBUG'}, 0, "ignoring rule, scope not specified: protocol/port: $protocol/$port, scope: $scope, target: $target, state: $state");
+					next RULE;
+				}
+				
+				notify($ERRORS{'DEBUG'}, 0, "existing iptables rule will be removed: protocol/port: $protocol/$port, scope: $scope, target: $target, state: $state");
+				push @commands, "iptables -D $chain $rule_number";
+			}
 		}
 	}
 	
+	# If no rules were found, nothing needs to be done
+	if (!@commands) {
+		notify($ERRORS{'DEBUG'}, 0, "no iptables firewall rules need to be cleaned from $computer_node_name");
+		return 1;
+	}
 	
-	#my $command = "sed -i -e '/-A INPUT -s */d' /etc/sysconfig/iptables";
-	#my ($status, $output) = $self->execute($command);
-	
-	#if (defined $status && $status == 0) {
-	#   notify($ERRORS{'DEBUG'}, 0, "executed command $command on $computer_node_name");
-	#}
-	#else {
-	#   notify($ERRORS{'WARNING'}, 0, "output from iptables:" . join("\n", @$output));
-	#}
-	
-	# restart iptables
-	$self->restart_service('iptables');
+	# Join the iptables commands together with ' && '
+	my $command = join(' && ', @commands);
+	notify($ERRORS{'DEBUG'}, 0, "attempting to execute commands to sanitize iptables rules on $computer_node_name:\n" . join("\n", @commands));
 	
-	if ($self->wait_for_ssh(0)) {
-		return 1;
+	my ($exit_status, $output) = $self->execute($command);
+	if (!defined $exit_status) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute commands to sanitize iptables rules on $computer_node_name");
+		return;
+	}
+	elsif ($exit_status == 0) {
+		notify($ERRORS{'DEBUG'}, 0, "sanitized iptables rules on $computer_node_name, command:\n$command\noutput:\n" . join("\n", @$output));
 	}
 	else {
-		notify($ERRORS{'CRITICAL'}, 0, "not able to login via ssh after cleaning_iptables");
-		return 0;
+		notify($ERRORS{'WARNING'}, 0, "failed to sanitized iptables rules on $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output));
+		return;
 	}
-
+	
+	# Save rules to /etc/sysconfig/iptables so changes persist across reboots
+	$self->save_firewall_configuration();
+	
+	return 1;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
 =head2 user_logged_in
 
- Parameters  : 
- Returns     : 
- Description : 
+ Parameters  : $username (optional)
+ Returns     : boolean
+ Description : Determines if the user is currently logged in to the computer.
 
 =cut
 
@@ -4129,38 +4168,68 @@ sub user_logged_in {
 		return;
 	}
 	
-	my $management_node_keys = $self->data->get_management_node_keys();
-	my $computer_node_name   = $self->data->get_computer_node_name();
+	my $computer_node_name = $self->data->get_computer_node_name();
 	
 	# Attempt to get the username from the arguments
 	# If no argument was supplied, use the user specified in the DataStructure
-	my $username = shift;
-	
-	# Remove spaces from beginning and end of username argument
-	# Fixes problem if string containing only spaces is passed
-	$username =~ s/(^\s+|\s+$)//g if $username;
+	my $username = shift || $self->data->get_user_login_id();
 	
-	# Check if username argument was passed
-	if (!$username) {
-		$username = $self->data->get_user_login_id();
+	my @logged_in_users = $self->get_logged_in_users();
+	if (grep { $username eq $_ } @logged_in_users) {
+		notify($ERRORS{'DEBUG'}, 0, "$username is logged in to $computer_node_name");
+		return 1;
 	}
-	notify($ERRORS{'DEBUG'}, 0, "checking if $username is logged in to $computer_node_name");
-	
-	my $cmd = "users";
-	my ($logged_in_status, $logged_in_output) = $self->execute($cmd);
-	if (!defined($logged_in_output)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to run who command ");
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "$username is NOT logged in to $computer_node_name");
+		return 0;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_logged_in_users
+
+ Parameters  : none
+ Returns     : array
+ Description : Retrieves the names of users logged in to the computer.
+
+=cut
+
+sub get_logged_in_users {
+	my $self = shift;
+	if (ref($self) !~ /linux/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
 		return;
 	}
-	elsif (grep(/$username/i, @$logged_in_output)) {
-		notify($ERRORS{'DEBUG'}, 0, "username $username is logged into $computer_node_name\n" . join("\n", @$logged_in_output));
-		return 1;
 	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	my $command = "users";
+	my ($exit_status, $output) = $self->execute($command);
+	
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run command to determine logged in users on $computer_node_name: $command");
+		return;
+	}
+	elsif (grep(/^users:/, @$output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to determine logged in users on $computer_node_name, command: $command, output:\n" . join("\n", @$output));
+		return;
 	}
 	
+	my @usernames;
+	for my $line (@$output) {
+		my @line_usernames = split(/[\s+]/, $line);
+		push @usernames, @line_usernames if @line_usernames;
+	}
 	
-	return 0;
-
+	my $username_count = scalar(@usernames);
+	if ($username_count) {
+		notify($ERRORS{'DEBUG'}, 0, "$username_count user" . ($username_count == 1 ? '' : 's') . " logged in to $computer_node_name: " . join(', ', @usernames));
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "no users logged in to $computer_node_name");
+	}
+	return @usernames;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -4225,8 +4294,8 @@ sub clean_known_files {
 
 =head2 user_exists
 
- Parameters  : 
- Returns     : 
+ Parameters  : $username (optional)
+ Returns     : boolean
  Description : 
 
 =cut
@@ -4764,6 +4833,630 @@ sub notify_user_console {
 	}
 }
 
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 is_64_bit
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Determines if the OS is 64-bit or not.
+
+=cut
+
+sub is_64_bit {
+	my $self = shift;
+	if (ref($self) !~ /linux/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	my $command = 'uname -m';
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute '$command' command to determine if $computer_node_name contains a 64-bit Linux OS");
+		return;
+	}
+	elsif (grep(/uname:/, @$output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to determine if $computer_node_name contains a 64-bit Linux OS, command: '$command', output:\n" . join("\n", @$output));
+		return;
+	}
+	elsif (grep(/64/, @$output)) {
+		#notify($ERRORS{'DEBUG'}, 0, "$computer_node_name contains a 64-bit Linux OS, output:\n" . join("\n", @$output));
+		return 1;
+	}
+	else {
+		#notify($ERRORS{'DEBUG'}, 0, "$computer_node_name does NOT contain a 64-bit Linux OS, output:\n" . join("\n", @$output));
+		return 0;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_user_remote_ip_addresses
+
+ Parameters  : none
+ Returns     : hash reference
+ Description : Retrieves info regarding users connected to the computer. A hash
+               reference is returned. The hash keys are the usernames logged in.
+               The value of each username key is an array reference containing
+               the remote IP addresses that user is connected from. Example:
+                  {
+                    "admin" => [
+                      "152.1.1.1"
+                    ],
+                    "root" => [
+                      "10.1.1.1"
+                    ]
+                  }
+
+=cut
+
+sub get_user_remote_ip_addresses {
+	my $self = shift;
+	if (ref($self) !~ /linux/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	my $who_command = 'who -u';
+	my ($who_exit_status, $who_output) = $self->execute($who_command, 0);
+	if (!defined($who_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute '$who_command' command to determine users connected to $computer_node_name");
+		return;
+	}
+	elsif (grep(/who:/, @$who_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to determine users connected to $computer_node_name, exit status: $who_exit_status, command: '$who_command', output:\n" . join("\n", @$who_output));
+		return;
+	}
+	
+	my $connected_user_info = {};
+	for my $line (@$who_output) {
+		# NAME     LINE         TIME             IDLE          PID COMMENT
+		# root     pts/0        2014-03-12 11:32   .          5403 (10.1.0.1)
+		# admin    pts/1        2014-03-11 13:34 22:01        4336 (152.1.1.1)
+		
+		my ($username, $remote_ip) = $line =~ /^([^\s]+).+\(([\d\.]+)\)/;
+		if ($username && $remote_ip) {
+			if (!defined($connected_user_info->{$username})) {
+				$connected_user_info->{$username} = [];
+			}
+			push @{$connected_user_info->{$username}}, $remote_ip;
+		}
+	}
+	
+	if ($connected_user_info) {
+		notify($ERRORS{'DEBUG'}, 0, "retrieved user connection info using the 'who' command from $computer_node_name:\n" . format_data($connected_user_info));
+		return $connected_user_info;
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "did not detect any users connected to $computer_node_name using the 'who' command, attempting to call 'last'");
+	}
+	
+	my $last_command = 'last';
+	my ($last_exit_status, $last_output) = $self->execute($last_command, 0);
+	if (!defined($last_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute '$last_command' command to determine users connected to $computer_node_name");
+		return;
+	}
+	elsif (grep(/last:/, @$last_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to determine users connected to $computer_node_name, exit status: $last_exit_status, command: '$last_command', output:\n" . join("\n", @$last_output));
+		return;
+	}
+	
+	for my $line (@$last_output) {
+		# root     pts/0        10.1.0.1      Wed Mar 12 11:32   still logged in
+		# admin    pts/1        152.1.1.1     Tue Mar 11 13:34   still logged in
+		# root     pts/0        10.1.0.1      Tue Mar 11 13:23 - 10:47  (21:24)
+		
+		my ($username, $remote_ip) = $line =~ /^([^\s]+).+[\s\t](\d+\.\d+\.\d+\.\d+)[\s\t].*logged\s+in/i;
+		if ($username && $remote_ip) {
+			if (!defined($connected_user_info->{$username})) {
+				$connected_user_info->{$username} = [];
+			}
+			push @{$connected_user_info->{$username}}, $remote_ip;
+		}
+	}
+	
+	if ($connected_user_info) {
+		notify($ERRORS{'DEBUG'}, 0, "retrieved user connection info using the 'last' command from $computer_node_name:\n" . format_data($connected_user_info));
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "did not detect any users connected to $computer_node_name");
+	}
+	return $connected_user_info;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_port_connection_info
+
+ Parameters  : none
+ Returns     : hash reference
+ Description : Retrieves information about established connections from the
+               computer. A hash is constructed:
+               {
+                 "tcp" => {
+                   22 => [
+                     {
+                       "local_ip" => "10.25.10.194",
+                       "pid" => 5400,
+                       "program" => "sshd",
+                       "remote_ip" => "10.25.0.241"
+                     },
+                     {
+                       "local_ip" => "192.168.18.135",
+                       "pid" => 5689,
+                       "program" => "sshd",
+                       "remote_ip" => "192.168.53.54"
+                     },
+                   ],
+                   3389 => [
+                     {
+                       "local_ip" => "192.168.18.135",
+                       "pid" => 6767,
+                       "program" => "xrdp",
+                       "remote_ip" => "192.168.53.54"
+                     }
+                   ]
+                 }
+               }
+
+=cut
+
+sub get_port_connection_info {
+	my $self = shift;
+	if (ref($self) !~ /linux/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	my $command = "netstat -anp";
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command: $command");
+		return;
+	}
+	elsif (grep(/^netstat: /, @$output)) {
+		notify($ERRORS{'WARNING'}, 0, "error occurred executing command: '$command', exit status: $exit_status, output:\n" . join("\n", @$output));
+		return;
+	}
+	
+	my $connection_info = {};
+	for my $line (@$output) {
+		# Proto Recv-Q Send-Q Local Address      Foreign Address     State        PID/Program name
+		# tcp        0      0 192.168.13.220:22  152.1.1.100:63497   ESTABLISHED  6199/sshd
+		# tcp        0      0 10.10.3.220:22     10.10.14.13:52239   ESTABLISHED  5189/sshd
+		my ($protocol, $local_ip_address, $port, $remote_ip_address, $state, $pid, $program) = $line =~ /^(\w+).+\s([\d\.]+):(\d+)\s+([\d\.]+):\d*\s+(\w+)\s+(\d+)?\/?(\w+)?/i;
+		if (!$state || $state !~ /ESTABLISHED/i) {
+			next;
+		}
+		
+		my $connection = {
+			remote_ip => $remote_ip_address,
+			local_ip => $local_ip_address,
+		};
+		$connection->{pid} = $pid if $pid;
+		$connection->{program} = $program if $program;
+		
+		push @{$connection_info->{$protocol}{$port}}, $connection;
+	}
+	
+	if ($connection_info) {
+		#notify($ERRORS{'DEBUG'}, 0, "retrieved connection info from $computer_node_name:\n" . format_data($connection_info));
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "did not detect any connections on $computer_node_name");
+	}
+	return $connection_info;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 enable_firewall_port_new
+ 
+ Parameters  : $protocol, $port, $scope (optional), $overwrite_existing (optional)
+ Returns     : boolean
+ Description : Enables an iptables firewall port. The protocol and port
+               arguments must be supplied. Port may be an integer, '*', or
+               'any'.
+               
+               If protocol is '*' or 'any' and the protocol is anything other
+               than ICMP, the scope argument must be specified otherwise the
+               firewall would be opened to any port from any address.
+               
+               If supplied, the scope argument must be in one of the following
+               forms:
+               x.x.x.x (10.1.1.1)
+               x.x.x.x/y (10.1.1.1/24)
+               x.x.x.x/y.y.y.y (10.1.1.1/255.255.255.0)
+               
+               If the $overwrite_existing argument is supplied, all existing
+               rules matching the protocol and port will be removed and
+               replaced.
+ 
+=cut
+
+sub enable_firewall_port_new {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my ($protocol, $port, $scope_argument, $overwrite_existing) = @_;
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	# Make sure iptables service exists
+	if (!$self->service_exists("iptables")) {
+		notify($ERRORS{'DEBUG'}, 0, "iptables service does NOT exist on $computer_node_name");
+		return 1;
+	}
+	
+	# Check the protocol argument
+	if (!defined($protocol)) {
+		notify($ERRORS{'WARNING'}, 0, "protocol argument was not supplied");
+		return;
+	}
+	elsif ($protocol !~ /^\w+$/i) {
+		notify($ERRORS{'WARNING'}, 0, "protocol argument is not valid: '$protocol'");
+		return;
+	}
+	$protocol = lc($protocol);
+	
+	# Check the port argument
+	if (!defined($port)) {
+		notify($ERRORS{'WARNING'}, 0, "port argument was not supplied");
+		return;
+	}
+	elsif ($port =~ /^(any|\*)$/i) {
+		# If the port argument is unrestricted, the scope argument must be specified
+		if ($protocol !~ /icmp/i && !$scope_argument) {
+			notify($ERRORS{'WARNING'}, 0, "firewall not modified, port argument is not restricted to a certain port: '$port', scope argument was not supplied, it must be restricted to certain IP addresses if the port argument is unrestricted");
+			return;
+		}
+		
+		# Check if icmp/* was specified, change the port to 255 (all ICMP types)
+		if ($protocol =~ /icmp/i) {
+			$port = 255;
+		}
+		else {
+			$port = 'any';
+		}
+	}
+	elsif ($port !~ /^\d+$/i) {
+		notify($ERRORS{'WARNING'}, 0, "port argument is not valid: '$port', it must be an integer, '*', or 'any'");
+		return;
+	}
+	
+	# Check the scope argument
+	if (!$scope_argument) {
+		$scope_argument = 'any';
+	}
+	my $parsed_scope_argument = $self->parse_firewall_scope($scope_argument);
+	if (!$parsed_scope_argument) {
+		notify($ERRORS{'WARNING'}, 0, "failed to parse firewall scope argument: '$scope_argument'");
+		return;
+	}
+	
+	my $chain = "INPUT";
+	my @commands;
+	my $new_scope = '';
+	my $existing_scope_string = '';
+	
+	# Loop through the rules
+	# Important: reverse sort the rules because some rules may be deleted
+	# They must be deleted in order from highest rule number to lowest
+	# Otherwise, unintended rules will be deleted unless the rule numbers are adjusted
+	my $firewall_configuration = $self->get_firewall_configuration() || return;
+	RULE: for my $rule_number (reverse sort keys %{$firewall_configuration->{$chain}}) {
+		my $rule = $firewall_configuration->{$chain}{$rule_number};
+		
+		# Check if the rule matches the protocol and port arguments
+		if (!defined($rule->{$protocol}{$port})) {
+			next RULE;
+		}
+		
+		# Ignore rule is existing scope isn't defined
+		my $existing_scope = $rule->{$protocol}{$port}{scope};
+		if (!defined($existing_scope)) {
+			notify($ERRORS{'DEBUG'}, 0, "ignoring rule $rule_number, existing scope is NOT specified:\n" . format_data($rule->{$protocol}{$port}));
+			next RULE;
+		}
+		$existing_scope_string .= "$existing_scope,";
+		
+		# Existing rules will be replaced with new rules which consolidate overlapping scopes
+		# This helps reduce duplicate rules and the number of individual rules
+		push @commands, "iptables -D $chain $rule_number";
+	}
+	
+	# Combine all of the existing scopes matching the protocol/port
+	my $parsed_existing_scope;
+	if ($existing_scope_string) {
+		$parsed_existing_scope = $self->parse_firewall_scope($existing_scope_string);
+		if (!$parsed_existing_scope) {
+			notify($ERRORS{'WARNING'}, 0, "failed to parse existing firewall scope: '$existing_scope_string'");
+			return;
+		}
+	}
+	
+	if (!$parsed_existing_scope) {
+		$new_scope = $parsed_scope_argument;
+		notify($ERRORS{'DEBUG'}, 0, "existing $protocol/$port firewall rule does not exist, new rule will be added, scope: $new_scope");
+	}
+	elsif ($overwrite_existing) {
+		if ($parsed_existing_scope eq $parsed_scope_argument) {
+			notify($ERRORS{'DEBUG'}, 0, "firewall modification for $protocol/$port not necessary, existing scope matches scope argument: $parsed_scope_argument");
+			return 1;
+		}
+		else {
+			$new_scope = $parsed_scope_argument;
+			notify($ERRORS{'DEBUG'}, 0, "overwrite existing argument specified, existing $protocol/$port firewall rule(s) will be replaced:\n" .
+				"existing scope: $parsed_existing_scope\n" .
+				"new scope: $new_scope"
+			);
+		}
+	}
+	else {
+		# Combine the existing matching scopes and the scope argument, then check if anything needs to be done
+		my $appended_scope = $self->parse_firewall_scope("$parsed_scope_argument,$parsed_existing_scope");
+		if (!$appended_scope) {
+			notify($ERRORS{'WARNING'}, 0, "failed to parse firewall scope argument appended with existing scope: '$parsed_scope_argument,$parsed_existing_scope'");
+			return;
+		}
+		
+		if ($parsed_existing_scope eq $appended_scope) {
+			notify($ERRORS{'DEBUG'}, 0, "firewall modification for $protocol/$port not necessary, existing scope includes entire scope argument:\n" .
+				"scope argument: $parsed_scope_argument\n" .
+				"existing scope: $parsed_existing_scope"
+			);
+			return 1;
+		}
+		else {
+			$new_scope = $appended_scope;
+			if ($parsed_scope_argument eq $appended_scope) {
+				notify($ERRORS{'DEBUG'}, 0, "existing $protocol/$port rule(s) will be replaced, scope argument includes entire existing scope:\n" .
+					"scope argument: $parsed_scope_argument\n" .
+					"existing scope: $parsed_existing_scope\n" .
+					"new scope: $new_scope"
+				);
+			}
+			else {
+				notify($ERRORS{'DEBUG'}, 0, "new $protocol/$port rule will be added, existing scope does NOT intersect scope argument:\n" .
+					"scope argument: $parsed_scope_argument\n" .
+					"existing scope: $parsed_existing_scope\n" .
+					"new scope: $new_scope"
+				);
+			}
+		}
+	}
+	
+	# Add the new rule to the array of iptables commands
+	my $new_rule_command;
+	$new_rule_command .= "/sbin/iptables -v -I INPUT 1";
+	$new_rule_command .= " -p $protocol";
+	$new_rule_command .= " -j ACCEPT";
+	$new_rule_command .= " -s $new_scope";
+	
+	if ($protocol =~ /icmp/i) {
+		if ($port ne '255') {
+			$new_rule_command .= "  --icmp-type $port";
+		}
+	}
+	else {
+		$new_rule_command .= " -m state --state NEW,RELATED,ESTABLISHED";
+		
+		if ($port =~ /^\d+$/) {
+			$new_rule_command .= " -m $protocol --dport $port";
+		}
+	}
+	
+	push @commands, $new_rule_command;
+	
+	# Join the iptables commands together with ' && '
+	my $command = join(' && ', @commands);
+	
+	# Make backup copy of original iptables configuration
+	my $iptables_backup_file_path = "/etc/sysconfig/iptables_pre_$protocol-$port";
+	if (!$self->copy_file("/etc/sysconfig/iptables", $iptables_backup_file_path)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to back up original iptables file to: '$iptables_backup_file_path'");
+	}
+	
+	#notify($ERRORS{'DEBUG'}, 0, "attempting to execute iptables commands on $computer_node_name:\n" . join("\n", @commands));
+	my ($exit_status, $output) = $self->execute($command, 0);
+	if (!defined $exit_status) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute iptables commands to enable firewall port on $computer_node_name, protocol: $protocol, port: $port, scope: $new_scope");
+		return;
+	}
+	elsif ($exit_status == 0) {
+		notify($ERRORS{'DEBUG'}, 0, "enabled firewall port on $computer_node_name, protocol: $protocol, port: $port, scope: $new_scope");
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to enable firewall port on $computer_node_name, protocol: $protocol, port: $port, scope: $new_scope, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output));
+		return;
+	}
+	
+	# Save rules to /etc/sysconfig/iptables so changes persist across reboots
+	$self->save_firewall_configuration();
+	
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 disable_firewall_port_new
+ 
+ Parameters  : $protocol, $port
+ Returns     : boolean
+ Description : 
+ 
+=cut
+
+sub disable_firewall_port_new {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my ($protocol, $port) = @_;
+
+	# Check to see if this distro has iptables
+	# If not return 1 so it does not fail
+	if (!($self->service_exists("iptables"))) {
+		notify($ERRORS{'WARNING'}, 0, "iptables does not exist on this OS");
+		return 1;
+	}
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	# Check the protocol argument
+	if (!defined($protocol)) {
+		notify($ERRORS{'WARNING'}, 0, "protocol argument was not supplied");
+		return;
+	}
+	elsif ($protocol !~ /^\w+$/i) {
+		notify($ERRORS{'WARNING'}, 0, "protocol argument is not valid: '$protocol'");
+		return;
+	}
+	$protocol = lc($protocol);
+	
+	# Check the port argument
+	if (!defined($port)) {
+		notify($ERRORS{'WARNING'}, 0, "port argument was not supplied");
+		return;
+	}
+	elsif (!$port) {
+		notify($ERRORS{'WARNING'}, 0, "port argument is not valid: '$port'");
+		return;
+	}
+	elsif ($port =~ /^(any|\*)$/) {
+		if ($protocol =~ /icmp/i) {
+			$port = 255;
+		}
+		else {
+			notify($ERRORS{'WARNING'}, 0, "port argument invalid: '$port', protocol argument: '$protocol', port must be an integer");
+			return;
+		}
+	}
+	elsif ($port !~ /^\d+$/i) {
+		notify($ERRORS{'WARNING'}, 0, "port argument is not valid: '$port'");
+		return;
+	}
+	elsif ($port eq '22') {
+		notify($ERRORS{'CRITICAL'}, 0, "disabling firewall port 22 is not allowed because it will cut off access from the management node");
+		return;
+	}
+	
+	my $chain = "INPUT";
+	my @commands;
+	my $new_scope;
+	my $existing_scope_string;
+	
+	my $firewall_configuration = $self->get_firewall_configuration() || return;
+	RULE: for my $rule_number (reverse sort keys %{$firewall_configuration->{$chain}}) {
+		my $rule = $firewall_configuration->{$chain}{$rule_number};
+		
+		# Check if the rule matches the protocol and port arguments
+		if ($protocol =~ /icmp/i && $port eq 255) {
+			if (!defined($rule->{$protocol})) {
+				#notify($ERRORS{'DEBUG'}, 0, "ignoring rule $rule_number, protocol is not $protocol:\n" . format_data($rule));
+				next;
+			}
+		}
+		elsif (!defined($rule->{$protocol}{$port})) {
+			#notify($ERRORS{'DEBUG'}, 0, "ignoring rule $rule_number, does not match protocol/port $protocol/$port:\n" . format_data($rule));
+			next RULE;
+		}
+		else {
+			# Only remove if target is 'ACCEPT'
+			my $target = $rule->{$protocol}{$port}{target};
+			if (!defined($target)) {
+				#notify($ERRORS{'WARNING'}, 0, "ignoring rule $rule_number, 'target' is not defined:\n" . format_data($rule));
+				next RULE;
+			}
+			elsif ($target !~ /ACCEPT/i) {
+				#notify($ERRORS{'DEBUG'}, 0, "ignoring rule $rule_number, 'target' is not 'ACCEPT':\n" . format_data($rule));
+				next RULE;
+			}
+		}
+		
+		push @commands, "iptables -D $chain $rule_number";
+	}
+	
+	# If no existing rules were found, nothing needs to be done
+	if (!@commands) {
+		notify($ERRORS{'DEBUG'}, 0, "firewall port $protocol/$port is not open on $computer_node_name");
+		return 1;
+	}
+	
+	# Join the iptables commands together with ' && '
+	my $command = join(' && ', @commands);
+	#notify($ERRORS{'DEBUG'}, 0, "attempting to execute iptables commands on $computer_node_name:\n" . join("\n", @commands));
+	my ($exit_status, $output) = $self->execute($command);
+	if (!defined $exit_status) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute iptables commands to disable firewall port $protocol/$port on $computer_node_name");
+		return;
+	}
+	elsif ($exit_status == 0) {
+		notify($ERRORS{'DEBUG'}, 0, "disabled firewall port $protocol/$port on $computer_node_name");
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to disable firewall port $protocol/$port on $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output));
+		return;
+	}
+	
+	# Save rules to /etc/sysconfig/iptables so changes persist across reboots
+	$self->save_firewall_configuration();
+	
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 save_firewall_configuration
+ 
+ Parameters  : none
+ Returns     : boolean
+ Description : Calls iptables-save to save the current iptables firewall
+               configuration to /etc/sysconfig/iptables.
+ 
+=cut
+
+sub save_firewall_configuration {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	
+	my $iptables_file_path = '/etc/sysconfig/iptables';
+	my $command = "/sbin/iptables-save > $iptables_file_path";
+	my ($exit_status, $output) = $self->execute($command);
+	if (!defined $output) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command to save iptables configuration on $computer_node_name:\n$command");
+		return;
+	}
+	elsif ($exit_status == 0) {
+		notify($ERRORS{'DEBUG'}, 0, "saved iptables configuration on $computer_node_name: $iptables_file_path");
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to save iptables configuration on $computer_node_name, exit status: $exit_status, command: $command, output:\n" . join("\n", @$output));
+		return;
+	}
+	
+	return 1;
+}
+
 ##/////////////////////////////////////////////////////////////////////////////
 1;
 __END__