#!/usr/bin/perl -w
###############################################################################
# $Id$
###############################################################################
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################

=head1 NAME

VCL::Module::OS::OSX.pm - OSX  support module

=head1 SYNOPSIS

 Needs to be written

=head1 DESCRIPTION

 This module provides VCL support for OSX operating systems.

=cut

###############################################################################
package VCL::Module::OS::OSX;

# Specify the lib path using FindBin
use FindBin;
use lib "$FindBin::Bin/../../..";

# Configure inheritance
use base qw(VCL::Module::OS);

# Specify the version of this module
our $VERSION = '2.5.1';

# Specify the version of Perl to use
use 5.008000;

use strict;
use warnings;
use diagnostics;
use English '-no_match_vars';
use VCL::utils;
use File::Basename;
#no warnings 'redefine';

###############################################################################

=head1 CLASS VARIABLES

=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:
               /opt/vcl/tools/OSX

=cut

our $SOURCE_CONFIGURATION_DIRECTORY = "$TOOLS/OSX";

=head2 $NODE_CONFIGURATION_DIRECTORY

 Data type   : String
 Description : Location on computer loaded with a VCL image where configuration
               files and scripts reside.

=cut

our $NODE_CONFIGURATION_DIRECTORY = '/var/root/VCL';

###############################################################################

=head1 INTERFACE OBJECT METHODS

=cut

#//////////////////////////////////////////////////////////////////////////////

=head2 pre_capture

 Parameters  : Hash containing 'end_state' key
 Returns     : 1 - success , 0 - failure
 Description : Performs the steps necessary to prepare a OSX OS before an
               image is captured.
               This subroutine is called by a provisioning module's capture()
               subroutine.
               
               The steps performed are:
               logout and delete users which were created for imaging reservation - done
               set root password - done
               set administrator password - done
               clear tmp files - done
               disable screen saver if VM - not done
               disable RDP access ... off by default --- done
               enable ssh access - done
               enable ping - not done
               start firewall -- done
               shutdown - done

=cut

sub pre_capture {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}

	my $computer_node_name = $self->data->get_computer_node_name();

	my $args = shift;

#	print "*** ".ref($self)."***\n";


	# Check if end_state argument was passed
	if (defined $args->{end_state}) {
		$self->{end_state} = $args->{end_state};
	}
	else {
		$self->{end_state} = 'off';
	}

	notify($ERRORS{'OK'}, 0, "beginning OSX image PRE_CAPTURE() preparation tasks on $computer_node_name");

	# copy pre_capture configuration files to the computer (scripts, etc)
	if (!$self->copy_capture_configuration_files()) {
		notify($ERRORS{'WARNING'}, 0, "unable to copy OSX script files to $computer_node_name");
		return 0;
	}

	# Log off users which were created for the imaging reservation
	if (!$self->logoff_users()) {
		notify($ERRORS{'WARNING'}, 0, "unable to log off all currently logged in users on $computer_node_name");
		return 0;
	}

	# block rdp via firewall
	if (!$self->firewall_disable_rdp(1)) {
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name failed to disable rdp");
		return 0;
	}

	# Delete the user assigned to this reservation
	my $deleted_user = $self->delete_user();
	if (!$deleted_user) {
		notify($ERRORS{'WARNING'}, 0, "pre_capture was unable to delete user");
	}

	# set root account password to known value
	# borrow the WINDOWS_ROOT_PASSW0RD from vcld.conf
	if (!$self->set_password("root", $WINDOWS_ROOT_PASSWORD)) {
		notify($ERRORS{'WARNING'}, 0, "unable to set root password");
		return 0;
	}

	# set administrator account password to known value
	if (!$self->set_password("administrator", $WINDOWS_ROOT_PASSWORD)) {
		notify($ERRORS{'WARNING'}, 0, "unable to set root password");
		return 0;
	}

	# Shutdown node
	if (!$self->shutdown()) {
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name failed to shutdown");
		return 0;
	}
	
	notify($ERRORS{'OK'}, 0, "pre_capture returning 1");
	return 1;

} ## end sub pre_capture

#//////////////////////////////////////////////////////////////////////////////

=head2 post_load

 Parameters  : None
 Returns     : 1 - success , 0 - failure
 Description : Performs the steps necessary to configure a OSX OS after an
               image has been loaded.
               
               This subroutine is called by a provisioning module's load()
               subroutine.
               
               The steps performed are:
               
               wait for ssh to respond -- done
               wait for root to logout -- not done
               logout all currently logged on users ... hopefully not needed -- not done
               # update known_hosts on management node -- not done
               enable ping on private network -- not done
               sync time -- not done
               # remove root password and other private info from vcl config files -- not done
               randomize root password -- done
               randomize administrator password -- done
               imagemeta postoption reboot is set of image ??? -- not done
               rename computer -- not done
               computer hostname -- done
               add line to currentimage.txt indicating post_load has run -- done

=cut

sub post_load {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}
	
	my $computer_node_name    = $self->data->get_computer_node_name();
	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 $image_os_install_type = $self->data->get_image_os_install_type();
	my $imagemeta_postoption  = $self->data->get_imagemeta_postoption();
	
	notify($ERRORS{'OK'}, 0, "beginning OSX POST_LOAD() $image_name on $computer_short_name");
	
	
	# Wait for computer to respond to SSH
	if (!$self->wait_for_response(15, 900, 8)) {
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name never responded to SSH");
		return 0;
	}

   if (!$self->os->update_public_ip_address()) {
      $self->reservation_failed("failed to update public IP address");
   }
	
	my $root_random_password = getpw();
	if ($self->set_password("root", $root_random_password)) {
		notify($ERRORS{'OK'}, 0, "successfully changed root password on $computer_node_name");
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unable to set root password");
		return 0;
	}
	
	my $administrator_random_password = getpw();
	if ($self->set_password("administrator", $administrator_random_password)) {
		notify($ERRORS{'OK'}, 0, "successfully changed administrator password on $computer_node_name");
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unable to set administrator password");
		return 0;
	}
	
	# Check if the imagemeta postoption is set to reboot, reboot if necessary
	if ($imagemeta_postoption =~ /reboot/i) {
		notify($ERRORS{'OK'}, 0, "imagemeta postoption reboot is set for image, rebooting computer");
		if (!$self->reboot()) {
			notify($ERRORS{'WARNING'}, 0, "failed to reboot the computer");
			return 0;
		}
	}
	
	$self->activate_irapp();
	
	# arkurth: added for possible future use, don't have a way to test
	# Use the following line to enable execution of stage scripts:
	# return $self->SUPER::post_load();
	notify($ERRORS{'OK'}, 0, "returning 1");
	return 1;

} ## end sub post_load

#//////////////////////////////////////////////////////////////////////////////

=head2 sanitize

 Parameters  :
 Returns     : 1 - success , 0 - failure
 Description : revert the changes made when preparing a resource for a particular reservation
               
               The steps performed are:
               
               if (user logged in)
               exit
               Firewall close RDP access
               delete user

=cut

sub sanitize {
	my $self = shift;
	if (ref($self) !~ /osx/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();

	notify($ERRORS{'OK'}, 0, "beginning OSX SANITIZE() on $computer_node_name");

	# block rdp via firewall
	if (!$self->firewall_disable_rdp()) {
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name failed to disable rdp");
		return 0;
	}

	# Delete user associated with the reservation
	if ($self->delete_user()) {
		notify($ERRORS{'OK'}, 0, "users have been deleted from $computer_node_name");
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to delete users from $computer_node_name");
		return 0;
	}

	notify($ERRORS{'OK'}, 0, "$computer_node_name has been sanitized");
	return 1;

} ## end sub sanitize


#//////////////////////////////////////////////////////////////////////////////

=head2 reboot

 Parameters  : $wait_for_reboot
 Returns     : 1 - success , 0 - failure
 Description : The steps performed are:
               
               graceful reboot of OS
               force logout of users
               wait for reboot to complete
               returns after reboot is complete
               
               make sure ssh is enabled
               make sure ping is enabled
               reboot
               wait for ssh to be up

=cut

sub reboot {
	my $self = shift;
	if (ref($self) !~ /osx/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();

	notify($ERRORS{'OK'}, 0, "beginning OSX REBOOT() on $computer_node_name");

	# Check if an argument was supplied
	my $wait_for_reboot = shift;
	if (!defined($wait_for_reboot) || $wait_for_reboot !~ /0/) {
		notify($ERRORS{'DEBUG'}, 0, "rebooting $computer_node_name and waiting for ssh to become active");
		$wait_for_reboot = 1;
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "rebooting $computer_node_name and NOT waiting for ssh to become active");
		$wait_for_reboot = 0;
	}

	my $reboot_start_time = time();
	notify($ERRORS{'DEBUG'}, 0, "reboot will be attempted on $computer_node_name");

	# Check if computer responds to ssh before preparing for reboot

	if ($self->wait_for_ssh(0)) {
		# Make sure SSH access is enabled from private IP addresses
		
		my $reboot_command = "/sbin/shutdown -r now";
		my ($reboot_exit_status, $reboot_output) = $self->execute($reboot_command,1);
		if (!defined($reboot_output)) {
			notify($ERRORS{'WARNING'}, 0, "failed to execute ssh command to reboot $computer_node_name");
			return 0;
		}
		
		if ($reboot_exit_status == 0) {
			notify($ERRORS{'OK'}, 0, "executed reboot command on $computer_node_name");
		}
		else {
			notify($ERRORS{'WARNING'}, 0, "failed to reboot $computer_node_name, attempting power reset, output:\n" . join("\n", @$reboot_output));
			
			# Call provisioning module's power_reset() subroutine
			if ($self->provisioner->power_reset()) {
				notify($ERRORS{'OK'}, 0, "initiated power reset on $computer_node_name");
			}
			else {
				notify($ERRORS{'WARNING'}, 0, "reboot failed, failed to initiate power reset on $computer_node_name");
				return 0;
			}
		}
	}
	else {
		# Computer did not respond to ssh
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name did not respond to ssh, graceful reboot cannot be performed, attempting hard reset");
		
		# Call provisioning module's power_reset() subroutine
		if ($self->provisioner->power_reset()) {
			notify($ERRORS{'OK'}, 0, "initiated power reset on $computer_node_name");
		}
		else {
			notify($ERRORS{'WARNING'}, 0, "reboot failed, failed to initiate power reset on $computer_node_name");
			return 0;
		}
	} ## end else [ if ($self->wait_for_ssh(0))
	
	# Check if wait for reboot is set
	if (!$wait_for_reboot) {
		return 1;
	}
	
	my $wait_attempt_limit = 2;
	if ($self->wait_for_reboot($wait_attempt_limit)) {
		# Reboot was successful, calculate how long reboot took
		my $reboot_end_time = time();
		my $reboot_duration = ($reboot_end_time - $reboot_start_time);
		notify($ERRORS{'OK'}, 0, "reboot complete on $computer_node_name, took $reboot_duration seconds");
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "reboot failed on $computer_node_name, made $wait_attempt_limit attempts");
		return 0;
	}
} ## end sub reboot

#//////////////////////////////////////////////////////////////////////////////

=head2 shutdown

 Parameters  : 
 Returns     : 1 - success , 0 - failure
 Description : The steps performed are:
               
               graceful shutdown of OS -- done
               force users to logout -- not done
               waits for shutdown to complete -- done
               returns after complete -- done
               
               # pre_capture

=cut

sub shutdown {
	my $self = shift;
	if (ref($self) !~ /osx/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();

	notify($ERRORS{'OK'}, 0, "beginning OSX SHUTDOWN() on $computer_node_name");
	
	my $command = '/sbin/shutdown -h now';
	
	my ($exit_status, $output) = $self->execute($command,1);
	
	if (defined $exit_status && $exit_status == 0) {
		notify($ERRORS{'DEBUG'}, 0, "executed command to shut down $computer_node_name");
	}
	else {
		if (!defined($output)) {
			notify($ERRORS{'WARNING'}, 0, "failed to execute command to shut down $computer_node_name, attempting power off");
		}
		else {
			notify($ERRORS{'WARNING'}, 0, "failed to shut down $computer_node_name, attempting power off, output:\n" . join("\n", @$output));
		}
		
		# Call provisioning module's power_off() subroutine
		if (!$self->provisioner->power_off()) {
			notify($ERRORS{'WARNING'}, 0, "failed to shut down $computer_node_name, failed to initiate power off");
			return;
		}
	}
	
	# Wait maximum of 3 minutes for the computer to become unresponsive
	if (!$self->wait_for_no_ping(180)) {
		# Computer never stopped responding to ping
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name never became unresponsive to ping after shutdown command was issued");
		return;
	}
	
	# Wait maximum of 5 minutes for computer to power off
	my $power_off = $self->provisioner->wait_for_power_off(300);
	if (!defined($power_off)) {
		# wait_for_power_off result will be undefined if the provisioning module doesn't implement a power_status subroutine
		notify($ERRORS{'OK'}, 0, "unable to determine power status of $computer_node_name from provisioning module, sleeping 1 minute to allow computer time to shutdown");
		sleep 60;
	}
	elsif (!$power_off) {
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name never powered off");
		return;
	}

	return 1;

} ## end sub shutdown

#//////////////////////////////////////////////////////////////////////////////

=head2 reserve

 Parameters  : 
 Returns     : 1 - success , 0 - failure
 Description : adds user to image 
               The steps performed are:
               
               if (!administrator !root)
               useradd
               
               set password

=cut

sub reserve {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}
	
	my $reservation_password = $self->data->get_reservation_password();
	my $username             = $self->data->get_user_login_id();
	my $computer_node_name   = $self->data->get_computer_node_name();
	
	notify($ERRORS{'OK'}, 0, "beginning OSX RESERVE() on $computer_node_name");
	
	# Add the users to the computer
	# The add_users() subroutine will add the reservation user
	if ($self->add_user()) {
		notify($ERRORS{'OK'}, 0, "Successfully added useracct: $username on $computer_node_name");
	}
	else {
		notify($ERRORS{'CRITICAL'}, 0, "Failed to add useracct: $username on $computer_node_name");
		return 0;
	}

	notify($ERRORS{'OK'}, 0, "returning 1");
	return 1;

} ## end sub reserve

#//////////////////////////////////////////////////////////////////////////////
 
=head2 grant_access

 Parameters  : called as an object
 Returns     : 1 - success , 0 - failure
 Description : opens port in firewall for external access

#
# gets called by reserved.pm after the user has clicked "Connect"
# the user's IP address is known when called
# opens firewall for RDP
#

=cut
 
sub grant_access {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}
	
	my $user		= $self->data->get_user_login_id();
	my $computer_node_name	= $self->data->get_computer_node_name();
	my $remote_ip		= $self->data->get_reservation_remote_ip();
	my $request_forimaging	= $self->data->get_request_forimaging();
	
	notify($ERRORS{'OK'}, 0, "GRANT_ACCESS() routine $user,$computer_node_name");
	
	# Check to make sure remote IP is defined
	my $remote_ip_range;
	if (!$remote_ip) {
		notify($ERRORS{'WARNING'}, 0, "reservation remote IP address is not set in the data structure, opening RDP to any address");
	}
	elsif ($remote_ip !~ /^(\d{1,3}\.?){4}$/) {
		notify($ERRORS{'WARNING'}, 0, "reservation remote IP address format is invalid: $remote_ip, opening RDP to any address");
	}
	else {
		# Assemble the IP range string in CIDR notation
		$remote_ip_range = "$remote_ip/24";
		notify($ERRORS{'OK'}, 0, "RDP will be allowed from $remote_ip_range on $computer_node_name");
	}
	
	# Set the $remote_ip_range variable to the string 'all' if it isn't already set (for display purposes)
	$remote_ip_range = 'any' if !$remote_ip_range;
	
	# Allow RDP connections
	if ($request_forimaging) {
		if ($self->firewall_enable_rdp($remote_ip_range,1)) {
			notify($ERRORS{'OK'}, 0, "firewall was configured to allow RDP access from $remote_ip_range on $computer_node_name");
		}
		else {
			notify($ERRORS{'WARNING'}, 0, "firewall could not be configured to grant RDP access from $remote_ip_range on $computer_node_name");
			return 0;
		}
	}
	else {
		if ($self->firewall_enable_rdp($remote_ip_range)) {
			notify($ERRORS{'OK'}, 0, "firewall was configured to allow RDP access from $remote_ip_range on $computer_node_name");
		}
		else {
			notify($ERRORS{'WARNING'}, 0, "firewall could not be configured to grant RDP access from $remote_ip_range on $computer_node_name");
			return 0;
		}
	}
	
	notify($ERRORS{'OK'}, 0, "access has been granted for reservation on $computer_node_name");
	
	return 1;

} ## end sub grant_access


#//////////////////////////////////////////////////////////////////////////////

=head2 enable_firewall_port

 Parameters  : $protocol, $port, $scope (optional)
 Returns     : 1 if succeeded, 0 otherwise
 Description : Enables a firewall port on the computer. The protocol and port
               arguments are required. An optional scope argument may supplied.

# called by OS::process_connect_methods()

=cut

sub enable_firewall_port {
	my $self = shift;
	if (ref($self) !~ /osx/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();

	notify($ERRORS{'OK'}, 0, " beginning OSX ENABLE_FIREWALL_PORT()");

	my $protocol = shift;
	if (!$protocol) {
		notify($ERRORS{'WARNING'}, 0, " protocol variable was not passed as an argument");
		return 0;
	}

	my $port = shift;
	if (!$port) {
		notify($ERRORS{'WARNING'}, 0, " port variable was not passed as an argument");
		return 0;
	}

	my $scope = shift;
	if (!$scope) {
		$scope = 'all';
	}

	my $command = "ipfw list";
	my ($status, $output) = $self->execute($command, 1);
	notify($ERRORS{'DEBUG'}, 0, " checking firewall rules on node $computer_node_name");

	my $rule=0;
	my $upper_limit=12300;
	my $found=0;
	while ($rule == 0  &&  $upper_limit > 0) {
		foreach my $line (@{$output}) {
			if ($line =~ /^$upper_limit\s+/) {
				$found=1;
			}
		}
		if ($found) {
			$upper_limit--;
			$found=0;
		} else {
			$rule = $upper_limit;
		}
	}

	$command = "ipfw add $rule allow $protocol from $scope to any dst-port $port";

	($status, $output) = $self->execute($command, 1);
	notify($ERRORS{'DEBUG'}, 0, "checking connections on node $computer_node_name on port $port");

	return 1;

} ## end sub enable_firewall_port


#//////////////////////////////////////////////////////////////////////////////

=head2 get_cpu_core_count

 Parameters  : none
 Returns     : integer
 Description : Retrieves the number of CPU cores the computer has by querying
               the NUMBER_OF_PROCESSORS environment variable.

# called by Provisioning::VMware:VMware.pm
#       Windows.pm only returns value from database
#       return $self->get_environment_variable_value('NUMBER_OF_PROCESSORS');

=cut

sub get_cpu_core_count {
	my $self = shift;
	if (ref($self) !~ /osx/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 $num_cpus	= 0;
	my $command	= "/usr/sbin/system_profiler SPHardwareDataType";

# Hardware:
#
#     Hardware Overview:
#
#       Model Name: Mac mini
#       Model Identifier: Macmini2,1
#       Processor Speed: 2.66 GHz
#       Number Of Processors: 2
#       Total Number Of Cores: 2
#       L2 Cache (per processor): 4 MB
#       Memory: 7.88 GB
#       Bus Speed: 367 MHz
#       Boot ROM Version: MM21.009A.B00
#       SMC Version (system): 1.30f3
#       Serial Number (system): SOMESRLNMBR
#       Hardware UUID: 9D002E7C-B39B-590F-B9E7-A7AE1554F9E2

	my ($status, $output) = $self->execute($command, 1);
	notify($ERRORS{'DEBUG'}, 0, " getting cpu count on node $computer_node_name ");

	foreach my $line (@{$output}) {
		if ($line =~ /\s+(Total)\s+(Number)\s+(Of)\s+(Cores:)\s+([0-9]*)/) {
			$num_cpus = $line;
			$num_cpus =~ s/ Total Number Of Cores: //;
		}
	}

	notify($ERRORS{'DEBUG'}, 0, " get_cpu_core_count() is $num_cpus");

	return $num_cpus;

}

#//////////////////////////////////////////////////////////////////////////////

=head2 check_connection_on_port

 Parameters  : $port
 Returns     : (connected|conn_wrong_ip|timeout|failed)
 Description : uses netstat to see if any thing is connected to the provided port

# called by OS.pm:is_user_connected()

=cut

sub check_connection_on_port {
	my $self = shift;
	if (ref($self) !~ /osx/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 $remote_ip                   = $self->data->get_reservation_remote_ip();
	my $computer_public_ip_address  = $self->data->get_computer_public_ip_address();

	my $port = shift;
	if (!$port) {
		notify($ERRORS{'WARNING'}, 0, "port variable was not passed as an argument");
		return "failed";
	}

	my $ret_val = "no";
	my $command = "netstat -an";

	my ($status, $output) = $self->execute($command, 1);
	notify($ERRORS{'DEBUG'}, 0, "checking connections on node $computer_node_name on port $port");


	foreach my $line (@{$output}) {
		if ($line =~ /tcp4\s+([0-9]*)\s+([0-9]*)\s+($computer_public_ip_address.$port)\s+($remote_ip).([0-9]*)(.*)(ESTABLISHED)/) {
			$ret_val = "connected";
		}
	}

	return $ret_val;

}


#//////////////////////////////////////////////////////////////////////////////

=head2 user_exists

 Parameters  :
 Returns     :
 Description :

=cut

sub user_exists {
	my $self = shift;
	if (ref($self) !~ /osx/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();

	# Attempt to get the username from the arguments
	# If no argument was supplied, use the user specified in the DataStructure
	my $username = shift;
	if (!$username) {
		$username = $self->data->get_user_login_id();
	}

	notify($ERRORS{'DEBUG'}, 0, "checking if user $username exists on $computer_node_name");

	# Attempt to query the user account
	my $query_user_command = "id $username";
	my ($query_user_exit_status, $query_user_output) = $self->execute($query_user_command,1);
	if (grep(/uid/, @$query_user_output)) {
		notify($ERRORS{'DEBUG'}, 0, "user $username exists on $computer_node_name");
		return 1;
	}
	elsif (grep(/No such user/i, @$query_user_output)) {
		notify($ERRORS{'DEBUG'}, 0, "user $username does not exist on $computer_node_name");
		return 0;
	}
	elsif (defined($query_user_exit_status)) {
		notify($ERRORS{'WARNING'}, 0, "failed to determine if user $username exists on $computer_node_name, exit status: $query_user_exit_status, output:\n@{$query_user_output}");
		return;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to determine if user $username exists on $computer_node_name");
		return;
	}

}





###############################################################################
#											#
#		END OF GLOBALLY REQUIRED OS MODULE SUBROUTINES				#
#											#
###############################################################################


=head1 AUXILIARY OBJECT METHODS

=cut

#//////////////////////////////////////////////////////////////////////////////

=head2 get_node_configuration_directory

 Parameters  : none
 Returns     : string
 Description : Retrieves the $NODE_CONFIGURATION_DIRECTORY variable value for
               the OS. This is the path on the computer's hard drive where image
               configuration files and scripts are copied.

=cut

sub get_node_configuration_directory {
	return $NODE_CONFIGURATION_DIRECTORY;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 copy_capture_configuration_files

 Parameters  : $source_configuration_directory
 Returns     :
 Description : Copies all required configuration files to the computer,
               including scripts, needed to capture an image.
               
# from pre_capture

=cut

sub copy_capture_configuration_files {
	my $self = shift;
	unless (ref($self) && $self->isa('VCL::Module')) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL module object method");
		return;	
	}

        my $computer_node_name		= $self->data->get_computer_node_name();
	my $management_node_keys	= $self->data->get_management_node_keys();

	my $command = "/bin/chmod -R 755 $NODE_CONFIGURATION_DIRECTORY";

	# Get an array containing the configuration directory paths on the management node
	# This is made up of all the the $SOURCE_CONFIGURATION_DIRECTORY values for the OS class and it's parent classes
	# The first array element is the value from the top-most class the OS object inherits from
	my @source_configuration_directories = $self->get_source_configuration_directories();
	if (!@source_configuration_directories) {
		notify($ERRORS{'WARNING'}, 0, "unable to retrieve source configuration directories");
		return;
	}
	
	# Delete existing configuration directory if it exists
	if (!$self->delete_capture_configuration_files()) {
		notify($ERRORS{'WARNING'}, 0, "unable to delete existing capture configuration files");
		return;
	}
	
	# Attempt to create the configuration directory if it doesn't already exist
	if (!$self->create_directory($NODE_CONFIGURATION_DIRECTORY)) {
		notify($ERRORS{'WARNING'}, 0, "unable to create directory on $computer_node_name: $NODE_CONFIGURATION_DIRECTORY");
		return;
	}
	
	# Copy configuration files
	for my $source_configuration_directory (@source_configuration_directories) {
		# Check if source configuration directory exists on this management node
		unless (-d "$source_configuration_directory") {
			notify($ERRORS{'OK'}, 0, "source directory does not exist on this management node: $source_configuration_directory");
			next;
		}
		
		notify($ERRORS{'OK'}, 0, "copying image capture configuration files from $source_configuration_directory to $computer_node_name");
		if (run_scp_command("$source_configuration_directory/*", "$computer_node_name:$NODE_CONFIGURATION_DIRECTORY", $management_node_keys)) {
			notify($ERRORS{'OK'}, 0, "copied $source_configuration_directory directory to $computer_node_name:$NODE_CONFIGURATION_DIRECTORY");
			
			notify($ERRORS{'DEBUG'}, 0, "attempting to set permissions on $computer_node_name:$NODE_CONFIGURATION_DIRECTORY");
			if ($self->execute($command,1)) {
				notify($ERRORS{'OK'}, 0, "chmoded -R 755 $computer_node_name:$NODE_CONFIGURATION_DIRECTORY");
			}
			else {
				notify($ERRORS{'WARNING'}, 0, "could not chmod -R 777 $computer_node_name:$NODE_CONFIGURATION_DIRECTORY");
				return;
			}
		} ## end if (run_scp_command("$source_configuration_directory/*"...
		else {
			notify($ERRORS{'WARNING'}, 0, "failed to copy $source_configuration_directory to $computer_node_name");
			return;
		}
	}
	
	return 1;

} ## end sub copy_capture_configuration_files

#//////////////////////////////////////////////////////////////////////////////

=head2 delete_capture_configuration_files

 Parameters  : 
 Returns     :
 Description : Deletes the capture configuration directory.
               
               # copy_capture_configuration_files

=cut

sub delete_capture_configuration_files {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	# Remove existing configuration files if they exist
	notify($ERRORS{'OK'}, 0, "attempting to remove old configuration directory if it exists: $NODE_CONFIGURATION_DIRECTORY");
	if (!$self->delete_file($NODE_CONFIGURATION_DIRECTORY)) {
		notify($ERRORS{'WARNING'}, 0, "unable to remove existing configuration directory: $NODE_CONFIGURATION_DIRECTORY");
		return 0;
	}
	
	return 1;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 delete_user

 Parameters  :
 Returns     :
 Description :

=cut

sub delete_user {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}

        my $computer_node_name = $self->data->get_computer_node_name();
	
	# Make sure the user login ID was passed
	my $user_login_id = shift;
	
	$user_login_id = $self->data->get_user_login_id() if (!$user_login_id);
	if (!$user_login_id) {
		notify($ERRORS{'WARNING'}, 0, "user could not be determined");
		return 0;
	}
	
	if ($user_login_id eq "root" || $user_login_id eq "administrator" ) {
		notify($ERRORS{'WARNING'}, 0, "$user_login_id MUST not be deleted");
		return 0;
	}

	my $userdel_cmd = $self->get_node_configuration_directory() . "/userdel $user_login_id";
	if ($self->execute($userdel_cmd,1)) {
		notify($ERRORS{'DEBUG'}, 0, "deleted user: $user_login_id from $computer_node_name");
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "failed to delete user: $user_login_id from $computer_node_name");
	}
	
	return 1;

} ## end sub delete_user


#//////////////////////////////////////////////////////////////////////////////

=head2 set_password

 Parameters  : $username, $password
 Returns     : 1 - success , 0 - failure
 Description : sets password for given username
               
# pre_capture

=cut

sub set_password {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}

        my $computer_node_name = $self->data->get_computer_node_name();
	
	my $username = shift;  
	my $password = shift; 
	
	# If no argument was supplied, use the user specified in the DataStructure
	if (!defined($username)) {
		$username = $self->data->get_user_logon_id();
	}
	if (!defined($password)) {
		$password = $self->data->get_reservation_password();
	}
	
	# Make sure both the username and password were determined
	if (!defined($username) || !defined($password)) {
		notify($ERRORS{'WARNING'}, 0, "username and password could not be determined");
		return 0;
	}
	
	# Attempt to set the password
	notify($ERRORS{'DEBUG'}, 0, "setting password of $username to $password on $computer_node_name");
	my $passwd_cmd = "/usr/bin/dscl . -passwd /Users/$username '$password'";
	my ($exit_status1, $output1) = $self->execute($passwd_cmd,1);
	if ($exit_status1 == 0) {
		notify($ERRORS{'OK'}, 0, "password changed to '$password' for user '$username' on $computer_node_name");
	}
	elsif (defined $exit_status1) {
		notify($ERRORS{'WARNING'}, 0, "failed to change password to '$password' for user '$username' on $computer_node_name, exit status: $exit_status1, output:\n@{$output1}");
		return 0;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to change password to '$password' for user '$username' on $computer_node_name");
		return 0;
	}
	
	# Attempt to remove the login.keychain
	if ("$username" eq "administrator" || "$username" eq "root") {
		notify($ERRORS{'DEBUG'}, 0, "removing login.keychain of $username on $computer_node_name");
		my $command2 = "find ~$username/Library/Keychains -type f -name login.keychain -exec rm {} \\;";
		#		my $command2 = "/bin/rm /Users/$username/Library/Keychains/login.keychain";
		my ($exit_status2, $output2) = $self->execute($command2,1);
		if ($exit_status2 == 0) {
			notify($ERRORS{'OK'}, 0, "removed login.keychain for user '$username' on $computer_node_name");
		}
		elsif (defined $exit_status2) {
			notify($ERRORS{'WARNING'}, 0, "failed to remove login.keychain for user '$username' on $computer_node_name, exit status: $exit_status2, output:\n@{$output2}");
			return 0;
		}
		else {
			notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to remove login.keychain for user '$username' on $computer_node_name");
			return 0;
		}
	}
	
	notify($ERRORS{'OK'}, 0, "changed password for user: $username");
	return 1;
} ## end sub set_password


#//////////////////////////////////////////////////////////////////////////////

=head2 file_exists

 Parameters  : $path
 Returns     : boolean
 Description : Checks if a file or directory exists on the OSX computer.
               
               # delete_file

=cut

sub file_exists {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	# Get the path from the subroutine arguments and make sure it was passed
	my $path = shift;
	if (!$path) {
		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
		return;
	}
	
	# Remove any quotes from the beginning and end of the path
	$path = normalize_file_path($path);
	
	# Escape all spaces in the path
	my $escaped_path = escape_file_path($path);
	
	my $computer_short_name = $self->data->get_computer_short_name();
	
	# 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,1);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to determine if file or directory exists on $computer_short_name:\npath: '$path'\ncommand: '$command'");
		return;
	}
	elsif (grep(/no such file/i, @$output)) {
		notify($ERRORS{'DEBUG'}, 0, "file or directory does not exist on $computer_short_name: '$path'");
		return 0;
	}
	elsif (grep(/stat: /i, @$output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to determine if file or directory exists on $computer_short_name:\npath: '$path'\ncommand: '$command'\nexit status: $exit_status, output:\n" . join("\n", @$output));
		return;
	}
	
	# Count the lines beginning with "Size:" and ending with "file", "directory", or "link" to determine how many files and/or directories were found
	my $files_found = grep(/^\s*Size:.*file$/i, @$output);
	my $directories_found = grep(/^\s*Size:.*directory$/i, @$output);
	my $links_found = grep(/^\s*Size:.*link$/i, @$output);
	
	if ($files_found || $directories_found || $links_found) {
		notify($ERRORS{'DEBUG'}, 0, "'$path' exists on $computer_short_name, files: $files_found, directories: $directories_found, links: $links_found");
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to determine if file or directory exists on $computer_short_name: '$path'\ncommand: '$command'\nexit status: $exit_status, output:\n" . join("\n", @$output));
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 delete_file

 Parameters  : $path
 Returns     : boolean
 Description : Deletes files or directories on the OSX computer.

=cut

sub delete_file {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	# Get the path argument
	my $path = shift;
	if (!$path) {
		notify($ERRORS{'WARNING'}, 0, "path argument were not specified");
		return;
	}
	
	# Remove any quotes from the beginning and end of the path
	$path = normalize_file_path($path);
	
	# Escape all spaces in the path
	my $escaped_path = escape_file_path($path);
	
	my $computer_short_name = $self->data->get_computer_short_name();
	
	# Delete the file
	my $command = "rm -rfv $escaped_path";
	my ($exit_status, $output) = $self->execute($command,1);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to run command to delete file or directory on $computer_short_name:\npath: '$path'\ncommand: '$command'");
		return;
	}
	elsif (grep(/(cannot access|no such file)/i, @$output)) {
		notify($ERRORS{'OK'}, 0, "file or directory not deleted because it does not exist on $computer_short_name: $path");
	}
	elsif (grep(/rm: /i, @$output)) {
		notify($ERRORS{'WARNING'}, 0, "error occurred attempting to delete file or directory on $computer_short_name: '$path':\ncommand: '$command'\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
	}
	else {
		notify($ERRORS{'OK'}, 0, "deleted '$path' on $computer_short_name");
	}
	
	# Make sure the path does not exist
	my $file_exists = $self->file_exists($path);
	if (!defined($file_exists)) {
		notify($ERRORS{'WARNING'}, 0, "failed to confirm file doesn't exist on $computer_short_name: '$path'");
		return;
	}
	elsif ($file_exists) {
		notify($ERRORS{'WARNING'}, 0, "file was not deleted, it still exists on $computer_short_name: '$path'");
		return;
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "confirmed file does not exist on $computer_short_name: '$path'");
		return 1;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 create_directory

 Parameters  : $directory_path, $mode (optional)
 Returns     : boolean
 Description : Creates a directory on the OSX computer as indicated by the
               $directory_path argument.
               
# copy_capture_configuration_files

=cut

sub create_directory {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	# Get the directory path argument
	my $directory_path = shift;
	if (!$directory_path) {
		notify($ERRORS{'WARNING'}, 0, "directory path argument was not supplied");
		return;
	}
	
	# Remove any quotes from the beginning and end of the path
	$directory_path = normalize_file_path($directory_path);
	
	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 \"$directory_path\" 2>&1 || mkdir -p \"$directory_path\" 2>&1 && ls -d \"$directory_path\"";
	my ($exit_status, $output) = $self->execute($command,1);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to run command to create directory on $computer_short_name:\npath: '$directory_path'\ncommand: '$command'");
		return;
	}
	elsif (grep(/mkdir:/i, @$output)) {
		notify($ERRORS{'WARNING'}, 0, "error occurred attempting to create directory on $computer_short_name: '$directory_path':\ncommand: '$command'\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
		return;
	}
	elsif (grep(/^\s*$directory_path\s*$/, @$output)) {
		if (grep(/ls:/, @$output)) {
			notify($ERRORS{'OK'}, 0, "directory created on $computer_short_name: '$directory_path'");
		}
		else {
			notify($ERRORS{'OK'}, 0, "directory already exists on $computer_short_name: '$directory_path'");
		}
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unexpected output returned from command to create directory on $computer_short_name: '$directory_path':\ncommand: '$command'\nexit status: $exit_status\noutput:\n" . join("\n", @$output) . "\nlast line:\n" . string_to_ascii(@$output[-1]));
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 firewall_enable_rdp

 Parameters  :
 Returns     : 1 if succeeded, 0 otherwise
 Description : # grant_access

=cut

sub firewall_enable_rdp {
	my $self = shift;
	if (ref($self) !~ /osx/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 $remote_ip_range = shift;
	my $persist = shift;
	my $fw_enable_rdp_cmd = "";
	
	# Make sure the remote ip range was passed
	if (!$remote_ip_range) {
		notify($ERRORS{'CRITICAL'}, 0, "remote IP range could not be determined, failed to open RDP on $computer_node_name");
		return 0;
	}
	
	if ($persist) {
		$fw_enable_rdp_cmd = $self->get_node_configuration_directory() . "/fw_enable_rdp $remote_ip_range $persist";
	}
	else {
		$fw_enable_rdp_cmd = $self->get_node_configuration_directory() . "/fw_enable_rdp $remote_ip_range";
	}
	if ($self->execute($fw_enable_rdp_cmd,1)) {
		notify($ERRORS{'DEBUG'}, 0, "enabled rdp through firewall on $computer_node_name");
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "failed to enable rdp through firewall on $computer_node_name");
	}
	
	return 1;

} ## end sub firewall_enable_rdp

#//////////////////////////////////////////////////////////////////////////////

=head2 firewall_disable_rdp

 Parameters  : optional persistence flag
 Returns     : 1 if succeeded, 0 otherwise
 Description : 
               
               # pre_capture
               # sanitize

=cut

sub firewall_disable_rdp {
	my $self = shift;
	if (ref($self) !~ /osx/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 $persist = shift;
	my $fw_disable_rdp_cmd;
	
	if ($persist) {
		$fw_disable_rdp_cmd = $self->get_node_configuration_directory() . "/fw_disable_rdp $persist";
	}
	else {
		$fw_disable_rdp_cmd = $self->get_node_configuration_directory() . "/fw_disable_rdp";
	}
	
	if ($self->execute($fw_disable_rdp_cmd,1)) {
		notify($ERRORS{'DEBUG'}, 0, "disabled rdp through firewall on $computer_node_name");
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "failed to disable rdp through firewall on $computer_node_name");
	}
	
	return 1;

} ## end sub firewall_disable_rdp


#//////////////////////////////////////////////////////////////////////////////

=head2 logoff_users

 Parameters  :
 Returns     : 1 if succeeded, 0 otherwise
 Description :
               
# pre_capture

=cut

sub logoff_users {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}

        my $computer_node_name = $self->data->get_computer_node_name();
	
	my $logout_users_cmd = "/usr/bin/killall loginwindow";
	if ($self->execute($logout_users_cmd,1)) {
		notify($ERRORS{'DEBUG'}, 0, "logged off all users on $computer_node_name");
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to log off all users on $computer_node_name");
	}
	
	return 1;

} ## end sub logoff_users


#//////////////////////////////////////////////////////////////////////////////

=head2 get_private_mac_address

 Parameters  : none
 Returns     : string
 Description : Returns the MAC address of the interface assigned the private IP
               address.

=cut

sub get_private_mac_address {
	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 $private_network_configuration = $self->get_network_configuration('private');
	if (!$private_network_configuration) {
		notify($ERRORS{'WARNING'}, 0, "failed to retrieve private network configuration");
		return;
	}
	
	my $private_mac_address = $private_network_configuration->{physical_address};
	if (!$private_mac_address) {
		notify($ERRORS{'WARNING'}, 0, "'physical_address' key is not set in the private network configuration hash:\n" . format_data($private_network_configuration));
		return;
	}
	
	notify($ERRORS{'DEBUG'}, 0, "retrieved private MAC address: $private_mac_address");
	return $private_mac_address;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 get_public_mac_address

 Parameters  : none
 Returns     : string
 Description : Returns the MAC address of the interface assigned the public IP
               address.

=cut

sub get_public_mac_address {
	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 $public_network_configuration = $self->get_network_configuration('public');
	if (!$public_network_configuration) {
		notify($ERRORS{'WARNING'}, 0, "failed to retrieve public network configuration");
		return;
	}
	
	my $public_mac_address = $public_network_configuration->{physical_address};
	if (!$public_mac_address) {
		notify($ERRORS{'WARNING'}, 0, "'physical_address' key is not set in the public network configuration hash:\n" . format_data($public_network_configuration));
		return;
	}
	
	notify($ERRORS{'DEBUG'}, 0, "retrieved public MAC address: $public_mac_address");
	return $public_mac_address;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 get_network_configuration

 Parameters  : $network_type (optional)
 Returns     : hash reference
 Description : Retrieves the network configuration on the OSX computer and
               constructs a hash. A $network_type argument can be supplied
               containing either 'private' or 'public'. If the $network_type
               argument is not supplied, the hash keys are the network interface
               names and the hash reference returned is formatted as follows:
               |--%{eth0}
                  |--%{eth0}{ip_address}
                    |--{eth0}{ip_address}{10.10.4.35} = '255.255.240.0'
                  |--{eth0}{name} = 'eth0'
                  |--{eth0}{physical_address} = '00:50:56:08:00:f8'
               |--%{eth1}
                  |--%{eth1}{ip_address}
                    |--{eth1}{ip_address}{152.1.14.200} = '255.255.255.0'
                  |--{eth1}{name} = 'eth1'
                  |--{eth1}{physical_address} = '00:50:56:08:00:f9'
               |--%{eth2}
                  |--%{eth2}{ip_address}
                    |--{eth2}{ip_address}{10.1.2.33} = '255.255.240.0'
                  |--{eth2}{name} = 'eth2'
                  |--{eth2}{physical_address} = '00:0c:29:ba:c1:77'
               |--%{lo}
                  |--%{lo}{ip_address}
                    |--{lo}{ip_address}{127.0.0.1} = '255.0.0.0'
                  |--{lo}{name} = 'lo'
                  
               If the $network_type argument is supplied, a hash reference is
               returned containing only the configuration for the specified
               interface:
               |--%{ip_address}
                  |--{ip_address}{10.1.2.33} = '255.255.240.0'
               |--{name} = 'eth2'
               |--{physical_address} = '00:0c:29:ba:c1:77'

=cut

sub get_network_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;
	}
	
	# Check if a 'public' or 'private' network type argument was specified
	my $network_type = shift;
	$network_type = lc($network_type) if $network_type;
	if ($network_type && $network_type !~ /(public|private)/i) {
		notify($ERRORS{'WARNING'}, 0, "network type argument can only be 'public' or 'private'");
		return;
	}
	
	my %network_configuration;
	
	# Check if the network configuration has already been retrieved and saved in this object
	if (!$self->{network_configuration}) {
		# Run ipconfig
		my $command = "ifconfig -a";
		my ($exit_status, $output) = $self->execute($command,1); 
		if (!defined($output)) {
			notify($ERRORS{'WARNING'}, 0, "failed to run command to retrieve network configuration: $command");
			return;
		}
		
		# Loop through the ifconfig output lines
		my $interface_name;
		for my $line (@$output) {
			# Extract the interface name from the "flags" line:
			# en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
			if ($line =~ /([^\s:]+).*flags/) {
				$interface_name = $1;
			}
			
			# Skip to the next line if the interface name has not been determined yet
			next if !$interface_name;
			
			# Parse the "ether" line:
			# ether 00:0c:29:e0:2c:6f
			if ($line =~ /ether\s+([\w:]+)/) {
				$network_configuration{$interface_name}{name} = $interface_name;
				$network_configuration{$interface_name}{physical_address} = lc($1);
			}
			
			# Parse the IP address line:
			# inet 137.151.131.151 netmask 0xfffff000 broadcast 137.151.143.255
			# converting from hex - nasty
			if ($line =~ /inet ([\d\.]+) netmask 0x([0123456789abcdef]+) broadcast/) {
				$network_configuration{$interface_name}{ip_address}{$1} = hex(substr($2,0,2)).".".hex(substr($2,2,2)).".".hex(substr($2,4,2)).".".hex(substr($2,6,2));
			}
		}
		
		$self->{network_configuration} = \%network_configuration;
		notify($ERRORS{'DEBUG'}, 0, "retrieved network configuration:\n" . format_data(\%network_configuration));
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "network configuration has already been retrieved");
		%network_configuration = %{$self->{network_configuration}};
	}
	
	# 'public' or 'private' wasn't specified, return all network interface information
	if (!$network_type) {
		return \%network_configuration;
	}
	
	# Determine either the private or public interface name based on the $network_type argument
	my $interface_name;
	if ($network_type =~ /private/i) {
		$interface_name = $self->get_private_interface_name();
	}
	else {
		$interface_name = $self->get_public_interface_name();
	}
	if (!$interface_name) {
		notify($ERRORS{'WARNING'}, 0, "failed to determine the $network_type interface name");
		return;
	}
	
	# Extract the network configuration specific to the public or private interface
	my $return_network_configuration = $network_configuration{$interface_name};
	if (!$return_network_configuration) {
		notify($ERRORS{'WARNING'}, 0, "network configuration does not exist for interface: $interface_name, network configuration:\n" . format_data(\%network_configuration));
		return;
	}
	notify($ERRORS{'DEBUG'}, 0, "returning $network_type network configuration");
	return $return_network_configuration;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 set_post_load_status

 Parameters  : none
 Returns     : boolean
 Description : Adds a line to currentimage.txt indicating the vcld OS post_load
               tasks have run. The format of the line added is:
               vcld_post_load=success (<time>)
               
               This line is checked when a computer is reserved to make sure the
               post_load tasks have run. A computer may be loaded but the
               post_load tasks may not run if it is loaded manually or by some
               other means not controlled by vcld.

=cut

sub set_post_load_status {
	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 $image_os_type	= $self->data->get_image_os_type();
	
	my $time = localtime;
	my $post_load_line = "vcld_post_load=success ($time)";
	my $command;
	
	# Remove existing lines beginning with vcld_post_load
	$command = "sed -i '' -e \'/vcld_post_load.*/d\' currentimage.txt";
	my ($exit_status, $output) = $self->execute($command, 1);
	if (defined($exit_status) && $exit_status == 0) {
		notify($ERRORS{'DEBUG'}, 0, "added line to currentimage.txt on $computer_node_name: '$post_load_line'");
	}
	elsif ($exit_status) {
		notify($ERRORS{'WARNING'}, 0, "failed to add line to currentimage.txt on $computer_node_name: '$post_load_line', exit status: $exit_status, output:\n" . join("\n", @$output));
		return;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to add line to currentimage.txt on $computer_node_name");
		return;
	}
	
	
	# Add a line to the end of currentimage.txt
	$command = "echo \"$post_load_line\" >> currentimage.txt";
	($exit_status, $output) = $self->execute($command, 1);
	if (defined($exit_status) && $exit_status == 0) {
		notify($ERRORS{'DEBUG'}, 0, "added line to currentimage.txt on $computer_node_name: '$post_load_line'");
	}
	elsif ($exit_status) {
		notify($ERRORS{'WARNING'}, 0, "failed to add line to currentimage.txt on $computer_node_name: '$post_load_line', exit status: $exit_status, output:\n" . join("\n", @$output));
		return;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to add line to currentimage.txt on $computer_node_name");
		return;
	}
	
	
	# Remove blank lines
	$command .= " && sed -i '' -e \'/^[\\s\\r\\n]*\$/d\' currentimage.txt";
	($exit_status, $output) = $self->execute($command, 1);
	if (defined($exit_status) && $exit_status == 0) {
		notify($ERRORS{'DEBUG'}, 0, "added line to currentimage.txt on $computer_node_name: '$post_load_line'");
	}
	elsif ($exit_status) {
		notify($ERRORS{'WARNING'}, 0, "failed to add line to currentimage.txt on $computer_node_name: '$post_load_line', exit status: $exit_status, output:\n" . join("\n", @$output));
		return;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to add line to currentimage.txt on $computer_node_name");
		return;
	}
	
	return 1;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 get_public_ip_address

 Parameters  : none
 Returns     : string
 Description : Returns the public IP address assigned to the computer.

=cut

sub get_public_ip_address {
	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 $public_network_configuration = $self->get_network_configuration('public');
	if (!$public_network_configuration) {
		notify($ERRORS{'WARNING'}, 0, "failed to retrieve public network configuration");
		return;
	}
	
	my $public_ip_address = (keys %{$public_network_configuration->{ip_address}})[0];
	if (!$public_ip_address) {
		notify($ERRORS{'WARNING'}, 0, "'ip_address' key is not set in the public network configuration hash:\n" . format_data($public_network_configuration));
		return;
	}
	
	notify($ERRORS{'DEBUG'}, 0, "retrieved public IP address: $public_ip_address");
	return $public_ip_address;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 add_user

 Parameters  :
 Returns     :
 Description :
               
# reserve

=cut

sub add_user {
	my $self = shift;
	if (ref($self) !~ /osx/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}

	my $reservation_password	= $self->data->get_reservation_password();
	
	# Make sure the user login ID was passed
	my $user_login_id = shift;
	$user_login_id = $self->data->get_user_login_id() if (!$user_login_id);
	if (!$user_login_id) {
		notify($ERRORS{'WARNING'}, 0, "user could not be determined");
		return 0;
	}
	
	# Make sure the computer node was passed
	my $computer_node_name = shift;
	$computer_node_name = $self->data->get_computer_node_name() if (!$computer_node_name);
	if (!$computer_node_name) {
		notify($ERRORS{'WARNING'}, 0, "computer node name could not be determined");
		return 0;
	}
	
	my $useradd_cmd = $self->get_node_configuration_directory() . "/useradd $user_login_id $reservation_password";
	if ($self->execute($useradd_cmd,1)) {
		notify($ERRORS{'DEBUG'}, 0, "added user: $user_login_id to $computer_node_name");
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "failed to add user: $user_login_id to $computer_node_name");
	}
	
	return 1;

} ## end sub add_user

#//////////////////////////////////////////////////////////////////////////////

=head2 firewall_enable

 Parameters  : optional persistence flag
 Returns     : 1 if succeeded, 0 otherwise
 Description :
               
               # pre_capture

=cut

sub firewall_enable {
	my $self = shift;
	if (ref($self) !~ /osx/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 $persist = shift;
	my $fw_enable_cmd = "";
	
	if ($persist) {
		$fw_enable_cmd = $self->get_node_configuration_directory() . "/fw_enable $persist";
	}
	else {
		$fw_enable_cmd = $self->get_node_configuration_directory() . "/fw_enable";
	}
	
	if ($self->execute($fw_enable_cmd,1)) {
		notify($ERRORS{'DEBUG'}, 0, "enabled firewall on $computer_node_name");
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "failed to enable firewall on $computer_node_name");
	}
	
	return 1;

} ## end sub firewall_enable

#//////////////////////////////////////////////////////////////////////////////

=head2 activate_irapp

 Parameters  : None
 Returns     : If successful: true
               If failed: false
 Description : Activates iRAPP license

=cut

sub activate_irapp {
	my $self = shift;
	if (ref($self) !~ /osx/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 = '/System/Library/CoreServices/rapserver.app/Contents/Tools/rapliccmd load -q -r -f /var/root/VCL/license.lic';
	
	my ($exit_status, $output) = $self->execute($command,1);
	
	if (defined $exit_status && $exit_status == 0) {
		notify($ERRORS{'DEBUG'}, 0, "executed command to load iRAPP license on $computer_node_name");
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to run command to load iRAPP license on $computer_node_name, output:\n" . join("\n", @$output));
	}
	
	return;
}

#//////////////////////////////////////////////////////////////////////////////

1;
__END__
