#!/usr/bin/perl
#
# Copyright 2004-2025 Citrix Systems, Inc. All rights reserved.
# This software and documentation contain valuable trade
# secrets and proprietary property belonging to Citrix Systems, Inc.
# None of this software and documentation may be copied,
# duplicated or disclosed without the express
# written permission of Citrix Systems, Inc.
#
### Begin installns script
# two variables are created when this file is processed by the Netscaler build.
#  "vers" is the external version number of the software releaase, e.g. 9.0
#  "variant" is either "v" for the multi-core netscaler software or "k" for the legacy kernel mode packet engine
# The template for this script is found in usr.src/netscaler/installns/template.pl

# IMPORTANT NOTE: Any installation/setup done here in installns needs to be replicated to
# `netscaler/scripts/vpx_build/script/installer_nsvpx_raw_disk.sh` for NS VPX image(XVA, ESX, etc).

$ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'.$ENV{PATH};

use Getopt::Std;
use Getopt::Long;
use File::Path;
use File::Copy;
use File::Basename;
use Cwd 'abs_path';
use Sys::Syslog;
use JSON::PP;
use POSIX qw( WEXITSTATUS WIFEXITED );
use File::Find;
use File::Basename;
use File::Path qw(mkpath);
use Cwd 'abs_path';
require XML::Simple;

## Exit Codes:
# NOTE: Do not reuse deleted exit code values to avoid conflicts with clients (e.g., nsnetsvc)
# that may still reference deprecated codes
$LIC_EXTENSION_NOT_FOUND_EXIT_CODE     = 42;
$LIC_EXPIRED_EXIT_CODE                 = 43;

### Variable Initialization
$vers                = "$ENV{NSVERSION}";
$variant             = "$ENV{NSVARIANT}";
$clversion           = "$ENV{CLVERSION}";
$debug_print         = 0;
$kernel              = "ns-$vers.gz";
$rsakey              = "rsakey-$vers\_pub.pem";
$kernelsignature     = "$kernel.sha512.signed";
$kernelname          = "ns-$vers";
$loaderconf          = "/boot/loader.conf";
$newpath             = "/nsconfig";
$newpathlicense      = "$newpath/license";
$testlicense         = "$newpath/testlicense";
$svm_check_path		 = "/mps";
$varstr              = "/var";
$metrics_conf        = "$varstr/metrics_conf";
$analytics_conf        = "$varstr/analytics_conf";
$nssynclogpath       = "/var/nssynclog";
$varpath             = "$varstr/netscaler";
$nskrb_path          = "$varpath/nskrb_old_binary";
$varbakpath          = "$varpath/nsbackup";
$templatespath       = "$varstr/nstemplates";
$applicationtemplatespath = "$templatespath/applications";
$apptemplatesdeploymentfilespath = "$applicationtemplatespath/deployment_files";
$entitytemplatespath = "$templatespath/entities";
$lbvservertemplatespath = "$templatespath/entities/lb vserver";
$lbvserverdeploymentfilepath = "$templatespath/entities/lb vserver/deployment_files";
$reportspath         = "$templatespath/reports";
$nitropath           = "$varpath/nitro";
$logonThemes         = "$varpath/logon/themes";
$logonPath           = "$varpath/logon";
$mastoolspath           = "$varstr/mastools";
$nsconnectorpath        = "$varstr/nsconnector";
$loginschema		 = "$newpath/loginschema";
$min_var_freespace = 4100000;    # leave just over 4GB for core files (1k blocks). From nslog.sh.
$ch_conf    = "/flash/nsconfig/.callhome.conf";
$admparam_conf = "/flash/nsconfig/admparam.conf";
$rpcs_conf    = "/flash/nsconfig/.rpcs";
$sshdir     = "ssh";
$ssldir     = "ssl";
$sslfipsdir = "fips";
$docdir     = "doc";
$helpdir    = "help";
$help_ciscodir="help_cisco";
$guidir     = "gui";
$state_file = "$varstr/nsinstall/installns_state";
$locdbdir   = "locdb";
$inCompassDbRoot = "gcf1";
$inCompassDb = "$inCompassDbRoot/data";
$defaultapispecsdir = "$newpath/apispec/netscaler/default";
$spapath            = "$varstr/spa/scripts";
$wasmpath           = "$varpath/wasm";
my %options = ();
my $flag_Y = 0;
my $flag_nsapi = 0;
my $old_ns_cert = 0;
my $nsconf_advanced = 0;

@dirs       = (
    "$newpath",                      "$newpath/$sshdir",
    "$newpath/$ssldir",              "$varpath",
    "$varpath/$ssldir",              "$varpath/$docdir",
    "$varpath/$helpdir",             "$templatespath", "$varpath/$help_ciscodir",
    "$entitytemplatespath", "$lbvservertemplatespath", "$lbvserverdeploymentfilepath",
    "$applicationtemplatespath", "$apptemplatesdeploymentfilespath",
    "$reportspath",         "$varpath/$guidir",
    "$newpath/$sslfipsdir",
    "$nitropath", 	"$mastoolspath",	    "$newpathlicense/$ssldir",
    "$varpath/$locdbdir", "$varstr/$inCompassDbRoot", "$varstr/$inCompassDb",
    "$loginschema", "$testlicense"
);

@files   = ( "ns.conf", "ns.lic", "resolv.conf", "rc.netscaler" );
$fips    = 0;
$fips_platform = 0;
$|       = 1;
$version = `uname -a`;
$ignore_platform_checks = "no";
$nsconf_check = 'yes';
$user_nsconf_check = 'yes';
$nocurses = 0;
$callhome_flag = 0;
$admserviceconnect_flag = 0;
$gui_flag = 0;
$enh_flag = 0;
$resize_flag = 0;
$invalid_config_check = 1;
$run_nspepi_tool = 0;
$is_vpx = `sysctl -n netscaler.hypervised_ns`;
$invalid_config_file = "/nsconfig/issues_ns.conf";
$error_config_file = "/nsconfig/error_ns.conf";

#options for syslogging the installation activity
$syslog_ident    = "installns: ";
$syslog_opt      = "ndelay,pid,nofatal,perror";
$syslog_facility = "LOG_USER";
$syslog_level    = "LOG_NOTICE";

# make STDOUT flush always.
STDOUT->autoflush(1);

### Subroutine Definitions

#write a string to the system log
sub log_to_syslog {
    my $log_msg = $_[0];
    syslog $syslog_level, "%s", $log_msg;
}

sub log_status {
    my $log_msg = $_[0];

    print "$log_msg\n";
    log_to_syslog($log_msg);
}

sub log_status_no_console {
    my $log_msg = $_[0];

    log_to_syslog($log_msg);
}

#write a string to the state log and the syslog. This file contains  information about the current
#install process that may later be used to restart or undo this installation
sub log_state {
    my $log_msg = $_[0];

    print STATE_LOG "$log_msg\n";
    log_to_syslog($log_msg);
}

#print trace messages about subroutine execution
#goes to syslog and to standard output. mostly for developer use.
sub log_trace {
    my $log_msg = $_[0];

    if ($debug_print) {
        log_status($log_msg);
    }
}

# This function checks that the hardware (sysid) is compatible with the this release of the Netscaler Software
#  Note that this install script is specific to a given release, i.e. this script installs release '$vers' and no other version.
sub check_sysid {
    my $this_subroutine_name = ( caller(0) )[3];
    my $ret = `sysctl netscaler.sysid`;

    log_trace " \nBEGIN $this_subroutine_name";

    if ( $ignore_platform_checks eq "yes" ) {
            log_status "*** WARNING *** System platform id checks are disabled. You may end up with an invalid installation. ****";
            log_status "Your system id is reported as $ret";
    }
	else {
	    if ( $variant eq "k" ) {
	        if ($ret =~ /940010/ || $ret =~ /940020/ ) {
	            ns_die(
	"Error: This version of Netscaler software is incompatible with the hardware platform $ret, please install the 8.1 release instead"
	            );
	        }
		 elsif ($ret =~ /980000/ || $ret =~ /980020/ || $ret =~ /980010/ || $ret =~ /981520/ || $ret =~ /995220/ || $ret =~ /995020/ ) {
		    ns_die(
		"\nError: This version of Netscaler software is incompatible with the hardware platform $ret please install the 9.3 release instead"
		    );
		}
		elsif ( $ret =~ /6753(0|1|2|6|7)0/ ||
			$ret =~ /22001(0|1|2)0/ ||
			$ret =~ /250(000|010|011|040|041|042|043|044|047|048|101|119|129|140|141|142|145|148|149|150)/ ||
			$ret =~ /30(010|020|030|040|110|130)/ ||
			$ret =~ /520((10|11|20|30|31|40|41|42|43|45|46)0|141)/ ||
			$ret =~ /35(00|01|05|10|11|02|03|06)0$/ ||
			$ret =~ /350(80|82)$/ ||
			$ret =~ /560(00|01|04|05|06|11)0$/ ) {
	            ns_die(
	"Error: This version of Netscaler software is incompatible with the hardware platform $ret, please install an nCore release \"nc\" variant instead"
	            );
	        }
	    }
	    elsif ( $variant eq "v" ) {
		# Hydra is dead
		if ( $ret =~ /13009(1|2|4)0$/ ) {
			ns_die(
			 "Error: This version of Netscaler software is incompatible with the hardware platform $ret, please install the 10.5 release instead"
			);
		}
			# Check for Rome, Thebes,
			# Decapolis, Pentapolis,
			# ROAD and Gladius-M
			# Matua and Wente platforms
			# Tinos P0 and P1
	        elsif ( $ret !~ /45014(0|1|2|3|4|5)$/  &&
	             $ret !~ /45006(0|2|3|4|5)$/  &&
	             $ret !~ /45008(0|1|2|3|4|5|6|7|8|9)$/  &&
	             $ret !~ /45009(2|3|6|7|8|9)$/  &&
	             $ret !~ /45010(0|1|2)$/  &&
	             $ret !~ /6753(0|1|2|6|7)0$/  &&
		     $ret !~ /22001(0|1|2)0$/  &&
	             $ret !~ /4501(1|6|7|8|9)0$/ &&
	             $ret !~ /4500(7|0|1|2)1$/ &&
	             $ret !~ /4500(7|5|4|3|2|1|0)0$/ &&
                     $ret !~ /450094$/ &&
		     $ret !~ /250(000|010|011|040|041|042|043|044|047|048|101|119|129|140|141|142|145|148|149|150)$/ &&
	             $ret !~ /30(010|020|030|040|110|130)$/  &&
		     $ret !~ /520((10|11|20|30|31|40|41|42|43|45|46)0|141)$/ &&
		     $ret !~ /35(00|01|05|10|11|02|03|06)0$/ &&
		     $ret !~ /350(80|82)$/ &&
		     $ret !~ /560(00|01|04|05|06|11)0$/ ) {
	            ns_die(
	"Error: This version of Netscaler software is incompatible with the hardware platform  $ret, please contact NetScaler support to make sure you have the correct software release."
	            );
	        }
	    }
	    else {
	        ns_die(
	"Error: This script does not understand the variant ($variant) that you are trying to install. Please check to see if you have the correct software release."
	        );
	    }
	}
    log_trace "END $this_subroutine_name";
} ## end sub check_sysid

sub fipsplatformcheck_loaderconf {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    open( LDCONF, "$loaderconf" );
    @ldconf = <LDCONF>;
    close(LDCONF);
    foreach $ldline (@ldconf) {
        if ( $ldline =~ /netscaler.fips_platform/i ) {
            $fips_platform = 1;
            print "Detected netscaler.fips_platform setting in existing loader.conf ... Will attempt FIPS install\n";
        }
    }
    log_trace "END $this_subroutine_name";
} ## end sub fipsplatformcheck_loaderconf

sub fipsplatformcheck_sysids {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";
    my $sysid = `sysctl -n netscaler.sysid`;

    if ($sysid == 30040 ||
        $sysid == 35020 ||
        $sysid == 520430) {
        $fips_platform = 1;
       print "Detected as known FIPS platform via sysid ... Will attempt FIPS install\n";
    }

    log_trace "END $this_subroutine_name";
} ## end sub fipsplatformcheck_sysids

# This function checks the version string for the presence of 'fips'
sub fipsplatformcheck_fips_build {
    # Check the kernel name we are trying to boot
    if ($kernel !~ /fips/) {
        ns_die("ERROR: FIPS build not detected. Aborting installation");
    }
} ## end sub check_fips_version

# This function checks for the Hardware requirement for FIPS Platform install
sub check_fipsplatform {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";

    if ( !$fips_platform ) {
        fipsplatformcheck_loaderconf();
        fipsplatformcheck_sysids();
    }
    
    # The above check will set fips_platform to 1 if VPX-FIPS or MPX-FIPS is detected. Make sure we are loading fips build.
    # Abort the installation if fips keyword is not present in version string.
    if ( $fips_platform ) {
        fipsplatformcheck_fips_build();
    }

    log_trace "END $this_subroutine_name";
} ## end sub check_fipsplatform

# This function checks for the Hardware requirement for VPX
sub check_vpx {
	my $this_subroutine_name = (caller(0) )[3];
	my $sysid = `sysctl -n netscaler.sysid`;

	log_trace " \nBEGIN $this_subroutine_name";

	if(stat($svm_check_path) != NULL) {
		ns_die("ERROR: This image belongs to NetScaler. Aborting Installation...");
	}

	if ($sysid == 450000 || $sysid == 450010 || $sysid == 450020 ||
	    $sysid == 450030 || $sysid == 450140 || $sysid == 450141 ||
	    $sysid == 450142 || $sysid == 450143 || $sysid == 450144 ||
	    $sysid == 450145 ||	$sysid == 450050 || $sysid == 450060 ||
	    $sysid == 450062 || $sysid == 450063 || $sysid == 450064 ||
	    $sysid == 450065 ||	$sysid == 450040 || $sysid == 450110 ||
	    $sysid == 450080 || $sysid == 450081 || $sysid == 450087 ||
	    $sysid == 450082 || $sysid == 450083 || $sysid == 450084 ||
	    $sysid == 450085 || $sysid == 450086 || $sysid == 450087 ||
	    $sysid == 450088 || $sysid == 450089 || $sysid == 450070 ||
	    $sysid == 450160 ||	$sysid == 450170 || $sysid == 450180 ||
	    $sysid == 450190 || $sysid == 450071 || $sysid == 450001 ||
	    $sysid == 450011 || $sysid == 450021 || $sysid == 450093 ||
	    $sysid == 450092 || $sysid == 450096 || $sysid == 450094 ||
	    $sysid == 450097 || $sysid == 450098 || $sysid == 450099 ||
		$sysid == 450100 || $sysid == 450101 || $sysid == 450102 ) {
		## 45xxxx sysid == VPX 9.x release & above
		my $ncpu = `sysctl -n hw.ncpu`;
		my $mem = `sysctl -n hw.realmem`;
		if ( $variant eq "k" ) {
			if ( $mem < 1000000000 ) {
				print "\n  WARNING: NetScaler on Classic VPX requires minimum 1 Gigabytes to start \n";
			}
		}
		elsif ( $variant eq "v" ) {
			if ( $ncpu < 2 || $mem < 2000000000) {
				ns_die("ERROR: NetScaler on nCore VPX requires minimum 2 Gigabytes and 2 cpus to start.");
			}
		}
	}
	# For HyperV and SDX: Classic Build is not supported
	if ($sysid == 450020 || $sysid == 450030 || $sysid == 450140 ||
	    $sysid == 450141 ||	$sysid == 450142 || $sysid == 450143 ||
	    $sysid == 450144 || $sysid == 450145 || $sysid == 450050 ||
	    $sysid == 450060 || $sysid == 450062 || $sysid == 450063 ||
	    $sysid == 450064 || $sysid == 450065 || $sysid == 450040 ||
	    $sysid == 450110 ||	$sysid == 450080 || $sysid == 450081 ||
	    $sysid == 450082 ||	$sysid == 450083 || $sysid == 450084 ||
	    $sysid == 450085 ||	$sysid == 450086 || $sysid == 450087 ||
	    $sysid == 450088 || $sysid == 450089 ||
	    $sysid == 450070 || $sysid == 450160 || $sysid == 450170 ||
	    $sysid == 450180 || $sysid == 450190 || $sysid == 450071 ||
	    $sysid == 450001 || $sysid == 450011 || $sysid == 450021 ||
	    $sysid == 450093 || $sysid == 450092 || $sysid == 450096 ||
	    $sysid == 450097 || $sysid == 450098 || $sysid == 450099 ||
		$sysid == 450100|| $sysid == 450101 || $sysid == 450102) {
            if ( $variant eq "k" ) {
                        ns_die("ERROR: NetScaler on classic VPX is not supported.\n");
                }
        }
	log_trace "END $this_subroutine_name";
} ## end sub check_vpx

# This function ensures upgrade does not cause instance to be bootstrapped.
sub  cloud_prevent_bootstrap {
        my $this_subroutine_name = (caller(0) )[3];
        my $sysid = `sysctl -n netscaler.sysid`;

        log_trace " \nBEGIN $this_subroutine_name";

        if ($sysid == 450040 ) {
		`touch /nsconfig/aws_bootstrap`;
        }
	log_trace "END $this_subroutine_name";
}

# This function invokes an executable which checks for an upgrade or downgrade
# situation and gives the user a chance to swap config files
sub check_nsconf {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";

    if ( !$nsconf_check ) {
        if ( !$user_nsconf_check ) {
	    log_status "ns.conf check skipped at user's request";
	} else {
	    log_status "ns.conf check skipped";
        }
        return;
    }

    chmod(0755, './nsconfig');
    system('./nsconfig check wait' . ($nocurses ? ',nocurses' : '') . ($nsconf_advanced ? ' advance' : ''));
    if (WIFEXITED($?) and (WEXITSTATUS($?) eq 2)) {
      log_status "\nInstallation aborted; Corresponding ns.conf either not found or copy unsuccesful; if you wish to view available ns.conf files use the -a option.\n";
      exit(21);
    }
    log_trace "END $this_subroutine_name";
} ## end sub check_nsconf

sub nspepi_tool_message {
    print "\nPlease fix the error mentioned in ".$error_config_file." and try to upgrade\n";
    print "\nNOTE: the nspepi tool doesn't convert the following configurations:\n\t1. SureConnect commands\n\t2. PriorityQueuing commands\n\t3. HTTP Denial of Service Protection commands\n\t4. HTMLInjection commands.\n";
}

sub nspepi_upgrade_tool_message {
    print "\nThe nspepi upgrade tool can be useful in converting your configuration - see the documentation at https://docs.citrix.com/en-us/citrix-adc/current-release/appexpert/policies-and-expressions/introduction-to-policies-and-exp/converting-policy-expressions-nspepi-tool.html. Use nspepi tool available at https://github.com/citrix/ADC-scripts/tree/master/nspepi for the most complete and up-to-date version.";
}

sub check_invalid_config_helper {
    nspepi_upgrade_tool_message();
    print "\nNOTE: the nspepi tool doesn't convert the following configurations:\n\t1. SureConnect commands\n\t2. PriorityQueuing commands\n\t3. HTTP Denial of Service Protection commands\n\t4. HTMLInjection commands.\n";
    log_status "\nInstallation cancelled; if you wish to run the NSEPEPI tool during installation, then use the -M option; or if you wish to force the installation use the -Y option but invalid configuration will be lost.\n";
    exit(16);
} # end check_invalid_config_helper

sub handle_module_error_helper {
    print "\nAs some required modules are not present, we can't check for an invalid configuration in the ns.conf file";
    $ans = 'n';
    if ($opt_string eq "") {
        print "\nDo you wish to continue installation? [Y/N] ";
        $ans = read_yesno_input_from_STDIN();
    }
    nspepi_upgrade_tool_message();
    if ($ans =~ /y/i) {
        return;
    } else {
        log_status "\nInstallation cancelled; if you wish to force the installation use the -Y option.\n";
        exit(16);
    }
} # end handle_module_error_helper

# This function is used to check that whether
# ns.conf file contains invalid features or expressions
sub check_invalid_config {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace "\nBEGIN $this_subroutine_name";

    print "\n*************************************************************** WARNING ****************************************************\n\n";
    print "In the 13.1 release many deprecated features, parameters, functionalities, and use of Classic expressions have been removed; please convert the configuration which will throw error in 13.1 or later releases to non-deprecated prior to upgrade; the nspepi tool can be used to help with this.";
    print "\n*****************************************************************************************************************************\n\n";
    if ( !$invalid_config_check ) {
        log_status "\nInvalid config check is disabled";
        return;
    }

    # $vers value would be like 13.1-3.23
    my $new_ns_version = (split(/-/, $vers))[0];
    @values = (split(/\./, $new_ns_version));
    my $new_major_version = int($values[0]);
    my $new_minor_version = int($values[1]);

    # Output would be like:
    # NetScaler NS13.1: Build 3.23.nc, Date: May 17 2021, 08:51:00 (64-bit)
    my $version_string = `sysctl -n netscaler.version`;
    my $ns_version = (split(/NS/, $version_string))[1];
    my $current_ns_version = (split(/:/, $ns_version))[0];
    @values = (split(/\./, $current_ns_version));
    my $current_major_version = int($values[0]);
    my $current_minor_version = int($values[1]);

    if (($current_major_version > $new_major_version) or
        (($current_major_version == $new_major_version) and
         ($current_minor_version >= $new_minor_version))) {
        log_status "\nSkipping invalid config check as we are either downgrading or installing the same release versions.";
        return;
    }
    log_status "\nChecking invalid config in ns.conf file";
    if ( -f "./invalid_config_check_files.tgz")
    {
        log_status "\nInstalling files to check for invalid config...";
        `tar xpfz invalid_config_check_files.tgz 2>> ./err_msg`;
    } else {
        log_status "\ninvalid_config_check_files.tgz file is not present.";
        log_status "\nInstallation cancelled; if you wish to force the installation use the -Y option.\n";
        exit(17);
    }
    # Output would be /var/python/bin/python if python is
    # present, otherwise output would be empty string.
    my $python_version_string = `which python 2> /dev/null`;
    if ($python_version_string eq "") {
        # Python is not installed
        handle_module_error_helper();
        return;
    }
    my $python_module_list = `python -m pip freeze 2> /dev/null`;
    if (!($python_module_list =~ m/\bply==/)) {
        handle_module_error_helper();
        return;
    }
    my $exit_status = system('python ./nspepi2/invalid_config_check_files/config_check_main.py -B '.$new_ns_version.' -f /nsconfig/ns.conf');
    if ($exit_status != 0) {
        log_status "\nFailed in checking ns.conf file: $exit_status";
        check_invalid_config_helper();
    }
    # Checks whether any command is present in the file
    if (!(-z $invalid_config_file)) {
        if ($run_nspepi_tool) {
            my $exit_status = system('python ./nspepi2/invalid_config_check_files/nspepi_main.py -E -f /nsconfig/ns.conf');
            if ($exit_status != 0) {
                log_status "\nError in running nspepi tool: $exit_status";
                log_status "\nInstallation cancelled; if you wish to force the installation use the -Y option.\n";
                exit(18);
            }
            if ( -e $error_config_file ) {
                print "\nThe following errors occurred in running the nspepi tool. Please fix the errors manually or reach out to the support team\n";
                system("cat $error_config_file");
                nspepi_tool_message();
                exit(19);
            } else {
                print "\nNo error occurred in running the nspepi tool.\n";
                system("cp /nsconfig/ns.conf /nsconfig/ns.conf_old");
                log_status "\nCurrent configuration file ns.conf content has been copied to /nsconfig/ns.conf_old\n";
                system("cp /nsconfig/new_ns.conf /nsconfig/ns.conf");
                log_status "\nns.conf file has been updated with the converted config file\n";
            }
        } else {
            my $number_of_invalid_cmds = `wc -l < $invalid_config_file`;
            if ($number_of_invalid_cmds == 1) {
                open my $invalid_file, '<', $invalid_config_file;
                while (my $line = <$invalid_file>) {
                    # Remove this if check if multiple default commands
                    # need to handle
                    if ($line =~ /^set cmp parameter/) {
                        # Ignore default configs
                        my $exit_status = system('python ./nspepi2/invalid_config_check_files/nspepi_main.py --handleDefaultConfig -f /nsconfig/ns.conf');
                        if ($exit_status != 0) {
                            log_status "\nError in running nspepi tool: $exit_status";
                            log_status "\nInstallation cancelled; if you wish to force the installation use the -Y option.\n";
                            exit(18);
                        }
                        print "\nNo error occurred in running the nspepi tool.\n";
                        system("cp /nsconfig/ns.conf /nsconfig/ns.conf_old");
                        log_status "\nCurrent configuration file ns.conf content has been copied to /nsconfig/ns.conf_old\n";
                        system("cp /nsconfig/new_ns.conf /nsconfig/ns.conf");
                        log_status "\nns.conf file has been updated with the converted config file\n";
                    } else {
                        print "\nThe following configuration lines will get errors in ".$new_ns_version." and both they and dependent configuration will be removed from the configuration:\n";
                        system("cat $invalid_config_file");
                        check_invalid_config_helper();
                    }
                }
            } elsif ($number_of_invalid_cmds == 5) {
                my $only_default_cmp_bindings = 1;
                open my $invalid_file, '<', $invalid_config_file;
                while (my $line = <$invalid_file>) {
                    if ($line !~ /^bind cmp global/) {
                        $only_default_cmp_bindings = 0;
                        last;
                    }
                    my @default_cmp_policies = ("ns_cmp_content_type", "ns_cmp_msapp", "ns_cmp_mscss", "ns_nocmp_mozilla_47", "ns_nocmp_xml_ie");
                    if ($line =~ /^bind cmp global (.*) -priority/) {
                        my $policy_name = $1;
                        if (grep(/^$policy_name$/, @default_cmp_policies) == 0) {
                            $only_default_cmp_bindings = 0;
                            last;
                        }
                    }
                }
                if ($only_default_cmp_bindings == 1) {
                    # Ignore default configs
                    my $exit_status = system('python ./nspepi2/invalid_config_check_files/nspepi_main.py --handleDefaultConfig1 -f /nsconfig/ns.conf');
                    if ($exit_status != 0) {
                        log_status "\nError in running nspepi tool: $exit_status";
                        log_status "\nInstallation cancelled; if you wish to force the installation use the -Y option.\n";
                        exit(18);
                    }
                    print "\nNo error occurred in running the nspepi tool.\n";
                    system("cp /nsconfig/ns.conf /nsconfig/ns.conf_old");
                    log_status "\nCurrent configuration file ns.conf content has been copied to /nsconfig/ns.conf_old\n";
                    system("cp /nsconfig/new_ns.conf /nsconfig/ns.conf");
                    log_status "\nns.conf file has been updated with the converted config file\n";
                } else {
                    print "\nThe following configuration lines will get errors in ".$new_ns_version." and both they and dependent configuration will be removed from the configuration:\n";
                    system("cat $invalid_config_file");
                    check_invalid_config_helper();
                }
            } elsif ($number_of_invalid_cmds == 6) {
                my $only_default_cmp_bindings = 1;
                open my $invalid_file, '<', $invalid_config_file;
                while (my $line = <$invalid_file>) {
                    if ($line !~ /^bind cmp global/) {
                        if ($line !~ /^set cmp parameter/) {
                            $only_default_cmp_bindings = 0;
                            last;
                        }
                    } else {
                        my @default_cmp_policies = ("ns_cmp_content_type", "ns_cmp_msapp", "ns_cmp_mscss", "ns_nocmp_mozilla_47", "ns_nocmp_xml_ie");
                        if ($line =~ /^bind cmp global (.*) -priority/) {
                            my $policy_name = $1;
                            if (grep(/^$policy_name$/, @default_cmp_policies) == 0) {
                                $only_default_cmp_bindings = 0;
                                last;
                            }
                        }
                    }
                }
                if ($only_default_cmp_bindings == 1) {
                    # Ignore default configs
                    my $exit_status = system('python ./nspepi2/invalid_config_check_files/nspepi_main.py --handleDefaultConfig2 -f /nsconfig/ns.conf');
                    if ($exit_status != 0) {
                        log_status "\nError in running nspepi tool: $exit_status";
                        log_status "\nInstallation cancelled; if you wish to force the installation use the -Y option.\n";
                        exit(18);
                    }
                    print "\nNo error occurred in running the nspepi tool.\n";
                    system("cp /nsconfig/ns.conf /nsconfig/ns.conf_old");
                    log_status "\nCurrent configuration file ns.conf content has been copied to /nsconfig/ns.conf_old\n";
                    system("cp /nsconfig/new_ns.conf /nsconfig/ns.conf");
                    log_status "\nns.conf file has been updated with the converted config file\n";
                } else {
                    print "\nThe following configuration lines will get errors in ".$new_ns_version." and both they and dependent configuration will be removed from the configuration:\n";
                    system("cat $invalid_config_file");
                    check_invalid_config_helper();
                }
            } else {
                print "\nThe following configuration lines will get errors in ".$new_ns_version." and both they and dependent configuration will be removed from the configuration:\n";
                system("cat $invalid_config_file");
                check_invalid_config_helper();
            }
        }
    } else {
        print "\nNo invalid config detected with the configuration.\n";
    }

    log_trace "END $this_subroutine_name";
} ## end sub check_invalid_config

sub check_callhome {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";
	if (!(-e $ch_conf)) {
    	print
"\n  CallHome feature will be enabled after the reboot.
  This lets the NetScaler device/instance automatically upload diagnostic
  and usage information to Citrix. This data will help detect critical errors
  and will also be used to improve the features and the product

  You can also configure this feature anytime using the command line
  interface or the configuration utility. Please see the documentation
  for more details.\n\n";
	}
    log_trace "END $this_subroutine_name";
} ## end sub check_callhome

sub check_bot {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";
	if (!(-e $ch_conf)) {
        print
"\n Following reboot, Bot Protection feature will be enabled in non-blocking mode.
  This enables the NetScaler device/instance inspect for any bot traffic and log the same.
  Additionally, you can apply mitigations like drop, redirect or reset using the command line interface or the configuration utility.
  Please refer the documentation for more details.\n\n";
	}
    log_trace "END $this_subroutine_name";
} ## end sub check_bot

## Fixing Q2-2020 issue of enabling Citrix ADM Service Connect feature without proper checks
sub admserviceconnect_config_fix() {
  my ($version, $build) = "";
  my $version_string = `sysctl -n netscaler.version`;

  my $ns_version = (split(/NS/, $version_string))[1];
  @values = split(/:/, $ns_version);
  $version = $values[0];

  $ns_version = (split(/Build /, $version_string))[1];
  my $token = (split(/[,]/, $ns_version))[0];
  @values = split(/[.]/, $token);
  $build = $values[0];

  if (($version == 13.0 && $build < 64) || ($version == 12.1 && $build < 58) || ($version <= 11.1)) {
    if (open(ADMPARAM_CONF, "< $admparam_conf")) {
      my %admparams = map { /(.*)=(.*)/ } <ADMPARAM_CONF>;
      close(ADMPARAM_CONF);

      if ($admparams{'autoconnect_status'} == 1) {
        # Delete the admparam.conf file if Citrix ADM Service Connect is enabled
        unlink($admparam_conf);
      }
    }
  }
}

## Inputs:
## -1  : Leave current value as it is.
## <=0 : Update to given value.
sub set_admserviceconnect_config {
  my ($autoconnect_status, $first_login, $initial_wait) = @_;

  # create admparam conf file if doesn't exist already
  if (!(-e $admparam_conf)) {
    `touch $admparam_conf`;
  }

  if (open(ADMPARAM_CONF, "+< $admparam_conf")) {
    my %admparams = map { /(.*)=(.*)/ } <ADMPARAM_CONF>;
    if ($autoconnect_status != -1) {
      $admparams{'autoconnect_status'} = $autoconnect_status;
    }
    if ($first_login != -1) {
      $admparams{'first_login'} = $first_login;
    }
    if ($initial_wait != -1) {
      $admparams{'initial_wait'} = $initial_wait;
    }

    seek ADMPARAM_CONF, 0, 0;
    truncate ADMPARAM_CONF, 0;
    print ADMPARAM_CONF "$_=$admparams{$_}\n" for keys %admparams;
    close(ADMPARAM_CONF);
  } else {
    log_status "Failed to save Citrix ADM Service Connect config\n";
  }
}

sub check_licenseserver_compatibility {
  $XML::Simple::PREFERRED_PARSER = 'XML::Parser';
  my $this_subroutine_name = ( caller(0) )[3];
  log_trace " \nBEGIN $this_subroutine_name";

  $pooledconfpath = '/nsconfig/pooledlicense.conf';

  my $server = "";
  my $port = "27000";
  my $xmlpath = "/var/tmp/";
  my $xmlfile = "ctxReturn.xml";
  my $connstatus = "FAIL";
  my $licmode = "0";

  if (-e $pooledconfpath) {
    log_status_no_console "Found pooled licensing conf \n";
    # Find IP and port
    open my $FH, '<', $pooledconfpath;
    while(my $line = <$FH>){
      # Remove CR/LF in the end
      chomp $line;
      # Remove leading whitespaces
      $line=~ s/^\s+//;
      if ($line =~ /^\s*$/ || $line =~ /^[#]/ ) {
              # Ignore empty line and comments
      }
      else {
        my $word = "server";
        $line =~ /$word\s*?(\S+)/;
        if ($1) {
          $server = $1;
        }
          $line =~ /port\s*?(\S+)/;
        if ($1) {
          $port = $1;
        }
          $line =~ /licensemode\s*?(\S+)/;
        if ($1) {
          $licmode = $1;
        }
      }
    }
    close($FH);
    if ($server eq '') {
      log_status_no_console "License server is empty in config file";
      return;
    }
    #licensemode = 10 is for LASConnected
    if ($licmode eq '10') {
      log_status_no_console "Configured with LASConnected licensing";
      return 1;
    }
    # remove any stale file
    unlink($xmlpath.$xmlfile);
    log_status_no_console "Detecting compatibility with License server ".$server." port ".$port."\n";
    chmod(0700, './licservverify');
    # Run licserververify tool which dumps the LS compatibility info to xml
    my $cmd = "./licservverify -h ".$server." -p ".$port." -v -x ".$xmlpath;
    system($cmd);
    my $simple = XML::Simple->new( );
    my $tree;
    if (-e $xmlpath.$xmlfile) {
      $tree = $simple->XMLin( $xmlpath.$xmlfile );
      unlink($xmlpath.$xmlfile);
      $connstatus = $tree->{Host}->{ReturnString};
    }
    if ($connstatus ne "SUCCESS") {
      print $connstatus."\n";
      print "Could not connect the License server to get FlexVersion, ADC may become unlicensed after upgrade";
      print "\nDo you wish to continue installation? [Y/N] ";
      # Could not connect, ask user whether to proceed.
      if ( $flag_Y == 1 ) {
          print("\n Auto selecting option: y\n");
          return 1;
	  }

      $ans = read_yesno_input_from_STDIN();
      if ( $ans eq 'y' || $ans eq 'Y' ) {
        return 1;
      }
      exit 1;
    }
    my $compatible = $tree->{ FlexVersion }->{ ReturnString };
    print $compatible."\n";
    my $serverver = $tree->{ FlexVersion }->{ ServerVersion };
    my $clientver = $tree->{ FlexVersion }->{ ClientVersion };

    if ($serverver eq "NULL") {
      print "Failed to get the License server FlexVersion, ADC may become unlicensed after upgrade\n";
      print "Do you wish to continue installation? [Y/N] ";
      if ( $flag_Y == 1 ) {
          print("\n Auto selecting option: y\n");
          return 1;
	  }
      $ans = read_yesno_input_from_STDIN();
      if ( $ans eq 'y' || $ans eq 'Y' ) {
        return 1;
      }
      exit 1;
    }

    if ($compatible ne "SUCCESS") {
      log_status_no_console $compatible."\n";
      print "License server version ".$serverver." not compatible with client version ".$clientver."\n";
      print "Either upgrade the ADM to ensure that it is running lmgrd with FlexVersion >= ".$clientver."\n";
      print "Or reconfigure ADC to point to License server with required version"."\n";
      print "Or remove pooled licensing config from ADC and upload a valid license file on the ADC to proceed"."\n";
      exit 1;
    }
    log_status_no_console "License server FlexVersion check OK\n";
  }

  log_trace "END $this_subroutine_name";
} ## end sub check_licenseserver_compatibility

sub check_admserviceconnect {
  my $this_subroutine_name = ( caller(0) )[3];

  log_trace " \nBEGIN $this_subroutine_name";
  my $is_ngsconnector = `sysctl -n netscaler.is_ngs_connector`;

  # If the ADC is NGS Connector, this feature will not be Enabled
  if ( $is_ngsconnector == 1 ) {
    set_admserviceconnect_config(0, 0, 0);
    log_status "Unsupported platform. Skipping Citrix ADM Service Connect checks.\n";
    log_trace "END $this_subroutine_name";
    return;
  }

  my $message = "\n     'Citrix ADM Service Connect' feature will be enabled on upgrade.
This feature helps you discover your Citrix ADC instances effortlessly on Citrix
ADM service and get insights and curated machine learning based recommendations
for applications and Citrix ADC infrastructure. This feature lets the Citrix ADC
instance automatically send system, usage and telemetry data to Citrix ADM service.
View here [https://docs.citrix.com/en-us/citrix-adc/13/data-governance.html] to
learn more about this feature.
You can also configure this feature anytime using the Citrix ADC command line
interface, API or GUI Settings.
Use of this feature is subject to the Citrix End User ServiceAgreement. View
here [https://www.citrix.com/buy/licensing/agreements.html].\n\n";

  # Option -A is given, user agreed for enabling Citrix ADM Service Connect feature
  if ( $admserviceconnect_flag == 1 ) {
    print $message;
    set_admserviceconnect_config(1, 1, 900);
    log_trace "END $this_subroutine_name";
    return;
  }

  ## Fixing Q2-2020 issue of enabling Citrix ADM Service Connect without proper checks
  admserviceconnect_config_fix();
  #Condition to enable Citrix ADM service if mastool is already running
  if (-e "$mastoolspath/conf/agent.conf") {
      set_admserviceconnect_config(1, 1, 900);
    } elsif (-e $admparam_conf) {
    if ( $gui_flag == 1 ) {
      # GUI/API: if option 'A' is not provided, then disable Citrix ADM Service Connect
      set_admserviceconnect_config(0, 1, 900);
    } else {
      # CLI: User has already configured this feature, maintain current status
      set_admserviceconnect_config(-1, 1, 900);
    }
  } else {
    my $feature = `/netscaler/nsremotexec 127.0.0.1 -exec "show feature" 2> /dev/null`;
    $feature =~ /(CallHome.*?)\n/s;
    $callhome_state = 0;
    if (index($1, 'ON') != -1) {
      $callhome_state = 1;
    }

    # First time upgrade to build with Citrix ADM Service Connect feature
    if ($callhome_state == 0 || $flag_Y == 1) {
      set_admserviceconnect_config(0, 1, 900);
    } else {
      print $message;
      if ( $gui_flag == 1 ) {
        # GUI: Auto enable the feature with initial wait time of 30 Minutes
        set_admserviceconnect_config(1, 1, 1800);
      } else {
        # CLI: Prompt user for confirmation to keep the feature enabled
        print "  Enter Y to continue or N to disable the feature [Y/N] ";
        open( TTY, "+</dev/tty" ) or die "no tty: $!";
        sysread( TTY, $ans, 1 );
        read_until_newline(TTY);
        close(TTY);
        if ( $ans =~ /y/i ) {
          # User wants to continue with Citrix ADM Service Connect feature
          set_admserviceconnect_config(1, 1, 900);
        } else {
          set_admserviceconnect_config(0, 1, 900);
        }
      }
    }
  }

  log_trace "END $this_subroutine_name";
} ## end sub check_admserviceconnect

# This routine will be used to check sym link with standard
# configuration files during upgrade or downgrade. If symlink exists,
# it can contain some customized settings which will not applicable
# during build upgrade or downgrade. So we will log message in syslog
# file so that user will know reason of failure of process starting.
sub check_sym_link() {
	my $this_subroutine_name = ( caller(0) )[3];
	log_trace " \n BEGIN $this_subroutine_name";
	# Provide list of files by comma seperation
	my @conf_files = ( "/etc/sshd_config", "/etc/httpd.conf" );
	foreach $elem (@conf_files) {
			if ( ( stat($elem) != NULL ) && ( -l $elem ) ) {
				log_status "WARNING:A symbolic link exists for $elem. Please review it for compatibility!!!"

			}
	}

}
# checking the checksum of the portal core files from the /var/netscaler/logon/LogonPoint/checksum_<ns_version_hash>.txt file.
# creating the backup of the modified file if found, at the location /var/netscaler/logon/backup/
# saving all backup files absolute path in the /var/netscaler/logon/backup/file_location.list file.
sub check_if_portal_core_file_is_modified() {
    # calculating the checksum of the ns version in the format NS13.1:10.24.nc
    my $nsver_sha = `/netscaler/nscli -U %%:.:. sh ver | awk 'NR==2{printf "\%s", \$2substr(\$4, 1, length(\$4)-1)}' | shasum | awk '{print \$1}'`;
    $nsver_sha =~ s/^\s+|\s+$//g;
    $checksum = "${logonPath}/LogonPoint/checksum_${nsver_sha}.txt";
    if (-e $checksum) {
        $out = `shasum -c $checksum 2>/dev/null | grep -v "/var/netscaler/logon/LogonPoint/scripts" |  grep FAILED`;
        print("Checking if portal core files are modified\n");
        if ($out) {
            print("Checksum failed for the following files.\n\n");
            print($out);
            print("\nNote: Current portal backup will be available at the location : $logonPath/backup/ and all the original file locations will be listed in $logonPath/backup/file_location.list. Any older backup will be deleted.\n");
            print("The above files will be overwritten upon upgrade and customizations in these files will be lost. Ensure to redo the customization in the respective file manually after the upgrade by referring to the backup created above. The Portal UI may not work as expected if files are directly copied back.\n");
            print("Do you want to create the backup and proceed with the installation, press y / n : ");
            select()->flush();
            if ($gui_flag || $flag_Y) {
                print("\n Auto selecting option: y\n");
                $ans  = "y";
            } else {
                open( TTY, "+</dev/tty" ) or die "no tty: $!";
                sysread( TTY, $ans, 1 );
                read_until_newline(TTY);
                close(TTY);
            }
            print("\nOption Selected : $ans\n");
            if($ans eq "y" || $ans eq "Y") {
                system("rm -rf /$logonPath/backup/");
                system("mkdir -p /$logonPath/backup/");
                foreach $i (split("\n", $out)) {
                    $file_path = (split(" ", $i))[0];
                    $file_path = substr($file_path , 0, length($file_path)-1);
                    $name = (split("/", $file_path))[length($file)-1];
                    system("echo '$file_path' >> /$logonPath/backup/file_location.list");
                    system("cp $file_path /$logonPath/backup/$name");
                }
                print("Backup Created Successfully\n")
            } else {
                ns_die("Stopping the installation ...\n");
            }
        } else {
            print ("No portal core files are modified. \n");
        }
    } else {
        print("Could not find the checksum file for the portal, skipping the check.\n");
    }
}

# function to check nsapimgr use in rc.netscaler file.
sub search_nsapimgr_and_warn {
    my @file_paths = ("/flash/nsconfig/rc.netscaler", "/flash/nsconfig/nsafter.sh");
    my $symbols_not_found = 0;

    # Initialize the hash
    my %nsppe_symbols;

    # Open the nsppe symbol text file for reading
    open my $nsppe_fh, '<', 'nsppe_symbols.txt' or warn "Could not open nsppe symbols file for reading: $!";

    while(my $line = <$nsppe_fh>) {
        chomp($line);
        next if $line =~ /^\s*$/;
        next if $line =~ /^\s*#/;
        $nsppe_symbols{$line} = 1;
    }

    close $nsppe_fh;

    foreach my $file_path (@file_paths) {
        if (-e $file_path) {
            print "\nEvaluating the '$file_path' file for nsapimgr usage.\n\n";
            eval {
                open my $file, '<', $file_path or warn "Cannot open file $file_path: $!";
                while (my $line = <$file>) {
                    chomp $line;  # Removes the trailing newline character.
                    next if $line =~ /^\s*#/; # skip commented lines
                    if ($line =~ /(^|\W)nsapimgr.+\scall=["']?(\w+)/) {
                        my $symbol = $2;
                        if ($debug_print) {
                            print "Looking for the nsapimgr call with the symbol '$symbol' from $file_path in the new nsppe.\n";
                        }
                        # Check unless the symbol exists in the file content
                        unless (exists $nsppe_symbols{$symbol}) {
                            print "The 'nsapimgr' call with the symbol '$symbol' is not found.\n";
                            $symbols_not_found++
                        }
                    }
                }
                close $file;
            };

            if ($@) {
                print "Error: $@\n";
            }
        }
    }

    if ($symbols_not_found > 0) {
        print "\n***************************************** WARNING *****************************************\n\n";
        print "Above functions are used as workarounds for invocation through nsapimgr. ";
        print "These functions are not included in the exception list, and as a result, these invocations will fail. ";
        print "For further information, please contact NetScaler support.\n\n";
        print "*******************************************************************************************\n";

        # Ask user to continue
        if ($flag_Y == 1 || $flag_nsapi == 1) {
            $ans = "y";
        } elsif ($gui_flag == 1) {
            # Abort installation if GUI doens't pass -p or -Y.
            $ans = 'n';
        } else {
            print "Do you want to continue installation? (y/n): ";
            $ans = read_yesno_input_from_STDIN();
        }

        if ($ans =~ /^[yY]$/) {
            print "Continuing installation...\n\n";
        } else {
            print "Installation aborted.\n\n";
            exit 0;
        }
    }
}

sub check_downgrade_prerequisite {
	my $this_subroutine_name = ( caller(0) )[3];
	log_trace " \nBEGIN $this_subroutine_name";

	my $dg_pr_dir = "/netscaler/downgrade_prerequisite";
	my $dg_pr_main = "$dg_pr_dir/main";

	if ( -f $dg_pr_main ) {
		log_status "Check version compatibility";
		chmod(0755, $dg_pr_main);
		$out = `NSVERSION=$vers NSVARIANT=$variant CLVERSION=$clversion "$dg_pr_main" 2>&1`;
		$rcode = $? >> 8;
		log_trace "script output (code=$rcode): $out";

		if ( $rcode == 0x00 ) {
			return
		}

		log_status_no_console "script output: $out";
		# refer downgrade_prerequisite/README.NETSCALER for expected return codes
		if (($rcode & 0xf0) == 0x10) {
			log_status "Done with warnings (code=$rcode)";
			if (($rcode & 0x0f) != 0x00) {
				# we need to confirm for downgrade
				print "\nWARNING: Downgrade prerequisite passed with warnings, please review warnings";
				if ($flag_Y == 1) {
					print "\nOption 'Y' set, Continuing with downgrade ....";
				} else {
					print "\nDo you want to continue with downgrade? (y/n): ";
					$ans = read_yesno_input_from_STDIN();
					if ($ans =~ /^[yY]$/) {
						print "Continuing with downgrade...\n";
					} else {
						print "Downgrade aborted, exiting installer.\n";
						exit 0;
					}
				}
			}
		} elsif (($rcode & 0x0f) != 0x00) {
			log_status "Failed (code=$rcode)";
			ns_die("version compatibility prerequisite check failed (code=$rcode).");
		} else {
			log_status "Unknown error code (code=$rcode)";
			log_status "WARNING: version compatibility prerequisite check gave unknown error code: $rcode";
			print "\nWARNING: Please review output before proceeding with installation";
			if ($flag_Y == 1) {
				print "\nOption 'Y' set, Continuing with installation ....";
			} else {
				print "\nDo you want to continue with installation? (y/n): ";
				$ans = read_yesno_input_from_STDIN();
				if ($ans =~ /^[yY]$/) {
					print "Continue with installation...\n";
				} else {
					print "Installation aborted, exiting installer.\n";
					exit 0;
				}
			}
		}
	}

	log_trace "END $this_subroutine_name";
} ## end sub check_downgrade_prerequisite

sub fix_downgrade_issues {
	my $this_subroutine_name = ( caller(0) )[3];
	log_trace " \nBEGIN $this_subroutine_name";

	my $dg_pr_dir = "/netscaler/downgrade_prerequisite";
	my $dg_pr_main = "$dg_pr_dir/main";

	if ( -f $dg_pr_main ) {
		log_status "Fix version incompatibility, if any.";
		chmod(0755, $dg_pr_main);
		$out = `NSVERSION=$vers NSVARIANT=$variant CLVERSION=$clversion "$dg_pr_main" -o fix 2>&1`;
		$rcode = $? >> 8;
		log_trace "script output (code=$rcode): $out";

		if ( $rcode != 0x00 ) {
			log_status_no_console "script output: $out";
			warn "fix to version incompatibility failed (code=$rcode).";
		}
	}

	log_trace "END $this_subroutine_name";
}

# Various pre-flight checks
sub check_system_before_install {
    my $this_subroutine_name = ( caller(0) )[3];
    $kernpath   = "/flash/";
    $loaderconf = "/flash/boot/loader.conf";

    log_trace " \nBEGIN $this_subroutine_name";
    check_downgrade_prerequisite();
    check_licenseserver_compatibility();
    check_local_license_validity();
    search_nsapimgr_and_warn();
    check_sysid();
    check_sym_link();
    # FIPS
    check_fipsplatform();
    check_vpx();
    check_nsconf();
    check_invalid_config();
    check_callhome();
    check_bot();
    check_admserviceconnect();
    statfs($kernpath);
    setup_paths();
    check_if_portal_core_file_is_modified();
    log_trace "END $this_subroutine_name";
} ## end sub check_system_before_install

# Copy the new kernel from the disk to the flash. Try to save the old flash kernel on disk first.
sub copy_kernel {
    my $this_subroutine_name     = ( caller(0) )[3];
    my $running_kernel_file_name = `sysctl kern.bootfile`;
    my $i                        = 0;
    my @timeData                 = localtime(time);
    my $year                     = 1900 + $timeData[5];
    my $curr_time =
        "ns_"
      . $year . "_"
      . $timeData[4] . "_"
      . $timeData[3] . "_"
      . $timeData[2] . "_"
      . $timeData[1] . "_"
      . $timeData[0];

    log_trace " \nBEGIN $this_subroutine_name";

    $bk_dir = "$varbakpath/$curr_time-running-kernel/";
    mkpath($bk_dir)
      || ns_die("Error: Failed to create archive directory $bk_dir ($!)\n");

# If the user select the archive option earlier, there may not be a kernel in /flash. otherwise back it up
    if ( -f $running_kernel_filename ) {
        log_status_no_console
"Backing up running $running_kernel_filename to $bk_dir/$running_kernel_filename ... ";
        system("cp $running_kernel_filename $bk_dir") == 0
          || ns_die(
            "Error: Can't back up running kernel $running_kernel_filename\n");
        $kernel_backed_up = " yes";
    }
    else {
        $kernel_backed_up = " no";
    }
    my $srcsha2sum = `sha256 -q ./$kernel`
      || ns_die("Error: Can't checksum $kernel\n");

# Compare checksum of new kernel/image file against stored checksum in build tarball
    log_status_no_console "Checksumming ns-${vers}.gz  ... ";
    my $stored_sha2_sum = `cat ns-${vers}.sha2`
      || ns_die("Error: Can't find checksum of build ns-${vers}\n");
    if ( $stored_sha2_sum ne $srcsha2sum ) {
        log_status(
"Error: the checksum $srcsha2sum of the kernel tarball $kernel is different than the stored checksum $stored_sha2_sum of the installing kernel tarball ns-${vers}. You may have a corrupt or incorrect distribution\n"
        );
        ns_die(
"  The new kernel is not installed. Your current running kernel is preserved.\n"
        );
    }
    log_status_no_console "Checksum ok. ";

    open( IN, "< ./$kernel" )
      or ns_die("Failed to open $kernel for reading ($!), aborting...\n");
    open( OUT, "> $kernpath$kernel" )
      or
      ns_die("Failed to open $kernpath$kernel for writing ($!), aborting...\n");

    $blksize = ( stat IN )[11] || 16384;
    log_status("Copying $kernel to $kernpath$kernel ... ");
    log_state("BEGIN KERNEL_COPY");
    $kernel_copied = "no";

    while ( $len = sysread IN, $buf, $blksize ) {
        if ( !defined $len ) {
            next if $! =~ /^Interrupted/;
            ns_die("Read error: $!\n");
        }
        $offset = 0;
        while ($len) {
            defined( $written = syswrite OUT, $buf, $len, $offset )
              or ns_die("System write error: $!\n");
            $len -= $written;
            $offset += $written;
        }

        # Print status dots for copy
        if ( $i % 512 == 0 ) {
            print ".";
        }
        $i++;
    } ## end while ( $len = sysread IN...
    print "\n";

    close(IN);
    close(OUT);
    my $dstsha2sum = `sha256 -q $kernpath$kernel`
      || ns_die("Error: Can't checksum $kernpath$kernel\n");

    if ( !( $srcsha2sum eq $dstsha2sum ) ) {
        log_status(
"Warning: ./$kernel:$srcsha2sum and $kernpath$kernel:$dstsha2sum checksums differ!\n"
        );

        # attempt to restore kernel archived above.
        if ( $kernel_backed_up eq "yes" ) {
            log_status(
"Attempting to restore previous kernel $running_kernel_filename to flash\n"
            );
            system('cp $bk_dir/$running_kernel_filename. gz /flash/') == 0
              || ns_die(
                "Error: Can't restore backup of $running_kernel_filename\n");
            ns_die(
"Installation failed: Mismatched kernel checksum, check free space on destination partition. Previous kernel $running_kernel_filename. gz  restored to flash."
            );
        } ## end if ( $kernel_backed_up...
    } ## end if ( !( $srcsha2sum eq ...
    log_state("END KERNEL_COPY");
    $kernel_copied = "yes";

    log_trace "END $this_subroutine_name";
} ## end sub copy_kernel

# This function removes all the old signature and associated kernel images
sub remove_all_old_signature_and_associated_kernel_images
{
	my $this_subroutine_name = ( caller(0) )[3];
	log_trace " \nBEGIN $this_subroutine_name";
	if ( !$options{D} ) {
		if ( $flag_Y == 1 ) {
			$ans = "y";
		}
		elsif ( $gui_flag == 1 ) {
			$ans = "n";
		}
		else {
			printf("\n  Do you wish to delete old rsa keys, signatures and kernel images? [Y/N] ");
			$ans = read_yesno_input_from_STDIN();
		}
		if ( $ans eq 'y' || $ans eq 'Y' ) {
			log_state("START DELETING ALL THE OLD RSA PUB KEYS, SIGNATURE FILES AND THEIR ASSOCIATED KERNEL IMAGES");
			foreach ( "/flash/secboot", "/nsconfig" ) {
				my $directory = $_;
				printf "\n directory now looking at is $directory\n";
				if( opendir(DIR, $directory) ) {
					while(my $file = readdir(DIR)) {
						next unless (-f "$directory/$file");
						next unless ($file =~ m/.*\.sha512\.signed\.bin$/);
						my $kernel_image_name = (split /\.sha512\.signed\.bin/, $file)[0];
						if (-e '/flash/'.$kernel_image_name) {
							system("rm -f /flash/$kernel_image_name");
							printf "\ndeleting $kernel_image_name in /flash\n";
						}
						else {
							printf "\nkernel images $kernel_image_name is not present\n";
						}
						printf "\ndelting signature file $file in $directory\n";
						system("rm -f $directory/$file");
					}
					closedir(DIR);
				}
				# for old and stale RSA pub keys deletion process.
				if( opendir(DIR, $directory) ) {
					while(my $rsakey = readdir(DIR)) {
						next unless (-f "$directory/$rsakey");
						next unless ($rsakey =~ m/.*\.pem$/);
						if (-e '/flash/secboot/'.$rsakey) {
							system("rm -f /flash/secboot/$rsakey");
							printf "\nDeleting RSA pub key $rsakey in /flash/secboot\n";
						}
						else {
							printf "\nRSA pub key $rsakey is not present\n";
						}
					}
					closedir(DIR);
				}
			}
			log_state("END DELETING ALL THE OLD RSA PUB KEYS, SIGNATURE FILES AND THEIR ASSOCIATED KERNEL IMAGES");
		}
	}
	log_trace "END $this_subroutine_name";
} ## end sub remove_all_old_signature_and_associated_kernel_images

# This function installs kernel signature
sub install_kernel_signature
{
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";
    log_state("START INSTALL KERNEL SIGNATURE");
    (system("mkdir -p /flash/secboot") == 0 and system("openssl base64 -d -in $kernelsignature -out /flash/secboot/$kernelsignature.bin") == 0) or log_status "Failed to install kernel signature: $?";
    log_state("END INSTALL KERNEL SIGNATURE");
    log_trace "END $this_subroutine_name";
} ## end sub install_kernel_signature

# This function installs rsakey
sub install_rsakey {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";
    log_state("START INSTALL RSAKEY");
    if ( -f "$rsakey" ) {
           `cp -pf $rsakey /flash/secboot/`;
    }
    else {
        log_status "Failed to copy as rsakey not found...";
    }
    log_state("END INSTALL RSAKEY");
    log_trace "END $this_subroutine_name";
}  ## end sub install_rsakey

# This function converts all the imported object names and
# their corresponding mapping file entries to lower case before upgrade.
sub convert_import_object_to_lower_case
{
    my $subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $subroutine_name";
    @import_objects = ("/var/download/custom", "/var/download/htmlerrorurl", "/var/download/xmlerrorurl", "/var/download/xmlschema","/var/download/wsdl");
    foreach (@import_objects) {
        my $dirname = $_;
        if (opendir( DIR, $dirname )) {
            my @list_of_files = readdir(DIR);
            foreach (@list_of_files) {
                rename "$dirname/$_","$dirname/\L$_";
            }
            closedir(DIR);
        }
    }
    @import_mapping_files = ("/var/download/mapping-custom", "/var/download/mapping-htmlerrorpage", "/var/download/mapping-xmlerrorpage", "/var/download/mapping-xmlschema", "/var/download/mapping-wsdl");
    foreach (@import_mapping_files) {
        if (open(FH, "<", $_)) {
            if (open(FOH, ">>", $_.tmp)) {
                foreach $line (<FH>) {
                    @csv = split(',', $line);
                    my $lowercasename = lc @csv[1];
                    $line =~ s/@csv[1]/$lowercasename/g;
                    print FOH $line;
                }
                close(FOH);
            }
            close(FH);
            rename $_.tmp,$_;
        }
   }
   log_trace "END $subroutine_name";
} # end sub convert_import_object_to_lower_case

sub handle_learn_db_before_upgrade {
        my $this_subroutine_name = ( caller(0) )[3];
        log_trace " \nBEGIN $this_subroutine_name";
        opendir( DIR, "/var/nslog/asl/" ) || return;
        my @list_of_files = grep(/\.db$/,readdir(DIR));
        foreach (@list_of_files) {
                rename ("/var/nslog/asl/".$_,"/var/nslog/asl/"."\L$_");
                log_state("$_ renamed to \L$_");
        }
        closedir(DIR);
        log_trace "END $subroutine_name";
}

sub handle_file {
    my $this_subroutine_name = ( caller(0) )[3];
    my $path                 = $_[2];
    my $argc                 = $_[1];
    my $line                 = $_[0];
    my @cmdbuf               = split( /\s+/, $line );
    my $file                 = $cmdbuf[$argc];
    my @filenamebuf          = split( /\//, $file );
    my $filename             = $filenamebuf[$#filenamebuf];

    log_trace " \nBEGIN $this_subroutine_name";

    $cmdbuf[$argc] = "$filename";

    copy_file( $file, "$path/$filename" );

    push @files, "$filename";
    log_trace "END $this_subroutine_name";
} ## end sub handle_file

sub copy_default_keys {
  if (-f "./skf")
  {
    log_status "Copying key files...";
    system("cp ./skf $newpath/.skf") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 '$newpath/.skf'") == 0
      || ns_die(
                "Error: Failed to change permission of the required file\n"
               );

  }
}

sub copy_metrics_json_files {
  system("mkdir -p $metrics_conf") == 0 || ns_die("Error: Not able to create required directory\n");
  if (-f "./schema.json")
  {
    log_status "Copying metrics schema.json file...";
    system("cp ./schema.json $metrics_conf/") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 $metrics_conf/schema.json") == 0 || ns_die("Error: Failed to change permission of the required file\n");
  }
  if (-f "./reference_schema.json")
  {
    log_status "Copying metrics reference_schema.json file...";
    system("cp ./reference_schema.json $metrics_conf/") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 $metrics_conf/reference_schema.json") == 0 || ns_die("Error: Failed to change permission of the required file\n");
  }
}

sub copy_analytics_json_files {
  system("mkdir -p $analytics_conf") == 0 || ns_die("Error: Not able to create required directory\n");
  if (-f "./splunk_format.txt")
  {
    log_status "Copying splunk_format.txt file...";
    system("cp ./splunk_format.txt $analytics_conf/") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 $analytics_conf/splunk_format.txt") == 0 || ns_die("Error: Failed to change permission of the required file\n");
  }
  if (-f "./elastic_format.txt")
  {
    log_status "Copying elastic_format.txt file...";
    system("cp ./elastic_format.txt $analytics_conf/") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 $analytics_conf/elastic_format.txt") == 0 || ns_die("Error: Failed to change permission of the required file\n");
  }
  if (-f "./JSON_fields.txt")
  {
    log_status "Copying JSON_fields.txt file...";
    system("cp ./JSON_fields.txt $analytics_conf/") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 $analytics_conf/JSON_fields.txt") == 0 || ns_die("Error: Failed to change permission of the required file\n");
  }
  if (-f "./auditlog_schema_generator_for_hec_export.py")
  {
    log_status "Copying auditlog_schema_generator_for_hec_export.py file...";
    system("cp ./auditlog_schema_generator_for_hec_export.py $analytics_conf/") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 $analytics_conf/auditlog_schema_generator_for_hec_export.py") == 0 || ns_die("Error: Failed to change permission of the required file\n");
  }
}

sub copy_wasm_files {
  system("mkdir -p $wasmpath") == 0 || ns_die("Error: Not able to create required directory\n");
  if (-f "./ns_sigsci.wasm")
  {
    log_status "Copying wasm file...";
    system("cp ./ns_sigsci.wasm $wasmpath/") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 $wasmpath/ns_sigsci.wasm") == 0 || ns_die("Error: Failed to change permission of the required file\n");
  }
  if (-f "./ns_sigsci_wasm.sig")
  {
    log_status "Copying wasm sig file...";
    system("cp ./ns_sigsci_wasm.sig $wasmpath/") == 0 || ns_die("Error: Not able to copy the required file\n");
    system("chmod 444 $wasmpath/ns_sigsci_wasm.sig") == 0 || ns_die("Error: Failed to change permission of the required file\n");
  }
}

sub fix_kek_perms {
  log_status "Fixing permissions of KEK files...";
  system("chmod 600 /nsconfig/.F1.key");
  system("chmod 600 /nsconfig/.F2.key");
  system("chmod 600 /nsconfig/F1.key");
  system("chmod 600 /nsconfig/F2.key");
  system("chmod 600 /nsconfig/keys/F1_*.key");
  system("chmod 600 /nsconfig/keys/F2_*.key");
}

sub fix_sensitive_tempfile_perms {
	log_status "Fixing permissions for sensitive temporary files";
	system("chmod 600 /configdb/nscfg.db.conf");
	system("chmod 600 /nsconfig/pooledlicense.conf");
}

sub fix_ssl_private_key_perms {
	log_status "Fixing permissions for auto-generated private SSL keys";
	# Clear all permission bits for group and other
	# Some of these files may not exist, that's expected
	system("chmod go= /nsconfig/ssl/ns-root.key");
	system("chmod go= /nsconfig/ssl/ns-server.key");
	system("chmod go= /nsconfig/ssl/ns-sftrust-root.key");
	system("chmod go= /nsconfig/ssl/ns-sftrust.key");
	system("chmod go= /nsconfig/ssl/ns-ngsconnector-root.key");
	system("chmod go= /nsconfig/ssl/ns-ngsconnector.key");
	system("chmod go= /nsconfig/ssl/ns-azure-root.key");
	system("chmod go= /nsconfig/ssl/ns-azure.key");
}

sub fix_system_backup_perms {
	if (glob("/var/ns_sys_backup/*.tgz")) {
		log_status "Fixing permissions of .tgz files in /var/ns_sys_backup/...";
		system("chmod 600 /var/ns_sys_backup/*.tgz");
	}
}

sub create_apispec_directory {
    log_status "Creating apispec directory";
    system("mkdir -p /nsconfig/apispec/proto");
    system("mkdir -p /nsconfig/apispec/netscaler/default");
    system("mkdir -p /var/tmp/apispecfile_backup");
}

sub install_default_apispecs {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    if ( -f "default_apispecs.tgz") {
        log_status "Installing default apispcs";
        if (system("tar xvpzf default_apispecs.tgz -C $defaultapispecsdir 2>> ./err_msg")){
                ns_die ("Error: Default apispec untar failed ($!)");
        }
    }
    else {
        log_status "No Default apispecs provided"
    }
}

sub fix_nssynclog_proplog_perms {
    log_status "Fixing permissions of files in /var/nssynclog...";
    system("chmod 600 $nssynclogpath/clus_sync_batch_status.log*");
    system("chmod 600 $nssynclogpath/sync_batch_failures.log*");
    system("chmod 600 $nssynclogpath/sync_batch_status.log*");
    system("chmod 600 $nssynclogpath/sync_clusdiff_status.log*");
    system("chmod 600 $nssynclogpath/sync_route_status.log*");
    system("chmod 600 $nssynclogpath/ns_clus_cfg.conf*");
    system("chmod 600 $nssynclogpath/ns_com_cfg.conf*");
    system("chmod 600 $nssynclogpath/ns_loclclus_cfg.conf*");
    system("chmod 600 $nssynclogpath/ns_sync_pri_cfg.conf*");
    log_status "Fixing permissions of file /var/prop_fail_cmds.txt...";
    system("chmod 600 $varstr/prop_fail_cmds.txt*");
    system("chmod 600 $varstr/log/nsfsyncd_del.list*");
}

sub install_doc {
    my $this_subroutine_name = ( caller(0) )[3];
    my $errs;

    log_trace " \nBEGIN $this_subroutine_name";
    `rm -f ./err_msg`;

    if ( -f "./ns-$vers-doc.tgz" ) {
        log_status "Installing documentation...";
        `tar xfpz ns-$vers-doc.tgz -C $varpath/$docdir 2> ./err_msg`;
    }
    if ( -f "./apidoc.tgz" ) {
        log_status "Installing XML API documentation...";
        `cp -f apidoc.tgz $varpath/$docdir 2>> ./err_msg`;
      }
    if ( -f "./NSConfig.wsdl" ) {
        log_status "Installing NSConfig.wsdl...";
        `cp -f NSConfig.wsdl $varpath/$docdir 2>> ./err_msg`;
      }
    if ( -f "./NSStat.wsdl" ) {
        log_status "Installing NSStat.wsdl...";
        `cp -f NSStat.wsdl $varpath/$docdir 2>> ./err_msg`;
    }
    if ( -f "./err_msg" && $debug_print ) {
        $errs = `cat ./err_msg`;
        log_status $errs;
    }
    log_trace "END $this_subroutine_name";
} ## end sub install_doc

sub install_help()
{
    if ( -f "./help.tgz")
    {
        log_status "Installing online help...";
        `tar xpfz help.tgz -C $varpath/$helpdir 2>> ./err_msg`;
    }
}

sub install_help_cisco()
{
    if ( -f "./help_cisco.tgz")
    {
        log_status "Installing Cisco online help...";
        `tar xpfz help_cisco.tgz -C $varpath/$help_ciscodir 2>> ./err_msg`;
    }
}

sub install_LogonPoint_workspace()
{
    if ( -f "./LogonPoint.tgz")
    {
        if (!-d "$varpath/logon/LogonPoint_workspace/custom") {
            `mkdir -p $varpath/logon/LogonPoint_workspace/custom`;
        }
        `tar xpfz LogonPoint.tgz logon/LogonPoint_workspace/custom  2>> ./err_msg`;
        `cp -n logon/LogonPoint_workspace/custom/* $varpath/logon/LogonPoint_workspace/custom/ 2>> ./err_msg`;
        `rm -rf ./logon 2>> ./err_msg`;
        `tar xpfz LogonPoint.tgz -C $varpath/ --exclude='LogonPoint_workspace/custom/*' --exclude='LogonPoint/custom/*' 2>> ./err_msg`;
    }
}

sub install_LogonPoint()
{

    if ( -f "./LogonPoint.tgz")
    {
        log_status "Installing Logon Point ...";
        if (-d "$varpath/logon/LogonPoint/custom") {
            `tar xpfz LogonPoint.tgz logon/LogonPoint/custom  2>> ./err_msg`;
            `cp -n logon/LogonPoint/custom/* $varpath/logon/LogonPoint/custom/ 2>> ./err_msg`;
            `rm -rf ./logon 2>> ./err_msg`;
            # Deleting old checksum_*.txt files if any
            `find $varpath/logon/LogonPoint/ -name "checksum_*.txt" -exec rm -rf {} \\;`;
            `mkdir -p $varpath/logon/images`;
            my @custom_themes = glob("$logonThemes/*");
            my @builtin_themes = ("$logonThemes/Default", "$logonThemes/Greenbubble", "$logonThemes/X1", "$logonThemes/EULA");
            my @index_files = ("index.html", "tmindex.html");
            foreach my $theme (@custom_themes) {
                if ( grep(/^$theme/, @builtin_themes) || (! -d $theme)) {
                    next;
                }
                foreach my $file (@index_files) {
                    if (! -e "$theme/$file") {
                        `ln -s $varpath/logon/LogonPoint/$file $theme/$file`;
                    }
                }
            }
        }
        else {
            `tar xpfz LogonPoint.tgz -C $varpath/ --exclude='LogonPoint_workspace/custom/*' 2>> ./err_msg`;
        }
        system("perl $varpath/logon/themes/EULA/eula_upgrade.pl") == 0 or log_status "Failed to execute eula_upgrade.pl: $?";
    }
}

sub install_LoginSchemaFiles()
{
    if ( -f "./LoginSchema.tgz")
    {
        log_status "Installing Login Schema files ...";
        `tar xpfz LoginSchema.tgz -C $newpath/loginschema/ 2>> ./err_msg`;
    }
}

sub install_OTPToolFiles()
{
    if ( -f "./OTP_tool.tgz")
    {
        log_status "Installing OTP tool files ...";
	`mkdir -p $varpath/otptool`;
        `tar xpfz OTP_tool.tgz -C $varpath/otptool/ 2>> ./err_msg`;
    }
}


sub install_app_catalog_files()
{
    if ( -f "./app_catalog.tgz")
    {
        log_status "Installing SaaS Template app catalog files ...";
        `tar xpfz app_catalog.tgz -C $varstr/ 2>> ./err_msg`;
    }
}


sub install_root_certs()
{
    if ( -f "./adc-root-certs.crt")
    {
        log_status "Installing root certificates ...";
        `cp -f adc-root-certs.crt $newpath/$ssldir 2>> ./err_msg`;
    }
}


sub install_callhome_cert()
{
    if ( -f "./BaltimoreCyberTrustRoot.cert")
    {
        log_status "Installing Jazz certificate ...";
        `cp -f BaltimoreCyberTrustRoot.cert $newpathlicense/$ssldir 2>> ./err_msg`;
    }
    if ( -f "./BaltimoreCyberTrustRoot_CH.cert")
    {
         log_status "Installing Call Home certificate ...";
         `cp -f BaltimoreCyberTrustRoot_CH.cert $newpathlicense/$ssldir 2>> ./err_msg`;
    }
    if ( -f "./cis.citrix.com.pem")
    {
        log_status "Installing CIS server certificate ...";
        `cp -f cis.citrix.com.pem $newpathlicense/$ssldir 2>> ./err_msg`;
    }
}

sub install_linux_package()
{
	log_status "Installing Debian, RPM packages ...";
	if ( ! -d "$varpath/gui_new/vpn/scripts/linux" )
	{
		system("mkdir -p $varpath/gui_new/vpn/scripts/linux") == 0 || ns_die("Error: Can't create $varpath/gui_new/vpn/scripts/linux");
	}
	system("cp -f nsg*  $varpath/gui_new/vpn/scripts/linux/") == 0 || ns_die("Error: Failed to copy Linux packages to the new path\n");

	system("cp -f clientversions.xml $varpath/gui_new/vpn/scripts/linux/") == 0 || ns_die("Error: Failed to copy clientversions.xml to the new path\n");
}

sub install_geoipdb()
{
    if ( -f "./Citrix_Netscaler_InBuilt_GeoIP_DB_IPv4.gz" && -f "./Citrix_Netscaler_InBuilt_GeoIP_DB_IPv6.gz")
    {
        log_status "Installing Geo-IP DB Citrix_Netscaler_InBuilt_GeoIP_DB_IPv4.gz and Citrix_Netscaler_InBuilt_GeoIP_DB_IPv6.gz …";
        if ( ! -d "$varpath/inbuilt_db" )
        {
          `mkdir -p $varpath/inbuilt_db 2>> ./err_msg`;
        }
        `mv ./Citrix_Netscaler_InBuilt_GeoIP_DB_IPv4.gz $varpath/inbuilt_db 2>> ./err_msg`;
        `mv ./Citrix_Netscaler_InBuilt_GeoIP_DB_IPv6.gz $varpath/inbuilt_db 2>> ./err_msg`;
        `cd $varpath/inbuilt_db/ ; gunzip -f Citrix_Netscaler_InBuilt_GeoIP_DB_IPv4.gz 2>>  ./err_msg`;
        `cd $varpath/inbuilt_db/ ; gunzip -f Citrix_Netscaler_InBuilt_GeoIP_DB_IPv6.gz 2>>  ./err_msg`;
    }
    elsif ( -f "./Citrix_Netscaler_InBuilt_GeoIP_DB.csv.gz")
    {
	log_status "Installing Geo-IP DB...";
	if ( ! -d "$varpath/inbuilt_db" )
	{
	  `mkdir -p $varpath/inbuilt_db 2>> ./err_msg`;
	}
	`mv ./Citrix_Netscaler_InBuilt_GeoIP_DB.csv.gz $varpath/inbuilt_db 2>> ./err_msg`;
	`cd $varpath/inbuilt_db/ ; gunzip -f Citrix_Netscaler_InBuilt_GeoIP_DB.csv.gz 2>>  ./err_msg`;
    }
}

sub install_nfast()
{
	if ( -f "./thales_dirs.tar")
	{
		if ( -d "/var/opt/nfast")
		{
			log_status "/var/opt/nfast directory exists. Extracting hardserver files.";
			`tar xpf thales_dirs.tar --uid 0 --gid 0 -C /var opt/nfast/sbin/hardserver 2>> ./err_msg`;
			`tar xpf thales_dirs.tar --uid 0 --gid 0 -C /var opt/nfast/scripts/startup/hardserver 2>> ./err_msg`;
			`tar xpf thales_dirs.tar --uid 0 --gid 0 -C /var opt/nfast/scripts/startup/hs_log_roll 2>> ./err_msg`;
		}
		else {
			log_status "Installing thales files...";
			`tar xpf thales_dirs.tar --uid 0 --gid 0 -C /var 2>> ./err_msg`;
		}
	}
}

sub install_safenet()
{
    if ( -f "./safenet_dirs.tar")
    {
        if( -d "/var/safenet/")
        {
            log_status "/var/safenet/ directory exists.";
            # /var/safenet/safenet/version only exists when safenet is installed
            my $conf = "/var/tmp/safenet_conf.$$.tar";
            if ( -f "/var/safenet/safenet/version" )
            {
                # save essential configuration
                `rm -f $conf`;
                `tar cvf $conf -C /var/safenet safenet_is_enrolled sfgw_ident_file sfnt_key_boot_file gateway/gw_delay 2>> ./err_msg`;
                `cd /var/safenet; find config/ -name "Chrystoki*.conf" -exec tar -uf $conf {} \\;`;
                `cd /var/safenet; tar -uvf $conf safenet/version safenet/lunaclient/configData safenet/lunaclient/cert safenet/lunaclient/htl safenet/lunaclient/data 2>> ./err_msg`;
            }

            # wipe the /var/safenet directory
            `rm -rf /var/safenet 2>> ./err_msg`;
            # install safenet bundle
            `tar xpf safenet_dirs.tar --uid 0 --gid 0 -C /var 2>> ./err_msg`;
            `cp  -f safenet_dirs.tar /var/safenet 2>> ./err_msg`;
            `chown root:wheel /var/safenet/safenet_dirs.tar 2>> ./err_msg`;

            # restore configuration.
            # if safenet was previously installed, all the lunaclient files except
            # bin/* and lib/* will be included in safenet_conf.tar
            # reinstall the bin/ and lib/ directories from the installed version
            if ( -f $conf)
            {
                `tar xpvf $conf -C /var/safenet 2>> ./err_msg`;
                `tar xzvf /var/safenet/SAClient_\`cat /var/safenet/safenet/version\`.tgz -C /var/safenet safenet/lunaclient/bin safenet/lunaclient/lib 2>> ./err_msg`;
                `rm -f $conf`;
            }
        }
        else {
            log_status "Installing safenet files...";
            `tar xpf safenet_dirs.tar --uid 0 --gid 0 -C /var 2>> ./err_msg`;
        }
    }
}

# Installs the empty database files that will be used for the NetStar inCompass SDK
# for URL categorization in the SWG URL filtering feature.
sub install_inCompass_db_files()
{
    if ( -f "./inCompass-db-files.tgz")
    {
        if ( ! -d "$varstr/$inCompassDb" )
        {
                system("mkdir -p $varstr/$inCompassDb") == 0 || ns_die("Error: Can't create $varstr/$inCompassDb");
        }
        log_status "Installing inCompass DB files...";
        `tar xpfz inCompass-db-files.tgz -C $varstr/$inCompassDb 2>> ./err_msg`;
    }
}

#Removing vPath Libraries as vPath is not supported

# Install Python Libraries
sub install_python {
    if ( -f "./python.tgz") {
        log_status "Extracting python...";
        if ( -f "$varstr/python" ){
            if (system ("rm -f $varstr/python")){
                ns_die("Unable to delete python file ($!)");
            }
        }
        if ( -d "$varstr/python" ){
            if (system("rm -rf $varstr/tmp/_nsinstalltmp/python")){
                ns_die ("Error: Unable to delete the temporary python folder '/var/tmp/_nsinstalltmp/python' ($!)");
            }
            if (system ("mkdir -p $varstr/tmp/_nsinstalltmp")){
                ns_die ("Error: Unable to create temporary folder '/var/tmp/_nsinstalltmp' ($!)");
            }
            if (system("tar xvpfz python.tgz -C $varstr/tmp/_nsinstalltmp 2>> ./err_msg")){
                ns_die ("Error: Python untar failed to temporary location /var/tmp/_nsinstalltmp ($!)");
            }
            if (system("rm -rf $varstr/python")){
                log_status "Unable to delete the python folder ...";
                system("ls -l $varstr/python/ >> ./err_msg");
                system("/usr/local/bin/lsof $varstr/python/ >> ./err_msg");
                log_status "Warning: Failed to remove contents of $varstr/python, renaming to python_old. please clean manually ($!)";
                if (system("mv $varstr/python $varstr/python_old")){
                    log_status ("Warning: Unable to move python ($!)");
                }
            }
            if (system("mv $varstr/tmp/_nsinstalltmp/python $varstr")){
                ns_die ("Error: Failed to move python, move '/var/tmp/python' to '/var/python' ($!)");
            }
        }
        else{
            if (system("tar xvpfz python.tgz -C $varstr 2>> ./err_msg")){
                ns_die ("Error: Python untar failed ($!)");
            }
        }
    }
    else{
        ns_die ("Python tar ball is not found in the installation package ($!) \n");
    }
}

# Install Old nskrb Binary
sub install_nskrb {
    if ( -f "./nskrb_1_5_3") {
        log_status "Copying old nskrb...";
        `mkdir -p $nskrb_path`;
        `cp -f nskrb_1_5_3 $nskrb_path/nskrb_1_5_3 2>> ./err_msg`;
        `chown root:wheel $nskrb_path/nskrb_1_5_3 2>> ./err_msg`;
        `chmod 550 $nskrb_path/nskrb_1_5_3 2>> ./err_msg`;
        if ( -f "./nskrb_Change_Instructions" )
        {
                log_status "Copying nskrb change instructions...";
                `cp -f nskrb_Change_Instructions $nskrb_path/nskrb_Change_Instructions 2>> ./err_msg`;
                `chown root:wheel $nskrb_path/nskrb_Change_Instructions 2>> ./err_msg`;
                `chmod 644 $nskrb_path/nskrb_Change_Instructions 2>> ./err_msg`;
        }
    }
}

# Install Perl5
sub install_perl {
    my $this_subroutine_name = ( caller(0) )[3];
    my $errs;
    if ( -f "./perl5.tar") {
        log_status "Extracting perl5...";
        if ( -d "$varstr/perl5" ) {
            if (system("rm -rf $varstr/perl5")) {
                log_status "\n Unable to delete the perl5 folder ...";
                ns_die("Error: Failed to remove contents of $varstr/perl5 ($!)\n");
            }
        }
        `tar xvpfz perl5.tar -C / 2>> ./err_msg`;
        log_status "Extracting perl5... done";
    }
    if ( -f "./err_msg" && $debug_print ) {
        $errs = `cat ./err_msg`;
        log_status $errs;
    }
    log_trace "END $this_subroutine_name";
}

# Install PostgreSQL
sub install_pgsql {
    my $this_subroutine_name = ( caller(0) )[3];
    my $errs;
    if ( -f "./pgsql.tar") {
        log_status "Extracting pgsql...";
        if ( -d "$varstr/pgsql" ) {
            if (system("rm -rf $varstr/pgsql")) {
                log_status "\n Unable to delete the pgsql folder ...";
                ns_die("Error: Failed to remove contents of $varstr/pgsql ($!)\n");
            }
        }
        `tar xvpfz pgsql.tar -C / 2>> ./err_msg`;
        log_status "Extracting pgsql... done";
    }
    if ( -f "./err_msg" && $debug_print ) {
        $errs = `cat ./err_msg`;
        log_status $errs;
    }
    log_trace "END $this_subroutine_name";
}

# Extract NextGenAPI
sub extract_nextgen {
    my $this_subroutine_name = ( caller(0) )[3];
    my $errs;
    if ( -f "./nextgenapi.tar") {
        log_status "Extracting nextgenapi...";
        if ( -d "$varstr/nextgen/infra" ) {
            if (system("rm -rf $varstr/nextgen/infra")) {
                log_status "\n Unable to delete the nextgen folder ...";
                ns_die("Error: Failed to remove contents of $varstr/nextgen/infra ($!)\n");
            }
        }
        `tar xvpfz nextgenapi.tar -C /var/ 2>> ./err_msg`;
        log_status "Extracting nextgenapi... done";
    }
    if ( -f "./err_msg" && $debug_print ) {
        $errs = `cat ./err_msg`;
        log_status $errs;
    }
    log_trace "END $this_subroutine_name";
}

sub install_openapi_specification{
    my $this_subroutine_name = ( caller(0) )[3];
    my $errs;
    my $nitro_oas = "$nitropath/nitro-oas";
    log_trace " \nBEGIN $this_subroutine_name";
    log_status "Extracting openapi specifications...";
    if ( -f "$nitropath/nitro-oas.tgz" ) {
       `mkdir $nitro_oas`;
       `tar xvpfz $nitropath/nitro-oas.tgz -C $nitro_oas 2>> ./err_msg`;
       `tar -xpf $nitro_oas/ns_nitro-oas_*.tar -C $nitro_oas 2>> ./err_msg`;
        if ( -f "./err_msg" && $debug_print ) {
            $errs = `cat ./err_msg`;
            log_status $errs;
         }
   }
   else {
          log_status "nitro-oas.tgz file not found";
    }
    log_status "Extracting openapi specifications... done";
    log_trace "END $this_subroutine_name";

}

# Remove existing golang
sub remove_golang {
    if ( -d "$varstr/golang" ) {
        log_status "Removing golang...";
        system("rm -rf $varstr/golang") == 0 || ns_die("Error: Failed to remove contents of $varstr/golang ($!)\n");
    }
}

# Remove gnostic
sub remove_gnostic {
    if ( -d "$varstr/gnostic" ) {
        log_status "Removing gnostic...";
        system("rm -rf $varstr/gnostic") == 0 || ns_die("Error: Failed to remove contents of $varstr/gnostic ($!)\n");
    }
}

sub install_epa_package()
{
    if ( -f "./epaPackage.exe")
    {
      log_status "Installing EPA Package ...";
      if ( ! -d "$varpath/gui_new/epa/scripts/win" )
      {
		system("mkdir -p $varpath/gui_new/epa/scripts/win") == 0 || ns_die("Error: Can't create $varpath/gui_new/epa/scripts/win");
	 }
      system("cp epaPackage.exe $varpath/gui_new/epa/scripts/win/") == 0 ||
	        		ns_die("Error: Failed to copy epaPackage to the new path\n");
    }
    if ( -f "./Citrix_Endpoint_Analysis.dmg")
    {
      print "Installing Mac EPA and Mac EPA version file...\n";
      if ( ! -d "$varpath/gui_new/epa/scripts/mac" )
      {
        system("mkdir -p $varpath/gui_new/epa/scripts/mac") == 0 || ns_die("Error: Can't create $varpath/gui_new/epa/scripts/mac");
      }
      system("cp ./Citrix_Endpoint_Analysis.dmg $varpath/gui_new/epa/scripts/mac/") == 0 || ns_die("Error: Can't copy Mac EPA binary\n");
      system("cp ./epamacversion.txt $varpath/gui_new/epa/scripts/mac/") == 0 || ns_die("Error: Can't cp Mac EPA version file\n");
    }
    if ( -f "./MacLibs.zip")
    {
      print "Installing Mac EPA libraries...\n";
      if ( ! -d "$varpath/gui_new/epa/scripts/mac" )
      {
        system("mkdir -p $varpath/gui_new/epa/scripts/mac") == 0 || ns_die("Error: Can't create $varpath/gui_new/epa/scripts/mac");
      }
      system("cp ./MacLibs.zip $varpath/gui_new/epa/scripts/mac/") == 0 || ns_die("Error: Can't copy Mac EPA libraries\n");
    }
	if( -f "./nsepa.deb")
	{
		log_status "Installing Linux EPA and Linux EPA version file...\n";
        if ( ! -d "$varpath/gui_new/epa/scripts/linux" )
		{
			system("mkdir -p $varpath/gui_new/epa/scripts/linux") == 0 || ns_die("Error: Can't create $varpath/gui_new/epa/scripts/linux");
		}
		system("cp nsepa*  $varpath/gui_new/epa/scripts/linux/") == 0 || ns_die("Error: Can't copy Linux EPA binary\n");
	}
}

# If a separate GUI tarball exists, install it on the disk.
# Netscaler releases have the capability to generate the GUI targets in the memory file system
# or on the /var partition. If the GUI tarball does not exist, the GUI is resident in the memory image and
# no installation is required.
sub install_gui {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";
    if ( -f "./ns-$vers-gui.tar" ) {
        # Remove gui_new directory if it exists and create fresh
        if ( -d "$varpath/gui_new" ) {
            system("rm -rf $varpath/gui_new") == 0
              || ns_die(
                "Error: Failed to remove contents of $varpath/gui_new ($!)\n");
        }
        system("mkdir -p $varpath/gui_new") == 0
          || ns_die("Error: Can't create $varpath/gui_new ($!)\n");
        
        log_status "Installing GUI to gui_new...";
        system ("tar xfp ns-$vers-gui.tar -C $varpath/gui_new") == 0
          || ns_die("Error: Can't un-tar GUI\n");
        system("chown -R root:wheel  $varpath/gui_new/vpn") == 0
          || ns_die("Error: Failed to change owner of $varpath/gui_new/vpn ($!)\n");
        system("chown -R root:wheel  $varpath/gui_new/vpns") == 0
          || ns_die("Error: Failed to change owner of $varpath/gui_new/vpns ($!)\n");
        system("chown -R root:wheel  $varpath/gui_new/epa") == 0
          || ns_die("Error: Failed to change owner of $varpath/gui_new/epa ($!)\n");
    }

    if ( -f "./Citrix_Access_Gateway.dmg")
    {
      print "Installing Mac binary and Mac version file...\n";
      if ( ! -d "$varpath/gui_new/vpns/scripts/mac" )
      {
        system("mkdir -p $varpath/gui_new/vpns/scripts/mac") == 0 || ns_die("Error: Can't create $varpath/gui_new/vpns/scripts/mac");
      }
      system("mv ./Citrix_Access_Gateway.dmg $varpath/gui_new/vpns/scripts/mac/Citrix_Access_Gateway.dmg") == 0 || ns_die("Error: Can't mv Mac binary\n");
      system("mv ./macversion.txt $varpath/gui_new/vpns/scripts/mac/macversion.txt") == 0 || ns_die("Error: Can't mv Mac version file\n");
    }

    if ( -f "./AGEE_setup.exe")
    {
      print "Installing Windows binary file...\n";
      if ( ! -d "$varpath/gui_new/vpns/scripts/vista" )
      {
        system("mkdir -p $varpath/gui_new/vpns/scripts/vista") == 0 || ns_die("Error: Can't create $varpath/gui_new/vpns/scripts/vista");
      }
      system("cp ./AGEE_setup.exe $varpath/gui_new/vpns/scripts/vista/AGEE_setup.exe") == 0 || ns_die("Error: Can't copy Windows binary\n");
    }
    else
    {
      print("Warning: Couldn't find AGEE_setup.exe file...\n");
    }

    if ( -f "./nsepa_setup.exe")
    {
      print "Installing Win EPA binary files...\n";
      if ( ! -d "$varpath/gui_new/epa/scripts/win" )
      {
        system("mkdir -p $varpath/gui_new/epa/scripts/win") == 0 || ns_die("Error: Can't create $varpath/gui_new/epa/scripts/win");
      }
      system("cp ./nsepa_setup.exe $varpath/gui_new/epa/scripts/win/nsepa_setup.exe") == 0 || ns_die("Error: Can't copy Win EPA binary\n");
    }
    else
    {
        print("Warning: Couldn't find nsepa_setup.exe file...\n");
    }

    log_trace "END $this_subroutine_name";
} ## end sub install_gui

sub check_version {
    my ($ver1, $ver2) = @_;
    if($ver1 eq $ver2) {
        return 2;
    }
    @values = (split(/\./, $ver1));
    my $ver1_major_version = int($values[0]);
    my $ver1_minor_version = int($values[1]);
    @values = (split(/\./, $ver2));
    my $ver2_major_version = int($values[0]);
    my $ver2_minor_version = int($values[1]);
    if(($ver1_major_version > $ver2_major_version) or(($ver1_major_version == $ver2_major_version) and ($ver1_minor_version > $ver2_minor_version))){
        return 1;
    }
    return 0;
}

sub cmp_version{
    my ($ver_1, $ver_2) = @_;
    my $ver_1_release_version = ((split(/-/, $ver_1))[0]);
    my $ver_1_build_version = ((split(/-/, $ver_1))[1]);
    print "ver_1 version: ", $ver_1_release_version, " ", $ver_1_build_version, "\n";

    my $ver_2_release_version = ((split(/-/, $ver_2))[0]);
    my $ver_2_build_version = ((split(/-/, $ver_2))[1]);
    print "ver_2_version: ", $ver_2_release_version, " ", $ver_2_build_version, "\n";

    my $cmp_release = check_version($ver_1_release_version, $ver_2_release_version);
    my $cmp_build = check_version($ver_1_build_version, $ver_2_build_version);
    print "cmp_release: ", $cmp_release, ", cmp_build: ", $cmp_build, "\n";
    if (($cmp_release == 1) or (($cmp_release == 2) and ($cmp_build == 1))) {
        return 1;
    }

    return 0;

}




sub install_mastools{
    my $this_subroutine_name = ( caller(0) )[3];


    log_trace " \nBEGIN $this_subroutine_name";
	if ( -f "./mastools_scripts.tgz") {
		log_status "Installing MASTools scripts...";
		if ( ! -e "$mastoolspath/version.txt" ) {
			log_status "Extracting MASTools...";
			if ( -d "$mastoolspath" ) {
				log_status "rm -rf $mastoolspath/*";
				system("rm -rf $mastoolspath/*") == 0 || ns_die("Error: Can't remove existing $mastoolspath/*");
			}
			else {
				log_status "mkdir -p $mastoolspath";
				system("mkdir -p $mastoolspath") == 0 || ns_die("Error: Can't create $mastoolspath");
			}
			log_status "tar xpfz ./mastools_scripts.tgz -C $mastoolspath";
			system("tar xpfz ./mastools_scripts.tgz -C $mastoolspath") == 0 || die "unable to unpack ./mastools-$vers.tgz at $mastoolspath\n";
			system("mkdir -p $mastoolspath/logs");
			system("mkdir -p $mastoolspath/conf");
			system("echo '0.0-0.0' > $mastoolspath/version.txt");
			if ( -e "$mastoolspath/scripts/mastools_init.sh" ) {
				system("chmod +x $mastoolspath/scripts/mastools_init.sh");
			}
			if ( -e "$mastoolspath/scripts/mastoolsd" ) {
				system("chmod +x $mastoolspath/scripts/mastoolsd");
			}
		}
		else {
    		my $mastools_version = `cat $mastoolspath/version.txt`;
			chomp($mastools_version);

            my $cmp_golden = cmp_version("14.1-9.10",$mastools_version);
            my $cmp_current = cmp_version($vers,$mastools_version);

            print "cmp_golden: ", $cmp_golden, ",  cmp_current: ", $cmp_current, "\n";

			if (($cmp_golden == 1) or (($cmp_golden == 0) and ($cmp_current == 1)))  {
				log_status "tar xpfz ./mastools_scripts.tgz -C $mastoolspath";
				system("tar xpfz ./mastools_scripts.tgz -C $mastoolspath") == 0 || die "unable to unpack ./mastools-$vers.tgz at $mastoolspath\n";
				if ( -e "$mastoolspath/scripts/mastools_init.sh" ) {
					system("chmod +x $mastoolspath/scripts/mastools_init.sh");
				}
				if ( -e "$mastoolspath/scripts/mastoolsd" ) {
					system("chmod +x $mastoolspath/scripts/mastoolsd");
				}
			}
		}
	}
}



sub install_nsconnector{
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";
        if ( -f "./nsconnector.tgz") {
                log_status "Installing nsconnector certs ...";
                        log_status "Extracting nsconnector...";
                        if ( -d "$nsconnectorpath" ) {
                                log_status "rm -rf $nsconnectorpath/cert/*";
                                system("rm -rf $nsconnectorpath/cert/*") == 0 || ns_die("Error: Can't remove existing $nsconnectorpath/cert/*");
                        }
                        else {
                                log_status "mkdir -p $nsconnectorpath";
                                system("mkdir -p $nsconnectorpath") == 0 || ns_die("Error: Can't create $nsconnectorpath");
                        }
                        log_status "tar xpfz ./nsconnector.tgz -C $nsconnectorpath";
                        system("tar xpfz ./nsconnector.tgz -C $nsconnectorpath") == 0 || die "unable to unpack ./nsconnector.tgz at $nsconnectorpath\n";
                        log_status "rm nsconnector.tgz";
			log_status "Copying ctrl-ca-cert.pem to /nsconfig/ssl ...";
			system("cp $nsconnectorpath/cert/ctrl-ca-cert.pem  $newpath/$ssldir") == 0 || die "unable to copy ctrl-ca-cert.pem to $newpath/$ssldir\n";
                        system("rm nsconnector.tgz") == 0 || die "unable to remove nsconnector.tgz\n";
        }
}

# Install Nitro
sub install_nitro {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";
    my %names = (
        'nitro-java'            => 'NITRO java',
        'nitro-csharp'          => 'NITRO csharp',
        'nitro-rest'            => 'NITRO rest',
        'nitro-python'          => 'NITRO python',
        'nitro-go'              => 'NITRO go',
        'nitro-perl-samples'    => 'NITRO perl samples',
        'nitro-powershell'      => 'NITRO powershell',
        'nitro-oas'             => 'NITRO Open-API SPEC');
    # XXX Not sure if this is necessary, but the file check was done before
    #     the change
    my $do_install = 0;
    foreach my $n (keys %names) {
        if ( -f "./ns-$vers-$n.tgz" ) {
            $do_install = 1;
            last;
        }
    }
    if ( $do_install ) {
        if ( -d "$nitropath" ) {
            system("rm -rf $nitropath/*") == 0
              || ns_die("Error: Failed to remove contents of $nitropath ($!)\n");
        }
        log_status "Installing NITRO...";
        foreach my $n (keys %names) {
            system("cp -f ns-$vers-$n.tgz $nitropath/") == 0
              || ns_die("Error: Can't copy $names{$n}\n");

            # Make sure that the file has the expected file permission.
            system("chmod 0644 $nitropath/ns-$vers-$n.tgz") == 0
              || ns_die("Error: Can't set the file permission on $names{$n}\n");

            #Before creating link, make sure there is no directory/file in the same name
            `rm -f $nitropath/$n.tgz`;
            system("ln -fs $nitropath/ns-$vers-$n.tgz $nitropath/$n.tgz") == 0
              || ns_die("Error: Failed to create $names{$n} link\n");
        }
    }
    log_trace "END $this_subroutine_name";
}

# Upgrade script to run BEFORE booting with new build
# to stop nsclfsyncd process to avoid overwriting of files with older versions.
sub nsclfsyncd_stop {
  system("/netscaler/nsp nsclfsyncd pbmonitor off");
  system("/netscaler/nsp nsclfsyncd exit");
}

# Upgrade script to run BEFORE booting with new build
# Add commands in /flash/nsconfig/ns_before_upgrade.sh, which will be run BEFORE boot.
sub upgrade_before_pe_start {
    my $this_subroutine_name = ( caller(0) )[3];
    my $installns_dir = dirname(abs_path("$0"));

    log_trace " \nBEGIN $this_subroutine_name";

    # Move installns_post_boot and cloud scripts to /var/tmp
    log_status "Moving installns_post_boot and cloud scripts to /var/tmp ...";
    system("mkdir -p /var/tmp/install_post_bootup_sources/") == 0 || ns_die("Error: Failed to create /var/tmp/install_post_bootup_sources/ ($!)\n");
    system("mv ./installns_post_boot /var/tmp/install_post_bootup_sources/") == 0 || ns_die("Error: Failed to move installns_post_boot to /var/tmp ($!)\n");
    if ( -f "./azureagent.tgz" ) {
        log_status "Moving azureagent.tgz to /var/tmp ...";
        system("mv ./azureagent.tgz /var/tmp/install_post_bootup_sources/") == 0 || ns_die("Error: Failed to move azureagent.tgz to /var/tmp ($!)\n");
    }

    if ( -f "./azureautoconfig.tgz" ) {
        log_status "Moving azureautoconfig.tgz to /var/tmp ...";
        system("mv ./azureautoconfig.tgz /var/tmp/install_post_bootup_sources/") == 0 || ns_die("Error: Failed to move azureautoconfig.tgz to /var/tmp ($!)\n");
    }

    if ( -f "./cloudhadaemon.tgz" ) {
        log_status "Moving cloudhadaemon.tgz to /var/tmp ...";
        system("mv ./cloudhadaemon.tgz /var/tmp/install_post_bootup_sources/") == 0 || ns_die("Error: Failed to move cloudhadaemon.tgz to /var/tmp ($!)\n");
    }

    if ( -f "./cloudautoscale.tgz" ) {
        log_status "Moving cloudautoscale.tgz to /var/tmp ...";
        system("mv ./cloudautoscale.tgz /var/tmp/install_post_bootup_sources/") == 0 || ns_die("Error: Failed to move cloudautoscale.tgz to /var/tmp ($!)\n");
    }

    # Upgrade all files, including imported objects
    log_status "Creating before PE start upgrade script ...";

    if (open(BEFORE_UPGRADE, "> /flash/nsconfig/upgrade_before_pe_start.sh ")) {
        print BEFORE_UPGRADE "#!/usr/bin/bash\n";
        print BEFORE_UPGRADE "/usr/bin/logger -p local0.err \"Starting upgrade of AppFw signature files\"\n";
        print BEFORE_UPGRADE "/usr/local/bin/perl /netscaler/upgrade_appfw_imports.pl -before_ppe_start\n";
        print BEFORE_UPGRADE "/usr/bin/logger -p local0.err \"Finished upgrade of AppFw signature files\"\n";
        print BEFORE_UPGRADE "/usr/bin/logger -p local0.err \"Starting upgrade of BOT signature files\"\n";
        print BEFORE_UPGRADE "/var/python/bin/python /netscaler/upgrade_bot_schema.py -before_ppe_start\n";
        print BEFORE_UPGRADE "/var/python/bin/python /netscaler/upgrade_bot_imports.py -before_ppe_start\n";
        print BEFORE_UPGRADE "/usr/bin/logger -p local0.err \"Finished upgrade of BOT signature files\"\n";
        print BEFORE_UPGRADE "/usr/bin/logger -p local0.err \"Starting upgrade of BOT mapping files\"\n";
        print BEFORE_UPGRADE "/var/python/bin/python /netscaler/upgrade_bot_mapfile.py\n";
        print BEFORE_UPGRADE "/usr/bin/logger -p local0.err \"Finished upgrade of BOT mapping files\"\n";
        print BEFORE_UPGRADE "/usr/bin/logger -p local0.err \"Starting upgrade of Responder mapping files\"\n";
        print BEFORE_UPGRADE "/var/python/bin/python /netscaler/upgrade_responder_mapfile.py\n";
        print BEFORE_UPGRADE "/usr/bin/logger -p local0.err \"Finished upgrade of Responder mapping files\"\n";
        print BEFORE_UPGRADE "cd /var/tmp/install_post_bootup_sources && NSVERSION=$vers NSVARIANT=$variant CLVERSION=$clversion \\\n";
        print BEFORE_UPGRADE "/usr/bin/perl installns_post_boot\n";
        print BEFORE_UPGRADE "cd .. && rm -rf /var/tmp/install_post_bootup_sources\n";
        close(BEFORE_UPGRADE);
    } else {
        log_status "Failed to create script to be run before PE start. Please reopen and save back all AppFw signatures after reboot.\n";
    }
    log_trace "END $this_subroutine_name";
}  ## end sub upgrade_before_pe_start


# Upgrade script to run after booting with new build
# Add commands in /flash/nsconfig/nsafterupgrade.sh, which will be run after boot.
sub after_upgrade {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    # Upgrade all files, including imported objects
    log_status "Creating after upgrade script ...";

    if (open(AFTER_UPGR, "> /flash/nsconfig/nsafterupgrade.sh ")) {
		close(AFTER_UPGR);
    } else {
        log_status "Failed to create script to be run after upgrade. Please reopen and save back all AppFw signatures after reboot.\n";
    }
    log_trace "END $this_subroutine_name";
}  ## end sub after_upgrade

# Upgrade nextgenapi script to run before nextgenapi bootup
sub nextgen_upgrade {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    log_status "Creating nextgenapi upgrade script ...";

    if (open(NGAPI_UPGR, "> /flash/nsconfig/nsnextgen_upgrade.sh ")) {
        print NGAPI_UPGR "[ -r /var/nextgen/infra/scripts/install_nextgen.sh ] && /bin/sh /var/nextgen/infra/scripts/install_nextgen.sh\n";
		close(NGAPI_UPGR);
    } else {
        log_status "Failed to create nextgenapi upgrade script.\n";
    }
    log_trace "END $this_subroutine_name";
}  ## end sub nextgen_upgrade

sub install_lom {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    if ( -f "./bmc_releases" ) {
        log_status "Storing LOM firmware...";
	   `mv bmc_releases /var/tmp/`;
    }
    if ( -f "./11k5_bmc.bin" ) {
	   `mv *_bmc.bin /var/tmp/`;
    }
    if ( -f "./sum" ) {
	`mv sum /var/tmp/`;
    }
}  ## end sub install_lom

sub install_bios {
 my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    if ( -f "./bios_releases" ) {
        log_status "Storing BIOS firmware...";
           `mv bios_releases /var/tmp/`;
    }
    if ( -f "./9k_bios.bin" ) {
           `mv *_bios.bin /var/tmp/`;
    }
}  ## end sub install_bios

sub install_nic_firmware {

    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    if ( -d "Mellanox" ) {
           log_status "Storing NIC firmware...";
           if ( -d "/var/tmp/Mellanox" ) {
                  `rm -rf /var/tmp/Mellanox`;
           }
           `mv Mellanox /var/tmp/`;
    }
    if ( -d "Fortville_Silicom_Intel" ) {
           log_status "Storing Fortville NIC firmware...";
           if ( -d "/var/tmp/Fortville_Silicom_Intel" ) {
                  `rm -rf /var/tmp/Fortville_Silicom_Intel`;
           }
           `mv Fortville_Silicom_Intel /var/tmp/`;
    }
}  ## end sub install_nic_firmware

sub install_mstflint_package {

	my $this_subroutine_name = ( caller(0) )[3];

	log_trace " \nBEGIN $this_subroutine_name";

	# Remove binaries from earlier package
	my @files_to_remove = (
			'/usr/local/sbin/mstflint',
			'/usr/local/sbin/mstregdump'
			);

	foreach my $file (@files_to_remove) {
		if (-e $file) {
			unlink $file
				or warn "Failed to remove $file: $!";
		}
	}

	# Extract and setup the new package.
	my $tarball = "mstflint_package.tgz";
	my $target_dir = "/var/mstflint_package";

	die "Package $tarball not found\n" unless -f $tarball;
	`rm -rf $target_dir`;

	mkpath($target_dir) or die "Failed to create $target_dir: $!";

	my $cmd = "tar -xzf $tarball -C $target_dir";
	system($cmd) == 0 or die "$tarball extraction failed: $?";

	my @files;
	my $base = "$target_dir/usr";
	find(
			sub {
			return if -d;
			push @files, $File::Find::name;
			},
			$base
	    );

	foreach my $src (@files) {
		(my $rel_path = $src) =~ s|^\Q$target_dir/\E||;
		my $dest = "/$rel_path";

		if (-e $dest) {
			my $is_link = -l $dest;
			my $link_target = $is_link ? readlink($dest) : '';
			my $abs_src = abs_path($src) || $src;

			if (!$is_link || abs_path($link_target // '') ne $abs_src) {
		}
		}

		my $dest_dir = dirname($dest);
		unless (-d $dest_dir) {
			mkpath($dest_dir) or die "Failed to create $dest_dir: $!";
		}

	# Replace and create symlink
		unlink $dest if -e $dest || -l $dest;
		symlink($src, $dest) or warn "Failed to create symlink $dest: $!";
	}
}  ## end sub install_mstflint_package

sub install_config_wipe {

    my $this_subroutine_name = ( caller(0) )[3];
    my @config_wipe_list = `ls ./config_wipe_*.tgz`;
    my $target_dir = "/flash/.recovery/";
    my $target_platform = `sysctl -n netscaler.hypervised_ns`;
    my $sysid = `sysctl -n netscaler.sysid`;

    log_trace " \nBEGIN $this_subroutine_name";

    if ( $sysid == 35060 || $sysid == 35000 || $sysid == 35030 || $sysid == 35020
        || $sysid == 560060 || $sysid == 560000 || $sysid == 560040
        || $sysid == 520460 || $sysid == 520400) {
        if ( (scalar @config_wipe_list) != 1 ) {
            log_status "\nThere should be only one config_wipe tgz file in the directory. Can't install Config Wipe package. Skipping...";
            return;
        }

        my $config_wipe = $config_wipe_list[0];
        chomp $config_wipe;
        if ( $config_wipe ne "" ) {
            mkdir $target_dir unless -d $target_dir;
            `rm -Rf /flash/.recovery/config_wipe_*`;
            `cp $config_wipe $target_dir`;
            `tar xfpz $config_wipe -C $target_dir 2> ./err_msg`;
            `chmod 744 /flash/.recovery/rc.* 2>> ./err_msg`;
            `chown root:wheel /flash/.recovery/rc.* \\
                              /flash/.recovery/loader \\
                              /flash/.recovery/.new_loader_version 2>> ./err_msg`;
            log_status "\nInstalling Config Wipe package...done";
        }
    }
    log_trace "END $this_subroutine_name";
}  ## end sub install_config_wipe

# Install Open VM Tools
sub install_vmtools {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";
    if ( -f "./open-vm-tools.tgz" ) {
	# start fresh with /var/vmtools
	system("rm -rf /var/vmtools") == 0
	    || ns_die("Error: Failed to remove /var/vmtools ($!)\n");
	system("mkdir -p /var/vmtools") == 0
	    || ns_die("Error: Can't create /var/vmtools ($!)\n");
	system ("tar xzfp open-vm-tools.tgz -C /var/vmtools") == 0
	    || ns_die("Error: Can't un-tar open-vm-tools ($!)\n");
    }
}  ## end sub install_vmtools

sub copy_file {
    my $this_subroutine_name = ( caller(0) )[3];
    my $file1                = $_[0];
    my $file2                = $_[1];

    log_trace " \nBEGIN $this_subroutine_name";

    open( IN, "< $file1" )
      or ns_die("Failed to open $file1 for reading ($!), aborting...\n");
    open( OUT, "> $file2" )
      or ns_die("Failed to open $file2 for writing ($!), aborting...\n");

    $blksize = ( stat IN )[11] || 16384;    # preferred block size?
    log_status "\nCopying $file1 to $file2: ";
    while ( $len = sysread IN, $buf, $blksize ) {
        if ( !defined $len ) {
            next if $! =~ /^Interrupted/;    # ^Z and fg
            ns_die("Read error: $!\n");
        }
        $offset = 0;
        while ($len) {
            defined( $written = syswrite OUT, $buf, $len, $offset )
              or ns_die("Write error: $!\n");
            $len -= $written;
            $offset += $written;
        }
    } ## end while ( $len = sysread IN...

    close(IN);
    close(OUT);
    log_trace "END $this_subroutine_name";
} ## end sub copy_file

sub fipscheck_loaderconf {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    open( LDCONF, "$loaderconf" );
    @ldconf = <LDCONF>;
    close(LDCONF);
    foreach $ldline (@ldconf) {
        if ( $ldline =~ /kern.vm.caviumfips.size/i ) {
            $fips = 1;
        }
    }
    log_trace "END $this_subroutine_name";
} ## end sub fipscheck_loaderconf

sub mlxringcheck_loaderconf {
    my $this_subroutine_name = ( caller(0) )[3];
    my $mlx_ring_entries = "";

    log_trace " \nBEGIN $this_subroutine_name";

    open( LDCONF, "$loaderconf" );
    @ldconf = <LDCONF>;
    close(LDCONF);
    foreach $ldline (@ldconf) {
        if ( $ldline =~ /netscaler\.mlx\..*\.rcv_ring_size/i ) {
            $mlx_ring_entries = $mlx_ring_entries.$ldline;
        }
    }
    chomp $mlx_ring_entries;
    log_trace "END $this_subroutine_name";
    return $mlx_ring_entries;
} ## end sub mlxringcheck_loaderconf

sub vmxnet3_ringcheck_loaderconf {
    my $this_subroutine_name = ( caller(0) )[3];
    my $ring_entries = "";

    log_trace " \nBEGIN $this_subroutine_name";

    open( LDCONF, "$loaderconf" );
    @ldconf = <LDCONF>;
    close(LDCONF);
    foreach $ldline (@ldconf) {
        if ( $ldline =~ /hw\.vmx\..*\.rxd/i ) {
            $ring_entries = $ring_entries.$ldline;
        }
    }
    chomp $ring_entries;
    log_trace "END $this_subroutine_name";
    return $ring_entries;
} ## end sub vmxnet3_ring_entries

sub sysidcheck_loaderconf {
    my $this_subroutine_name = ( caller(0) )[3];
    my $sysid_entries = "";

    log_trace " \nBEGIN $this_subroutine_name";

    open( LDCONF, "$loaderconf" );
    @ldconf = <LDCONF>;
    close(LDCONF);
    foreach $ldline (@ldconf) {
        if ( $ldline =~ /^netscaler\.sysid=/ ) {
            $sysid_entries = $sysid_entries.$ldline;
        }
    }
    chomp $sysid_entries;
    log_trace "END $this_subroutine_name";
    return $sysid_entries;
} ## end sub sysidcheck_loaderconf

sub mod_loaderconf {
    my $this_subroutine_name = ( caller(0) )[3];
    my $sysid = `sysctl -n netscaler.sysid`;
    my $is_simple_gateway = `sysctl -n netscaler.is_simple_gateway`;
    my $vpx_on_cloud = `sysctl -n netscaler.vpx_on_cloud`;
    my $kvm_virtio_mq = `grep hw.vtnet.mq_disable= $loaderconf`;
    my $mlx_ring_entries = "";
    my $vmxnet3_ring_entries = vmxnet3_ringcheck_loaderconf();

    log_trace " \nBEGIN $this_subroutine_name";

    if ( !$fips ) {
        fipscheck_loaderconf();
    }
    $mlx_ring_entries = mlxringcheck_loaderconf();
    # FIPS
    if ( !fips_platform ) {
       fipsplatformcheck_loaderconf();
    }

    $sysid_entry = sysidcheck_loaderconf();

    log_status_no_console "Changing $loaderconf for $kernelname ...\n";
    open( LDCONF, ">$loaderconf" )
      || ns_die("Failed to open $loaderconf ($!), aborting...\n");
    print LDCONF "autoboot_delay=3\n";
    print LDCONF "boot_verbose=0\n";
    print LDCONF "kernel=\"/$kernelname\"\n";
    print LDCONF "vfs.root.mountfrom=\"ufs:/dev/md0c\"\n";
	if ($sysid == 450000 || $sysid == 450030 || $sysid == 450140 ||
	    $sysid == 450141 ||	$sysid == 450142 || $sysid == 450143 ||
	    $sysid == 450144 || $sysid == 450145 || $sysid == 450050 ||
	    $sysid == 450060 || $sysid == 450062 || $sysid == 450063 ||
	    $sysid == 450064 || $sysid == 450065 || $sysid == 450040 ||
	    $sysid == 450110 || $sysid == 450080 || $sysid == 450081 ||
	    $sysid == 450082 || $sysid == 450083 || $sysid == 450084 ||
	    $sysid == 450085 || $sysid == 450086 || $sysid == 450087 ||
	    $sysid == 450088 || $sysid == 450089 ||
	    $sysid == 450070 || $sysid == 450160 || $sysid == 450170 ||
	    $sysid == 450180 || $sysid == 450190 || $sysid == 450071 ||
	    $sysid == 450001 || $sysid == 450093 || $sysid == 450092 ||
		$sysid == 450096 || $sysid == 450097 || $sysid == 450098 ||
		$sysid == 450099 || $vpx_on_cloud == 3 || $sysid == 450100 ||
		$sysid == 450101 || $sysid == 450102 ) {
        # For NS VPX on Xen we need console on both serial and vga
		print LDCONF "console=\"vidconsole,comconsole\"\n";
    }
    if ($fips) {
        print LDCONF "kern.vm.caviumfips.size=134217728\n";
    }
    # Ensure loader.conf has the right value when upgrading simple gateway appliance
    if ($is_simple_gateway == 1) {
        print LDCONF "netscaler.is_simple_gateway=1\n"
    }

    # Ensure loader.conf retains previous virtio multiqueue sysctl value
    if ($kvm_virtio_mq) {
        print LDCONF "$kvm_virtio_mq";
    }

    # Ensure loader.conf retains previous sysid entries which is critical for
    # Disk Encryption
    if (length $sysid_entry) {
        print LDCONF "$sysid_entry\n"
    }

    if (length $mlx_ring_entries) {
        print LDCONF "$mlx_ring_entries\n"
    }

    if (length $vmxnet3_ring_entries) {
        print LDCONF "$vmxnet3_ring_entries\n"
    }
    # FIPS
    # Preserve FIPS platform setting
    if ($fips_platform == 1) {
       print LDCONF "netscaler.fips_platform=1\n"
    }

    close(LDCONF);
    log_trace "END $this_subroutine_name";
} ## end sub mod_loaderconf

sub mod_loaderconflocal {
    my $this_subroutine_name = ( caller(0) )[3];
    my $loaderconflocal = "/flash/boot/loader.conf.local";
    my $sysid = `sysctl -n netscaler.sysid`;

    log_trace " \nBEGIN $this_subroutine_name";

    # Disable uart1 on MPX-16000/MPX-16000T
    # hint.uart.1.disabled=1
    if (int($sysid/1000) == 560) {
        my $name = 'hint.uart.1.disabled';
        # Check if it's already there
        if (system(qq(grep -q '^\s*$name="*1"*\s*\$' $loaderconflocal 2>/dev/null))) {
            # Not found. Need to add it.
            log_status "Disabling uart1 on MPX-16000/MPX-16000T";
            if (system("echo >>$loaderconflocal; echo $name=1 >>$loaderconflocal")) {
                log_status "Failed to disable uart1";
            }
        }
    }

    log_trace "END $this_subroutine_name";
} ## end sub mod_loaderconflocal

sub prompt_epaProfileWarning()
{
	local $v = $vers;
	unless($v =~ m/^(10\.1)\S+(\.e)$/) {
		local $epa_count = `grep -i -c 'add vpn epaprofile' /nsconfig/ns.conf`;
		if($epa_count > 0)
		{
			print "\n***************************************** WARNING *****************************************\n\n";
			print "Advanced EPA profiles have been found in the running NetScaler 10.1.e configuration.\n";
			print "The Advanced EPA feature has changed in later versions of NetScaler requiring reconfiguration of the policy expressions for this feature after an upgrade.\n";
			print "A copy of the running 10.1.e configuration has been saved to /var/ns.conf.deprecated-AEPA.\n\n";
			print "*******************************************************************************************\n";
    			log_trace " \nAdvanced EPA was found to be configured";
		}
        	system("cp /nsconfig/ns.conf /var/ns.conf.deprecated-AEPA");
	}
}

#find old 512/1024bit ns-server certificate/key
sub find_old_server_cert {
	# All new images generate 2K server certificate,
	# however if NS is having any older service certificate, it'll not generate.
	# For this we are removing older 512bit or 1024bit server certificate during upgrade
	# and reboot will take care of generating new certificate.

	my $ns_server_cert = "/nsconfig/ssl/ns-server.cert";

	my $key_sz = `openssl x509 -in $ns_server_cert -noout -text`;
	if (!$key_sz){
		$old_ns_cert = 1;
		#Give warning about certificate file not present or empty
		log_status "openssl output is empty, ns-server.cert is not present or empty, re-generating";
	}
	else{
		$key_sz=~/Key\s*:\s*\((\d+)\s*bit\s*\)/;

		if (!defined $1){
			$old_ns_cert = 1;
			#In case of change in openssl output, re-generate internal certificate
			log_status "Not able to determine size of ns-server.cert, re-generating";
		}
		elsif ($1 <= 1024){
			if ($key_sz =~ /NetScaler Generated Certificate/){
				$old_ns_cert = 1;
				log_status "ns-server.cert size is less than or equal to 1024, re-generating";
			}
			else{
				#Print log for certifcate not generated on Netscaler and size is less than or equal to 1024.
				log_status "ns-server.cert not generated on Netscaler and size is less than or equal to 1024, skipping re-generation";
			}
		}
	}
}

sub print_secure_rpc_on {

  if (!(-e $rpcs_conf)) {
    check_and_reset_secure_rpc();
    print
    "\n
        ###################################################################################
        #  Secure option for all the RPC nodes turns ON or OFF based on tlsv12            #
        #  ENABLED or DISBALED after reboot.                                              #
        #                                                                                 #
        #  If Secure option is turned ON, it secures the communication between the ADC    #
        #  nodes in the HA, Cluster, and GSLB deployments, which use  port number 3008.   #
        #  Unblock port number 3008 if it is blocked by firewall between ADC nodes        #
        #  before proceeding.                                                             #
        #                                                                                 #
        #  If Secure option is turned OFF, Configuration sync and propagation             #
        #  will be done unsecured way.                                                    #
        #                                                                                 #
        #  You can change this option any time using the command line                     #
        #  interface or the configuration utility. See the documentation for              #
        #  more details.                                                                  #
        #                                                                                 #
        ###################################################################################
     \n\n";
  }
}

sub prompt_reboot {
    my $this_subroutine_name = ( caller(0) )[3];
    $log_flag = 0;
    $ret = `/netscaler/nsapimgr -d allvariables | grep clversion`;
    if ( $ret =~ /clversion:.*\d+/ ) {
        $log_flag = 1;
    }

    if ( $options{n} ) {
        if ( $log_flag ) {
            log_status "Reboot cancelled. Please ignore below message if it is a standalone or HA system.\nIf it is a cluster system and if it is not the last node to be upgraded, please manually change cluster version before reboot using below shell command.\n/netscaler/nsapimgr -ys clversion=$clversion\n";
	    }
	exit(0);
    }
    elsif ( $flag_Y || $options{y} ) {
        if ($old_ns_cert == 1) {
            `touch /nsconfig/ssl/.gen_new_cert`;
        }
        if ( $log_flag ) {
            `/netscaler/nsapimgr -ys clversion=$clversion`;
            sleep(5);
        }
        # ADM / SVM parse console output for 'Rebooting' as part of ADC upgrade, changing this log will result in breakage of backward compatibility
        log_status "Rebooting ...\n";
        `/sbin/reboot`;
    }
    else {
	if ( $gui_flag ) {
            if ( $log_flag ) {
                `/netscaler/nsapimgr -ys clversion=$clversion`;
                sleep(5);
            }
        print "\nInstallation has completed.\n\nReboot is required for configuration changes to take effect.";
		exit 10;
	}
        print "\nInstallation has completed.\n\nReboot NOW? [Y/N] ";
        $ans = read_yesno_input_from_STDIN();
        if ( $ans =~ /y/i ) {
            if ($old_ns_cert == 1) {
                `touch /nsconfig/ssl/.gen_new_cert`;
            }
            if ( $log_flag ) {
                `/netscaler/nsapimgr -ys clversion=$clversion`;
                sleep(5);
            }
            log_status "Rebooting ...\n";
            `/sbin/reboot`;
        } else {
            if ( $old_ns_cert == 1 ) {
                `touch /nsconfig/ssl/.gen_new_cert`;
            }
            if ( $log_flag ) {
                log_status "Reboot cancelled. Please ignore below message if it is a standalone or HA system.\nIf it is a cluster system and if it is not the last node to be upgraded, please manually change cluster version before reboot using below shell command.\n/netscaler/nsapimgr -ys clversion=$clversion\n";
            }
        }
    } ## end else [ if ( $options{y} )
    log_trace "END $this_subroutine_name";
} ## end sub prompt_reboot

sub move_file {
    my $this_subroutine_name = ( caller(0) )[3];
    my $file                 = $_[0];

    log_trace " \nBEGIN $this_subroutine_name";

    $newfile = $file . ".old";
    log_status "Moving file $file to $newfile...\n";
    move( $file, $newfile )
      || ns_die("Error: Failed to move $file to $newfile ($!)\n");
    log_trace "END $this_subroutine_name";
} ## end sub move_file

sub setup_paths {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    log_status_no_console "Checking directories ...";
    foreach $elem (@dirs) {
        move_file($elem) if -f $elem;
        if ( stat($elem) == NULL ) {
            log_status_no_console "$elem does not exist, creating\n";
            if ( mkdir( $elem, 0755 ) != 1 ) {
                ns_die("Failed to create $elem ($!), aborting\n");
            }
        }
    }
    log_trace "END $this_subroutine_name";
} ## end sub setup_paths

sub statfs {
    my $this_subroutine_name = ( caller(0) )[3];
    my $path                 = $_[0];
    my @statbuf              = split( /\n/, `df -k $path` );
    my @kernstats            = stat("./$kernel");
    my @stats                = split( /\s+/, $statbuf[1] );
    my $kernsize             = int( $kernstats[7] / 1000 );

    log_trace " \nBEGIN $this_subroutine_name";

    log_status_no_console "Size of kernel $kernel is $kernsize kilobytes";
    log_status_no_console
      "Available space on $path filesystem is $stats[3] kilobytes";

    if ( $stats[3] < $kernsize ) {
        log_status
"Available space on $path filesystem is insufficient to install $kernel\n";

	if ( $gui_flag ) {
            log_status "Error: No space left on $path filesystem, aborting installation...\n";
            unlink("/$kernel");
	    exit 11;
	}
	ns_die(
"Error: No space left on $path filesystem, aborting installation...\n"
            );
    } ## end if ( $stats[3] < $kernsize)

    my $varoutstr = `df -k $varstr | grep $varstr`;
    my @varoutlst = split( " ", $varoutstr );
    my $varfree   = $varoutlst[3];
    log_status_no_console "Available space on $varstr is $varfree kilobytes";

    if ( $varfree < $min_var_freespace ) {
        log_status
"  $varfree 1k blocks free space on $varstr is insufficient to operate the Netscaler\n";
	if ( $gui_flag ) {
            log_status "Error: Please free space on $varstr and then re-install. You may want to examine /var/log or in /var/netscaler/nsbackup.\n";
            unlink("/$kernel");
	    exit 14;
	}
        ns_die(
"Error: Please free space on $varstr and then re-install. You may want to examine /var/log or in /var/netscaler/nsbackup.\n"
        );
    }
    log_trace "END $this_subroutine_name";
} ## end sub statfs

sub ns_die {
    log_status "@_\n";
    unlink("/$kernel");
    exit(-1);
}

sub print_banner {
    print "\n";
    log_status "installns version ($vers) kernel ($kernel)\n";

    print "  The Netscaler version $vers checksum file is located on \n";
    print
"  http://www.mycitrix.com under Support > Downloads > Citrix NetScaler.\n";
    print
"  Select the Release $vers link and expand the \"Show Documentation\" link\n";
    print "  to view the SHA2 checksum file for build $vers.\n";

    check_enhanced_upgrade();

    check_sysid();

	check_vpx();

    repart_swap_var();

    my $sysid = `sysctl -n netscaler.sysid`;
    if (($sysid != 450020) || ($sysid != 450021)) {
	install_Bootloader();
    }

    print
"\n  There may be a pause of up to 3 minutes while data is written to the flash.\n";
    print "  Do not interrupt the installation process once it has begun.\n\n";
	if (!($gui_flag)) {
        print "Installation will proceed in 5 seconds, CTRL-C to abort\n";
		sleep(5);
	}
    log_status "Installation is starting ...";
} ## end sub print_banner

sub print_help {
    print "
Usage:
      -F FIPS install
      -h Help
      -Y Answer Yes to everything
      -y Force Reboot
      -n Don't Reboot
      -c Force Clean up
      -N Don't check ns.conf
      -G No curses
      -L Enable CallHome
      -A Enable Citrix ADM Service Connect
      -e Upgrading from\\to \"enhancement\" build
      -R Resize swap and \/var. all data on \/var will be erased
      -D Delete all signature files and associated kernel images
      -a Advance option to select ns.conf explicitly during installation
";
    exit(0);
} ## end sub print_help

sub read_until_newline($) {
    my $in = shift;
    my $char;
    my $Done = 0;

    do {
        sysread( $in, $char, 1 );
        $Done = ( $char == '\n' );
    } until ( $Done != 0 );
} ## end sub read_until_linefeed($)

sub read_yesno_input_from_STDIN() {
	my $input;
	while(1) {
		$input = <STDIN>;
		chomp $input;
		last if $input =~ /^[yYnN]$/;
		print "\nPlease enter one of [yYnN]: ";
	}
	return $input
}

sub read_until_eof($) {
    my $in = shift;

    sysseek( $in, 0, SEEK_END );
} ## end sub read_until_eof($)

sub Bootloader_Announcement() {
    local $ans;

    log_status "\n  A new FreeBSD bootloader installation is required.\n";
    printf("  Many files in /flash/boot will be overwritten.\n");
    printf("  /flash/boot/defaults/loader.conf will be moved\n");
    printf("  to /flash/boot/defaults/loader.conf.old\n");

    if ($flag_Y) {
	return 1;
    }

    printf("\n  One can either complete the new bootloader installation\n");
    printf("  or cancel installing the new NetScaler update\n");
    printf("\n  Do you wish to continue bootloader installation? [Y/N] ");

    $ans = read_yesno_input_from_STDIN();

    if ( $ans eq 'y' || $ans eq 'Y' ) {
        return 1;
    }

    exit 1;
} ## end sub Bootloader_Announcement()

sub unpack_MANIFEST_SUMS() {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    system("tar xzf ${full_pkgtgz} ./MANIFEST ./MD5SUMS > /dev/null 2>&1") == 0
      || die "unable to unpack MANIFEST and MD5SUMS";

    open( S, "<MD5SUMS" ) || die "unable to open MD5SUMS";
    $iPkgSums = 0;
    while (<S>) {
        $PkgSums[$iPkgSums] = $_;
        $iPkgSums++;
    }
    close S;
    log_trace "END $this_subroutine_name";

} ## end sub unpack_MANIFEST_SUMS()

sub generate_NewSums() {
    my $this_subroutine_name = ( caller(0) )[3];

    log_trace " \nBEGIN $this_subroutine_name";

    open( M, '<MANIFEST' ) || die "unable to open MANIFEST";
    $iNewSums = 0;
    while (<M>) {
        $file = $_;
        $NewSums[$iNewSums] = `cd /flash/boot ; (sha256 $file) 2> /dev/null`;

# print "PkgSums[$iNewSums]=$PkgSums[$iNewSums]  NewSums[$iNewSums]=$NewSums[$iNewSums]\n";

        if ( $PkgSums[$iNewSums] ne $NewSums[$iNewSums] ) {
            $bootloader_OverWrite = 1;
        }
        $iNewSums++;
    } ## end while (<M>)
    close M;
    log_trace "END $this_subroutine_name";

} ## end sub generate_NewSums()

sub install_Bootloader {
    my $this_subroutine_name = ( caller(0) )[3];
    my $is_nCore = `sysctl -n netscaler.nCore`;

# if it is nCore, we do not need to install a new bootloader.
    if ( $is_nCore == 1 ) {
        return 0;
    }

    log_trace " \nBEGIN $this_subroutine_name";

    $bootloader_tgz    = "bootloader.tgz";
    $bootdir           = "/flash/boot";
    $bootloader_pkgdir = `pwd`;
    chomp($bootloader_pkgdir);
    $full_pkgtgz = "$bootloader_pkgdir" . "/" . "$bootloader_tgz";

    $iPkgSums = 0;
    @PkgSums;

    $iNewSums = 0;
    @NewSums;

    $bootloader_OverWrite = 0;

    if ( $variant ne "v" ) {
	return 0;
    }

    if ( -f ${bootloader_tgz} ) {
        unpack_MANIFEST_SUMS();
        generate_NewSums();

        if ( $bootloader_OverWrite > 0 ) {
            Bootloader_Announcement();

            if ( -f "/flash/boot/defaults/loader.conf" ) {
                system(
"mv /flash/boot/defaults/loader.conf /flash/boot/defaults/loader.conf.old"
                  ) == 0
                  || die
"Error moving /flash/boot/defaults/loader.conf to /flash/boot/defaults/loader.conf.old";
            }

            log_status_no_console "Unpacking new bootloader in ${bootdir}\n";
            system(
"cd ${bootdir} ; tar xzf ${full_pkgtgz} --exclude MANIFEST --exclude MD5SUMS "
              ) == 0
              || die "Error unpacking Bootloader";
        } ## end if ( $bootloader_OverWrite...
    } ## end if ( -f ${bootloader_tgz...
    log_trace "END $this_subroutine_name";

} ## end sub install_Bootloader

sub check_swap_var_size() {

    my $ans;

    my $var_df;
    my $var_dev;
    my $var_slice;
    my $var_part;
    my $var_letter;

    my $unamer, $graidl;
    my $is114kernel=0, $isRaid=0;

    my $disk_size,    $disk_offset;
    my $recover_size, $recover_offset;
    my $swapb_dev,    $swapb_size,   $swapb_offset;
    my $swapg_dev,    $swapg_size=0,   $swapg_offset=0;
    my $var_size,     $var_offset;

    my $target_var_size,  $target_var_offset;
    my $target_swapb_size, $target_swapb_offset;
    my $target_swapg_size, $target_swapg_offset;

    my $target_swapb_blocks   = 8 * 1024 * 1024 * 1024 / 512;     #  8 GB
    my $target_swapg_blocks   = 32 * 1024 * 1024 * 1024 / 512;    # 32 GB
    my $this_subroutine_name = ( caller(0) )[3];

    my $NeedNewDiskLabel=0;

    log_trace " \nBEGIN $this_subroutine_name";

    # Get freebsd version
    chomp($unamer = `uname -r`);
    if ($unamer =~ /11\.4\-NETSCALER/) {
        $is114kernel = 1;
        # RAID check
        chomp($graidl = `graid list`);
        if ($graidl ne "") {
            $isRaid = 1;
        }
    }

    if ($debug_print) {
        log_status "uname=$unamer\n";
        log_status "graid=\n$graidl\n";
    }

    # Get the /var device
    $var_df = `df | grep var`;
    chomp($var_df);
    if ($debug_print) {
        log_status "var_df=${var_df}\n";
    }

    if ($is114kernel) {
        if ($isRaid) {
            $var_df =~ m/^(\/dev\/raid\/r[0-9])/;
            $var_dev = $1;

            $var_df =~ m/^(\/dev\/raid\/..s1)/;
            $var_slice = $1;

            $var_df =~ m/^(\/dev\/raid\/..s1.)/;
            $var_part = $1;

            $var_df =~ m/^\/dev\/raid\/..s1(.)/;
            $var_letter = $1;
        }
		else {
            $var_df =~ m/^(\/dev\/[ad][ad]a?[0-9])/;
            $var_dev = $1;

            $var_df =~ m/^(\/dev\/[ad][ad]a?[0-9]s1)/;
            $var_slice = $1;

            $var_df =~ m/^(\/dev\/[ad][ad]a?[0-9]s1.)/;
            $var_part = $1;

            $var_df =~ m/^\/dev\/[ad][ad]a?[0-9]s1(.)/;
            $var_letter = $1;
        }
    }
    else {
        $var_df =~ m/^(\/dev\/[ad][ad][0-9])/;
        $var_dev = $1;

        $var_df =~ m/^(\/dev\/...s1)/;
        $var_slice = $1;

        $var_df =~ m/^(\/dev\/...s1.)/;
        $var_part = $1;

        $var_df =~ m/^\/dev\/...s1(.)/;
        $var_letter = $1;

    }

    if ($debug_print) {
        log_status "var_df=$var_df\n";
        log_status "var_dev=$var_dev\n";
        log_status "var_slice=$var_slice\n";
        log_status "var_part=$var_part\n";
        log_status "var_letter=$var_letter\n";
    }

    # swap is always slice b (8GB/Classic) & g (32GB/nCore):
    my $disklabel = `disklabel ${var_slice}`;
    if ($debug_print) { log_status "disklabel=\n${disklabel}\n"; }
    $swapb_dev = "${var_dev}" . "s1b";
    $swapg_dev = "${var_dev}" . "s1g";

    if ($disklabel =~ /\s+a:/) {
        $disklabel =~ m/\s+a:\s+(\d+)\s+(\d+)\s+/;
        $recover_size   = $1;
        $recover_offset = $2;
    }

    if ($disklabel =~ /\s+b:/) {
        $disklabel =~ m/\s+b:\s+(\d+)\s+(\d+)\s+/;
        $swapb_size   = $1;
        $swapb_offset = $2;
    }

    if ($disklabel =~ /\s+c:/) {
        $disklabel =~ m/\s+c:\s+(\d+)\s+(\d+)\s+/;
        $disk_size   = $1;
        $disk_offset = $2;
    }

    if ($disklabel =~ /\s+${var_letter}:/) {
        $disklabel =~ m/\s+${var_letter}: (\d+)\s+(\d+)\s+/;
        $var_size   = $1;
        $var_offset = $2;

    }

    if ($disklabel =~ /\s+g:/) {
        $disklabel =~ m/\s+g:\s+(\d+)\s+(\d+)\s+/;
        $swapg_size   = $1;
        $swapg_offset = $2;
    }

    if ($debug_print) {
        log_status "disklabel=${disklabel}\n";

        log_status "disk_size=${disk_size}\n";
        log_status "disk_offset=${disk_offset}\n";

        log_status "recover_size=${recover_size}\n";
        log_status "recover_offset=${recover_offset}\n";

        log_status "swapb_dev=${swapb_dev}\n";
        log_status "swapb_size=${swapb_size}\n";
        log_status "swapb_offset=${swapb_offset}\n";

        log_status "var_dev=${var_dev}\n";
        log_status "var_size=${var_size}\n";
        log_status "var_offset=${var_offset}\n";

        log_status "swapg_dev=${swapg_dev}\n";
        log_status "swapg_size=${swapg_size}\n";
        log_status "swapg_offset=${swapg_offset}\n";
    } ## end if ($debug_print)

    if ( $swapg_size < $target_swapg_blocks ) {
	$NeedNewDiskLabel = 1;
    }

    $target_swapb_size   = $target_swapb_blocks;
    $target_swapg_size   = $target_swapg_blocks;

    $target_swapb_offset = $swapb_offset;

    $target_var_size   = $disk_size - $recover_size - $target_swapb_size - $target_swapg_size;
    $target_var_offset = $target_swapb_offset + $target_swapb_size;

    $target_swapg_offset = $target_var_offset + $target_var_size;

    if ( $NeedNewDiskLabel == 1) {
        my $target_disklabel = $disklabel;

        $target_disklabel =~ s/${swapb_size}/${target_swapb_size}/;
        $target_disklabel =~ s/${swapb_offset}/${target_swapb_offset}/;
        $target_disklabel =~ s/${var_size}/${target_var_size}/;
        $target_disklabel =~ s/${var_offset}/${target_var_offset}/;


	if ( $swapg_size != 0 ) {
		$target_disklabel =~ s/${swapg_size}/${target_swapg_size}/;
		$target_disklabel =~ s/${swapg_offset}/${target_swapg_offset}/;
	} else {
		$target_disklabel = $target_disklabel . "\n  g: $target_swapg_size $target_swapg_offset      swap\n";
	}

        print "
  A re-sizing of swap and /var is required.
  All data on /var will be erased.
";

  if ($flag_Y == 1) {
	print "option Y given, answer is YES";
	$ans = 'y';
  } elsif ($resize_flag){
	$ans = 'y';
  } else {
    if ( !$gui_flag ) {
	print "Please reply 'N' if you need to save data from /var.

 	Do you wish to continue? [Y/N] ";

	$ans = read_yesno_input_from_STDIN();

    } else {
	    exit 13;
    }
  }

        if ( $ans eq 'y' || $ans eq 'Y' ) {

            if ( $debug_print == 1 ) {
                open( ORIG_DISKLABEL, ">$orig_disklabel_fn" )
                  || die "Error opening $orig_disklabel_fn";
                print ORIG_DISKLABEL $disklabel;
                close ORIG_DISKLABEL;
            }

            open( NEW_DISKLABEL, ">$new_disklabel_fn" )
              || die "Error opening $new_disklabel_fn";
            print NEW_DISKLABEL $target_disklabel;
            close NEW_DISKLABEL;

            if ($debug_print) { log_status "$target_disklabel\n"; }
        } ## end if ( $ans eq 'y' || $ans...
        else {
            log_status "Aborting installation....";
            exit 1;
        }
    } ## end if ( $swap_size < $target_swap_blocks)
    log_trace "END $this_subroutine_name";

} ## end sub check_swap_var_size()

sub repart_swap_var() {
    my $this_subroutine_name = ( caller(0) )[3];
	my $sysid = `sysctl -n netscaler.sysid`;

    log_trace " \nBEGIN $this_subroutine_name";

    # change swap size on ONLY 64bit nCore/MCNS/VMPE systems
    if ( $variant ne "v" ) { return 0; }

	 # Change swap size ONLY on NON-VPX systems
	if ($sysid == 450000 || $sysid == 450010 || $sysid == 450020 ||
	    $sysid == 450030 || $sysid == 450140 || $sysid == 450141 ||
	    $sysid == 450142 || $sysid == 450143 || $sysid == 450144 ||
	    $sysid == 450145 || $sysid == 450050 || $sysid == 450060 ||
	    $sysid == 450062 || $sysid == 450063 || $sysid == 450064 ||
	    $sysid == 450065 || $sysid == 450040 || $sysid == 450110 ||
	    $sysid == 450080 || $sysid == 450081 || $sysid == 450087 ||
	    $sysid == 450082 || $sysid == 450083 || $sysid == 450084 ||
	    $sysid == 450085 || $sysid == 450086 || $sysid == 450087 ||
	    $sysid == 450088 || $sysid == 450089 || $sysid == 450070 ||
	    $sysid == 450160 || $sysid == 450170 || $sysid == 450180 ||
	    $sysid == 450190 || $sysid == 450071 || $sysid == 450001 ||
	    $sysid == 450011 || $sysid == 450021 || $sysid == 450092 ||
		$sysid == 450093 || $sysid == 450096 || $sysid == 450097 ||
		$sysid == 450098 || $sysid == 450099 || $sysid == 450100 ||
		$sysid == 45101  || $sysid == 450101 || $sysid == 450102 )
		{ return 0; }

    local $new_disklabel_fn  = "/flash/nsconfig/.new_disklabel";
    local $orig_disklabel_fn = "/flash/nsconfig/.orig_disklabel";

    check_swap_var_size();

    log_trace "END $this_subroutine_name";

} ## end sub repart_swap_var()

# Handle Interrupts
sub interrupt_int {
    my ($signal) = @_;
    print "\nCaught user interrupt ($signal).\n";
    if ( $kernel_copied eq "no" ) {
        print
"WARNING: The kernel has not been copied successfully. Your Netscaler may not be bootable  !!\n";
    }

    print "\nAre you sure you want to quit ? [Y/N] ";

    # Do not call read_yesno_input_from_STDIN() or read from <STDIN> in the
    # signal handler.

    # Set STDIN to non-blocking mode
    my $flags;
    eval {
        $flags = fcntl(STDIN, F_GETFL, 0);
    };
    if ($@) {
        log_state "ABORTED_INTERRUPT_GET_STDIN_FLAGS\n";
        ns_die("ABORTED INSTALLATION: Coudln't get flags for STDIN: $@\n");
    }
    eval {
        fcntl(STDIN, F_SETFL, $flags | O_NONBLOCK);
    };
    if ($@) {
        log_state "ABORTED_INTERRUPT_SET_STDIN_FLAGS\n";
        ns_die("ABORTED INSTALLATION: Couldn't set flags for STDIN: $@\n");
    }

    my $input = '';
    while (1) {
        my $char;
        if (sysread(STDIN, $char, 1) > 0) {
            last if $char eq "\n";
            $input .= $char;
        } elsif ($! != EAGAIN) {
            log_state "ABORTED_INTERRUPT_sysread_STDIN\n";
            ns_die("ABORTED INSTALLATION: Error reading from STDIN: $!\n");
        }
    }

    # Retore the previous flags for STDIN
    eval {
        fcntl(STDIN, F_SETFL, $flags);
    };
    if ($@) {
        log_state "ABORTED_INTERRUPT_RESTORE_STDIN_FLAGS\n";
        ns_die("ABORTED INSTALLATION: Couldn't restore flags for STDIN: $@\n");
    }

    if ($input =~ /y/i) {
        log_state "ABORTED_INTERRUPT\n";
        ns_die("ABORTED INSTALLATION DUE TO USER INTERRUPT\n");
    }
    print "Continuing...\n";
}

sub interrupt_other {
    my ($signal) = @_;
    log_state "ABORTED_OTHER: $signal";
    ns_die("ABORTED INSTALLATION DUE TO SIGNAL $signal\n");
}

sub check_install_ns_enhanced {
    $vers =~ m/\.(e)$/;
    my $ns_ver_installns_enhanced = $1;

    if ( $ns_ver_installns_enhanced eq "e" ) {
        return 1;
    }
    return 0;
}

sub check_running_ns_enhanced {
    my $ns_ver_ln = `/netscaler/nscli -U %%:.:. sh ver`;
    my $ns_ver_num;
    my $ns_ver_build;
    chomp($ns_ver);

    $ns_ver_ln =~ m/ NS([0-9]+\.[0-9]): Build/;
    $ns_ver_num = $1;

    if ( $ns_ver_num >= "9.1") {
        $ns_ver_ln =~ m/Build ([0-9]+\.[0-9]+[\.e]*\.[ncl]+), /;
        $ns_ver_build = $1;

        $ns_ver_build =~ m/\.(e)\.[ncl]+$/;
        $ns_ver_build_enhanced = $1;

        if ( $ns_ver_build_enhanced eq "e" ) {
            return 1;
        }

    }
    return 0;
}

sub check_enhanced_upgrade {
    my $installns_enhanced = check_install_ns_enhanced();
    my $running_ns_enhanced = check_running_ns_enhanced();
	my $ask = 0;

    if ( $running_ns_enhanced == 0 && $installns_enhanced == 1) {
        print "\nWarning: you are upgrading from \"maintenance\" to \"enhancement\" software version.";
		$ask = 1;
    }
    if ( $running_ns_enhanced == 1 && $installns_enhanced == 0) {
        print "\nWarning: you are upgrading from \"enhancement\" to \"maintenance\" software version.";
		$ask = 1;
    }

	if (!$ask) {
		return 1;
	}

    if ($flag_Y) {
        return 1;
    }

    if ( !$gui_flag ) {

        if ( !$enh_flag ) {
            print " Do you want to continue? [Y/N] ";

            $ans = read_yesno_input_from_STDIN();

            if ( $ans eq 'y' || $ans eq 'Y' ) {
                return 1; # YES - do the install
             }
             exit 1 ; # NO - don't do the install
        } else {
            return 1; # YES - do the install
        }
    } else {
        if ( !$enh_flag ) {
            exit 15 ; # NO - don't do the install
        } else {
            return 1; # YES - do the install
        }
    }
}

### Subroutines for cleaning up ntp configuration start here ####

# If $file exists, and contains any references to $reg_ex
# move $file to $backup_file, and return true (1)
# Otherwise return false (0)
sub fix_file_setup ($$$) {
    my ($file, $backup_file, $reg_ex) = @_;

    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";

    my $ret;
    my $fix_needed = 0;
    if (-e $file) {
	$fix_needed = `grep -c $reg_ex $file`;
    }
    if ($fix_needed == 0) {
	return 0;
    }
    $ret = rename $file, $backup_file;
    if ($ret != 1) {
	log_status_no_console "Unable to upgrade $file to remove references to $reg_ex";
	return 0;
    }
    return 1;
}

# If $monitrc exists, and contains any references to ntp
# move $monitrc to $monitrc_backup,
# and then copy it to $monitrc without the lines relating to ntp, including
# expected comments etc.
# Basically we're expecting a pattern like
#   [comments] check-process-line start-line stop-line [if-line] white-space
# One (or more) block(s) like this may contain ntp, in which case we delete
#   the whole block. Any of check-process etc. may be commented out; we don't
#   care
# It would be nice to get rid of /nsconfig/monitrc entirely, if
# only existed for ntp, but since we can't be sure, leave it present
# (side effect - if /etc/monitrc has change between releases, the user will
# still have the old behaviour.)
sub fix_monitrc () {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";

    my $monitrc = "/nsconfig/monitrc";
    my $monitrc_backup = "$monitrc.prev";
    my $ret;
    my $fix_needed = fix_file_setup($monitrc, $monitrc_backup, "ntp");

    if ($fix_needed == 0) {
	return;
    }

    $ret = open (OLD, $monitrc_backup);
    if (!$ret) {
	log_status_no_console "Unable to upgrade $monitrc to remove references to ntp";
	return;
    }
    $ret = open (NEW, ">$monitrc");
    if (!$ret) {
	log_status_no_console "Unable to upgrade $monitrc to remove references to ntp";
	close OLD;
	return;
    }

    my $line;
    my $in_ntp = 0;
    while ($line = <OLD>) {
	if ($line =~ /ntp/) {
	    $in_ntp = 1;
	}

	if ($in_ntp) {
	    # Seen "ntp", we want a 'check' line
	    if ($line =~ /check process/) {
		if (not $line =~ /ntpd/) {
		    # Not our line, better stop processing as ntp
		    $in_ntp = 0;
		}
	    } elsif (not $line =~ /\S/) {
		# Nothing but whitespace - end of ntp block
		# stop processing as ntp _after_ this line
		$in_ntp = 0;
		next;
	    }
	}
	if (not $in_ntp) {
	    print NEW $line;
	}
    }
    close OLD;
    close NEW;
    # Monit demands 0700 (or stricter) permissions on $monitrc
    chmod (0700, $monitrc);
    log_trace "END $this_subroutine_name";
}

# If $rc_conf exists, and contains any references to ntp
# move $rc_conf to $rc_conf_backup,
# and then copy it to $rc_conf without the lines relating to ntp, including
# expected comments etc.
# Basically we're expecting a pattern like
#   [comments] ntpd_enable_line [ntp_disable_line] [ntp_flags_line]
#   Any of ntpd_enable_line etc. may be commented out; we don't care
# return 1 IFF we found a non-commented out ntpd_enable line that set
#   the value to YES ; otherwise return 0
# Such a line looks like:
#   ntpd_enable="YES"
sub fix_rc_conf () {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";

    my $rc_conf = "/nsconfig/rc.conf";
    my $rc_conf_backup = "$rc_conf.prev";
    my $ret;
    my $fix_needed = fix_file_setup($rc_conf, $rc_conf_backup, "ntp");

    if ($fix_needed == 0) {
	return 0;
    }

    $ret = open (OLD, $rc_conf_backup);
    if (!$ret) {
	log_status "Unable to upgrade $rc_conf to remove references to ntp at ".__FILE__." line ".__LINE__;
	return 0;
    }
    $ret = open (NEW, ">$rc_conf");
    if (!$ret) {
	log_status "Unable to upgrade $rc_conf to remove references to ntp at ".__FILE__." line ".__LINE__;
	close OLD;
	return 0;
    }
    my $line;
    my $in_ntp = 0;
    my $got_enable = 0;
    my $found_ntp_enabled = 0;
    while ($line = <OLD>) {
	if ($line =~ /ntpd/) {
	    $in_ntp = 1;
	}

	# once we've seen enable, we're out of ntp as soon as we see a line
	# without the ntp token
	if ($got_enable) {
	    if (not $line =~ /ntp/) {
		$in_ntp = 0;
		$got_enable = 0;
	    }
	}

	if ($in_ntp) {
	    # Seen "ntpd", we want an 'ntpd_enable' line
	    if ($line =~ /enable/) {
		if (not $line =~ /ntp/) {
		    # Not our line, better stop processing as ntp
		    $in_ntp = 0;
		    $got_enable = 0;
		} else {
		    $got_enable = 1;
		    # but is it really enabled?
		    if ((not $line =~ /#.*ntpd_enable/)
			and ($line =~ /ntpd_enable.*YES/)) {
			$found_ntp_enabled = 1;
		    }
		}
	    }
	}
	if (not $in_ntp) {
	    print NEW $line;
	}
    }
    close OLD;
    close NEW;
    log_trace "END $this_subroutine_name";
    return $found_ntp_enabled;
}

# Fix up $rc_netscaler
# We will change it
# (1) if it exists, and has a reference to ntp which doesn't use ntpd_ctl
# (2) if it exists, and our $new_enable argument is true (1)
# We will create it
# (3) if it does nto exist, and our $new_enable argument is true (1)
# If we are changing it, we'll mv the existing file to $rc_netscaler_backup
sub fix_rc_netscaler ($) {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";

    my ($new_enable) = @_;
    my $rc_netscaler = "/nsconfig/rc.netscaler";
    my $rc_netscaler_backup = "$rc_netscaler.prev";
    my $new_rc_netscaler_ntp_line = "/bin/sh /etc/ntpd_ctl full_start";
    my $ret;
    my $fix_needed = 0;
    my $mv_needed = 0;
    my $new_style_line_written = 0;

    if (-e $rc_netscaler) {
	my $has_ntp = `grep -c ntp $rc_netscaler` + 0;
	my $has_new_ntp = `grep -c \"$new_rc_netscaler_ntp_line\" $rc_netscaler` + 0;

	# This test is slightly hypersensitive, as it triggers on a commented
	# out ntp line - not something we create, but a customer might
	if (($has_ntp > $has_new_ntp) or ($new_enable and not $has_new_ntp)) {
	    $fix_needed = 1;
	}
	$mv_needed = 1;
    } else {
	$fix_needed = $new_enable;
    }

    if ($fix_needed == 0) {
	return;
    }

    if ($mv_needed) {
	$ret = rename $rc_netscaler, $rc_netscaler_backup;
	if ($ret != 1) {
	    log_status "Unable to upgrade $rc_netscaler to normalize references to ntpd at ".__FILE__." line ".__LINE__;
	    return;
	}

	$ret = open (OLD, $rc_netscaler_backup);
	if (!$ret) {
	    log_status "Unable to upgrade $rc_netscaler to normalize references to ntpd at ".__FILE__." line ".__LINE__;
	    return;
	}

    }

    $ret = open (NEW, ">$rc_netscaler");
    if (!$ret) {
	log_status "Unable to upgrade $rc_netscaler to normalize references to ntpd at ".__FILE__." line ".__LINE__;
	close OLD;
	return;
    }

    if ($mv_needed) {
	# Copy old to new, with replacement if necc.
	my $line;
	while ($line = <OLD>) {
	    if (not $line =~ /ntp/) {
		print NEW $line;
		next;
	    }
	    # process line which had ntp
	    if ($line =~ /#.*ntp/) {
		# just dump comments
		next;
	    }
	    if ($line =~ /$new_rc_netscaler_ntp_line/) {
		# Write the new-style line, and we don't need to add another
		print NEW $line;
		$new_style_line_written = 1;
		next;
	    }
	    # whatever we found was an old style line, replace if it's
	    # the first we've seen
	    if (not $new_style_line_written) {
		print NEW "$new_rc_netscaler_ntp_line\n";
		$new_style_line_written = 1;
	    }

	}
	close OLD;
    }
    if ($new_enable and not $new_style_line_written) {
	print NEW "$new_rc_netscaler_ntp_line\n";
    }
    close NEW;
    log_trace "END $this_subroutine_name";
}

# Given a restrict or server line, in format
#     [#] server|restrict [-6|-4] host_or_ip [lots more stuff]
# Find and return the host or IP
# If the line is commented out (has a # before the host_or_ip) return the
#     empty string
# First argument is the line, second is either "server" or "restrict"
sub parse_out_host($$) {
    my ($line, $keyword) = @_;

    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name - keyword $keyword; line $line";

    my @tokens = split /\s+/, $line;
    my $i;
    my $got_keyword = 0;
    my $host;
    my $rest;
    for ($i = 0; $i < @tokens; $i++) {
	my $token = $tokens[$i];
	if ($token =~ /#/) {
	    return "";
	} elsif ($token =~ /$keyword/) {
	    $got_keyword = 1;
	} elsif ($got_keyword and $token =~ /-6/) {
	    next;
	} elsif ($got_keyword and $token =~ /-4/) {
	    next;
	} elsif ($got_keyword) {
	    # the next thing has to be a host name or ip
	    return $token;
	}
    }
    # short or otherwise malformed line
    return "";
}

# Fix up $ntp_conf
# We will change it
# (1) if it exists, and doesn't have an includefile line
# (2) if it exists, and has servers without restrict lines
# If we are changing it, we'll mv the existing file to $ntp_conf_backup
my $ntp_conf = "/nsconfig/ntp.conf";
sub fix_ntp_conf () {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";
    my $ntp_conf_backup = "$ntp_conf.prev";
    my $new_includefile_line = "includefile /etc/ntp.common.conf";
    my $new_restrict_tail = "nomodify notrap nopeer noquery";
    my $ret;

    if (not -e $ntp_conf) {
	return;
    }

    # Note - this will trigger on a comment, even one that mentions includefile
    # i.e. we won't upgrade an ntp.conf that even mentions includefile in
    # a comment.
    my $has_includefile = `grep -c includefile $ntp_conf`;

    my $servers = `grep server $ntp_conf`;
    my @servers_arr = split /\n/, $servers;
    my $restricts = `grep restrict $ntp_conf`;
    my @restricts_arr = split /\n/, $restricts;

    if ($has_includefile != 0) {
	# If we have at least as many restrict lines as server lines
	# as well as an includefile line, we figure it's already been
	# updated.
	if ($#servers_arr <= $#restricts_arr) {
	    return;
	}
    }

    $ret = rename $ntp_conf, $ntp_conf_backup;
    if ($ret != 1) {
	log_status "Unable to upgrade $ntp_conf at ".__FILE__." line ".__LINE__;
	return;
    }

    $ret = open (OLD, $ntp_conf_backup);
    if (!$ret) {
	log_status "Unable to upgrade $ntp_conf at ".__FILE__." line ".__LINE__;
	return;
    }

    $ret = open (NEW, ">$ntp_conf");
    if (!$ret) {
	log_status "Unable to upgrade $ntp_conf at ".__FILE__." line ".__LINE__;
	close OLD;
	return;
    }

    if ($has_includefile == 0) {
	# Start by putting a good includefile at the start of the file
	print NEW "$new_includefile_line\n";
    }

    # Get the restricts into a hash, so we can recognize servers
    # without matching restricts
    my %restricts_hash;
    foreach my $restrict_line (@restricts_arr) {
	my $host = parse_out_host($restrict_line, "restrict");

	if ($host ne "") {
	    $restricts_hash{$host} = $restrict_line;
	}
    }

    # Copy old to new, with replacement if necc.
    my $line;
    while ($line = <OLD>) {
	# Copy whatever we found
	print NEW $line;
	if ($line =~ /server/) {
	    my $host = parse_out_host($line, "server");
	    if ($host ne "") {
		if (not exists $restricts_hash{$host}) {
			# no matching restrict line, so we need to add it
			print NEW "restrict $host $new_restrict_tail\n";
		}
	    }
	}
    }
    close OLD;
    close NEW;
    log_trace "END $this_subroutine_name";
}

# Handling of ntp changed rather significantly at 11.0, backported to various
# earlier releases. If a system being upgraded already has ntp enabled,
# we want to change their ntp configuration to conform to the new methods.
# (An old-style configuration will have subtle problems on a new-style release.)
# Old config files will saved as /nsconfig/*.prev,
# e.g. /nsconfig/ntp.conf will be saved as /nsconfig/ntp.conf.prev
# Warning: downgrade is not handled automatically.
sub fix_ntp {
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";

    # $version comes from uname -a, format looks like:
    # FreeBSD ns 8.4-NETSCALER-11.0 FreeBSD 8.4-NETSCALER-11.0 #0: Fri Mar 20 14:01:39 PDT 2015     root@sjcdbldbsd8404.eng.citrite.net:/usr/obj/home/build/TOT/usr.src/sys/NS64  amd64
    my ($junk1, $junk2, $ns_name) = split /\s+/, $version;
    my ($junk3, $ns_ver) = split /-NETSCALER-/, $ns_name;
    my ($major, $minor) = split /\./, $ns_ver;

    # This one gives build as well as major and minor
    # Format like: netscaler.version: NetScaler NS11.0: Build 43.1.nc, Date: Mar 20 2015, 14:00:35
    # my $ns_version = `sysctl netscaler.version`;
    # Include this (parsed) in the test below if things ever get more complex
    # (As it is, it's safe to rerun this on OK config files thru all of 11.00
    # even though some builds of 10.1, 10.5, and 11.0 will have new-style
    # NTP configs)

    # Nothing needs to be done if the release is late enough
    if (($major > 11) || (($major == 11) && ($minor > 0))) {
	return;
    }

    return if (! (-e $ntp_conf));

    fix_monitrc();
    my $enabled_in_rc_conf = fix_rc_conf();
    fix_rc_netscaler($enabled_in_rc_conf);
    fix_ntp_conf;
    log_trace "END $this_subroutine_name";
}

sub find_json_response($) {
    my ($sslservice) = @_;
    $start = index($sslservice, "NITRORESP :");
    if ( $start != -1) {
        $sslservice = substr $sslservice, $start+11;
        $end = index($sslservice, "} ] }");
        $sslservice  = substr $sslservice, 0, $end+5;
    }
    return $sslservice;
}

sub is_tlsv12_enabled() {
    my $sslservice = `/netscaler/nsremotexec 127.0.0.1 -exec \"clicmdnitro -clicmd 'show ssl service ' -execute \" 2> /dev/null`;
    my $jsonservice = find_json_response($sslservice);
    my $decodedservice = decode_json($jsonservice);
    my $isTLSv12 = 1;
    my @mysslservices = @{ $decodedservice->{'sslservice'} };
        foreach my $f1 ( @mysslservices ) {
            if (($f1->{"servicename"} =~ /nskrpcs*/) || ($f1->{"servicename"} =~ /nsrpcs*/)) {
                if (exists $f1->{"tls12"}) {
                    if ($f1->{"tls12"} =~ /DISABLED/) {
                        $isTLSv12 = 0;
                        last;
                     }
                } else {
                    if (exists $f1->{"sslprofile"}) {
                         my $sslprofilename = $f1->{"sslprofile"};
                         my $sslprofile = `/netscaler/nsremotexec 127.0.0.1 -exec \"clicmdnitro -clicmd 'show ssl profile $sslprofilename' -execute \" 2> /dev/null`;
                         my $jsonprofile = find_json_response($sslprofile);
                         my $decodedprofile = decode_json($jsonprofile);
                         my @mysslprofiles = @{ $decodedprofile->{'sslprofile'}};
                         foreach my $f2 ( @mysslprofiles ) {
                             if (exists $f2->{"tls12"}) {
                                 if ($f2->{"tls12"} =~ /DISABLED/) {
                                     $isTLSv12 = 0;
                                     return $isTLSv12;
                                 }
                             }
                         }
                     }
                 }
             }
         }
    return $isTLSv12;
}

sub check_and_reset_secure_rpc () {
    my $cmd = `grep \"set ns rpcNode\" /nsconfig/ns.conf`;
    my $indexpos = index($cmd,"-secure");
    if ($indexpos == -1) {
        my $isTLSv12 = is_tlsv12_enabled();
        if ($isTLSv12 == 0) {
            # if there is no secure flag, then make it secure NO in case of TLSV12 disabled
            my $cmd2=`sed -i -e  \"/set ns rpcNode.*/ s/\$/ -secure NO/\" /nsconfig/ns.conf`;
            return 1;
        }
    }
    return 0;
}

sub get_json_from_resp {
    my ($obj) = @_;
    $start = index($obj, "NITRORESP :");
    if ( $start != -1) {
        $obj = substr $obj, $start+11;
        $end = index($obj, "} }");
        $obj  = substr $obj, 0, $end+5;
    }
    return $obj;
}

# Determines if the system is operating in local license mode.
sub is_local_license_mode {
    # Skip for VPX on SDX instances
    chomp(my $sdxvpx = `sysctl -n netscaler.sdxvpx`);
    return 0 if $sdxvpx;

    my $nslicense = `/netscaler/nsremotexec 127.0.0.1 -exec "clicmdnitro -clicmd 'show ns license' -execute " 2> /dev/null`;
    my $jsonnslicense = get_json_from_resp($nslicense);
    my $decodednslicense = decode_json($jsonnslicense);

    my $licmode = $decodednslicense->{"nslicense"}->{"licensingmode"};

    # Continue only if licensingmode is defined and set to "local" or "cpu-local"
    return 0 unless defined $licmode && (lc($licmode) eq "local" || lc($licmode) eq "cpu-local");

    # Continue only if a license file is present. Skip devices with no valid license file (irrespective of expiry).
    my $isstandardlic = $decodednslicense->{"nslicense"}->{"isstandardlic"};
    my $isenterpriselic = $decodednslicense->{"nslicense"}->{"isenterpriselic"};
    my $isplatinumlic = $decodednslicense->{"nslicense"}->{"isplatinumlic"};

    # Check if at least one license type is defined and set to true. If none is set to true, it indicates no valid license file is present.
    return 0 unless (
        (defined $isstandardlic && lc($isstandardlic) eq "1") ||
        (defined $isenterpriselic && lc($isenterpriselic) eq "1") ||
        (defined $isplatinumlic && lc($isplatinumlic) eq "1")
    );

    my $cloudsub = $decodednslicense->{"nslicense"}->{"cloudsubscriptionimage"};
    chomp(my $vpx_on_cloud = `sysctl -n netscaler.vpx_on_cloud`);
    # For VPX on cloud, upgarde via this path is only applicable for cloudsubscriptionimage="NO" (BYOL case)
    if ($vpx_on_cloud && defined $cloudsub) {
        return lc($cloudsub) eq "no" ? 1 : 0;
    }
    return 1;
}

sub check_license_expiry_and_decode_extension {
    if ($options{skip_lic_exp_chk}) {
        log_status "Skipping License Expiry Checks.";
        return;
    }
    my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";
    
    # Target date: April 15th, 2026 at midnight
    my $target_epoch = 1776297599;  # April 15, 2026 23:59:59 GMT
    my $current_epoch = time();

    log_status "Checking for license extension blobs...";
    
    log_status_no_console "Current epoch: $current_epoch";
    log_status_no_console "Target epoch: $target_epoch";
    
    # Check if current time is greater than April 15th, 2026
    if ($current_epoch <= $target_epoch) {
        log_status_no_console "System date is not greater than April 15th, 2026. Skipping extension blob check.";
        log_trace "END $this_subroutine_name";
        return;
    }
    
    log_status_no_console "System date is greater than April 15th, 2026. Checking license mode...";
    
    # Check if system is in local license mode with valid expiry
    if (!is_local_license_mode()) {
        log_status_no_console "System is not in local license mode. Skipping extension blob decode.";
        log_trace "END $this_subroutine_name";
        return;
    }
    
    log_status_no_console "System is in local license mode with valid expiry. Checking extension directory...";
    
    # Check if extension directory exists
    my $extension_dir = "/nsconfig/license/extension";
    if (!(-d $extension_dir)) {
        log_status_no_console "Extension directory $extension_dir does not exist.";
        log_trace "END $this_subroutine_name";
        handle_las_extension_error();
        return;
    }
    
    log_status_no_console "Extension directory found. Decoding all extension files...";
    
    # Invoke Python script to decode all extension files (returns array)
    my $python_script = "./check_license_extension.py";
    my $json_output = `/var/python/bin/python $python_script 2>/dev/null`;
    my $exit_code = $? >> 8;
    
    if ($exit_code != 0 || !$json_output) {
        log_status_no_console "Python script failed with exit code: $exit_code";
        log_trace "END $this_subroutine_name";
        handle_las_extension_error();
        return;
    }

    chomp($json_output);
    my $json = JSON::PP->new;
    my $extensions_array;

    eval {
        $extensions_array = $json->decode($json_output);
    };
    if ($@) {
        log_status_no_console "JSON decode error: $@";
        log_trace "END $this_subroutine_name";
        handle_las_extension_error();
        return;
    }

    # Ensure we have an array
    if (!ref($extensions_array) || ref($extensions_array) ne 'ARRAY') {
        log_status_no_console "Expected array of extensions but got: " . ref($extensions_array);
        log_trace "END $this_subroutine_name";
        handle_las_extension_error();
        return;
    }

    # Process each extension in the array
    foreach my $extension_index (0..$#{$extensions_array}) {
        my $decoded_data = $extensions_array->[$extension_index];
        my $file_name = $decoded_data->{source_file} || "extension_$extension_index";

        log_status_no_console "Processing extension file: $file_name";

        # Check if this extension is valid
        if (is_extension_valid($decoded_data, $current_epoch, $file_name)) {
            log_status_no_console "Extension file $file_name is valid and applicable";
            log_trace "END $this_subroutine_name";
            return;
        }
    }

    log_status_no_console "No valid extension files found with future expiry date and matching criteria";
    log_trace "END $this_subroutine_name";
    handle_las_extension_error();
    return;
}

# Helper function to validate individual extension
sub is_extension_valid {
    my ($decoded_data, $current_epoch, $file_name) = @_;
    
    # Validation 1: Check extension expiry
    if (!exists $decoded_data->{exp}) {
        log_status_no_console "Extension file $file_name expiry field not found";
        return 0;
    }
    
    my $extension_expiry = $decoded_data->{exp};
    log_status_no_console "Extension file $file_name expiry epoch: $extension_expiry";
    
    if ($current_epoch >= $extension_expiry) {
        log_status_no_console "Extension file $file_name has expired. Current epoch: $current_epoch, Expiry epoch: $extension_expiry";
        return 0;
    }
    
    # Validation 2: Check extension type
    if (!exists $decoded_data->{type}) {
        log_status_no_console "Extension file $file_name type field not found";
        return 0;
    }
    
    my $extension_type = $decoded_data->{type};
    log_status_no_console "Extension file $file_name type: $extension_type";
    
    if ($extension_type ne "v6.extension") {
        log_status_no_console "Extension file $file_name has invalid type: $extension_type (expected: v6.extension)";
        return 0;
    }
    
    # Validation 3: Check hostname binding
    if (!(exists $decoded_data->{binding} && exists $decoded_data->{binding}->{hostname})) {
        log_status_no_console "Extension file $file_name hostname binding information not found";
        return 0;
    }
    
    my $extension_hostname = $decoded_data->{binding}->{hostname};
    log_status_no_console "Extension file $file_name hostname: $extension_hostname";
    
    # Check for wildcard - if "*", skip hostname validation
    if ($extension_hostname eq "*") {
        log_status_no_console "Extension file $file_name hostname is wildcard (*) - applicable for all systems";
        return 1;
    }
    
    # Get hostname from lmutil lmhostid -n command
    my $system_hostid = `lmutil lmhostid -n 2>/dev/null`;
    chomp($system_hostid);
    
    if (!$system_hostid) {
        log_status_no_console "Extension file $file_name - lmutil lmhostid command failed or returned empty output";
        return 0;
    }
    
    if (index($system_hostid, $extension_hostname) == -1) {
        log_status_no_console "Extension file $file_name hostname mismatch. Extension hostname: $extension_hostname, System hostname: $system_hostid";
        return 0;
    }
    
    log_status_no_console "Extension file $file_name hostname validation passed";
    return 1;
}

sub handle_las_extension_error {
    if (has_valid_las_based_license()) {
        log_status "Valid LAS offline activation blob found.";
        return;
    }
    log_status "LAS enforcement extension blob is required for the installation of the new version.";
    log_status "Please apply the extension blob to proceed.";
    exit $LIC_EXTENSION_NOT_FOUND_EXIT_CODE;
}

# Helper: Parse date in format DD-mon-YYYY (e.g., 01-dec-2025) to epoch seconds
sub parse_ddmonyyyy {
    my $date = shift;
    if ($date =~ /^(\d{2})-([a-z]{3})-(\d{4})$/i) {
        my ($d, $m, $y) = ($1, lc($2), $3);
        my %mon = (jan=>0, feb=>1, mar=>2, apr=>3, may=>4, jun=>5, jul=>6, aug=>7, sep=>8, oct=>9, nov=>10, dec=>11);
        return unless exists $mon{$m};
        return POSIX::mktime(0,0,0,$d,$mon{$m},$y-1900);
    }
    return;
}

# Helper: Parse date in format YYYY.MMDD (e.g., 2025.1201) to epoch seconds
sub parse_yyyymodd {
    my $date = shift;
    if ($date =~ /^(\d{4})\.(\d{2})(\d{2})$/) {
        return POSIX::mktime(0,0,0,$3,$2-1,$1-1900);
    }
    return;
}

# Helper: Extracts the build date from the nsconfig binary and returns it as epoch seconds.
sub extract_nsconfig_build_epoch {
    my $binary = shift;
    my $build_date = '';
    my $what_out = `what $binary 2>/dev/null`;
    foreach my $line (split /\n/, $what_out) {
        # Handles: NetScaler NS14.1 CLI : Build 56.2, Date: Jul  3 2025, 17:35:59
        if ($line =~ /Date:\s*([A-Za-z]{3})\s+(\d{1,2})\s+(\d{4})/) {
            my %months = (
                Jan => '01', Feb => '02', Mar => '03', Apr => '04',
                May => '05', Jun => '06', Jul => '07', Aug => '08',
                Sep => '09', Oct => '10', Nov => '11', Dec => '12'
            );
            my ($mon, $day, $year) = ($1, $2, $3);
            $day = sprintf("%02d", $day);  # pad single digit day
            if (exists $months{$mon}) {
                $build_date = "$year.$months{$mon}$day";
                last;
            }
        }
    }
    return $build_date ? parse_yyyymodd($build_date) : undef;
}

sub is_sa_enforcement_check_applicable {

    if ($is_vpx == 1) {
        log_status "Hypervised NS detected, Not Exempt from SA enforcement.";
        return 1;
    }
    # Get the current system ID
    my $sysid = `sysctl -n netscaler.sysid`;
    chomp($sysid);

    # List of SYSIDs where SA enforcement has been done
    # List compiled from code in Licesning module (usr.src/netscaler/flexlm/nslicense/nslicense.c)
    my @sa_enforcement_sysids = (
        675310, 750000, 760000, 760100, 2200100, 2200110,
        250040, 250041, 250043, 250044, 250048, 250011, 250000,
        250101, 250141, 250150, 250140, 250148, 250145,
        250119, 250149, 250129, 30010, 30110, 30130,
        520100, 520110, 520141, 520200, 520300, 520310,
        520400, 520460, 520410, 520420, 520450,
        35000, 35060, 35100, 35020, 35080, 35082, 35090,
        560000, 560040, 560060
    );

    # Check if current sysid is in the exempt list
    foreach my $sa_enforcement_sysid (@sa_enforcement_sysids) {
        if ($sysid == $sa_enforcement_sysid) {
            log_status "SYSID $sysid is not exempt from SA enforcement.";
            return 1;
        }
    }

    log_status "SYSID $sysid is exempt from SA enforcement.";
    return 0;
}

# Validates license expiry and contract dates against build date and current time.
sub is_license_valid {
    my ($real_expiry, $contract_expiry) = @_;

    if (is_sa_enforcement_check_applicable()) {

        my $nsconfig_binary = "./nsconfig";

        # Always check contract_expiry (format: YYYY.MMDD) vs build date
        my $build_epoch = extract_nsconfig_build_epoch($nsconfig_binary);
        unless ($build_epoch) {
            log_status "WARNING: Could not determine build date from $nsconfig_binary";
            return 0;
        }
        my $contract_epoch = parse_yyyymodd($contract_expiry);
        unless (defined $contract_epoch && defined $build_epoch) {
            log_status "WARNING: Could not parse contract or build date";
            return 0;
        }
        # Contract must be valid for the build
        return 0 unless $contract_epoch >= $build_epoch;
    }

    # If real_expiry is not "permanent", check that it is in the future
    if (lc($real_expiry) ne "permanent") {
        my $real_epoch = parse_ddmonyyyy($real_expiry);
        my $now_epoch = time();
        unless (defined $real_epoch) {
            log_status "WARNING: Could not parse real expiry date: $real_expiry";
            return 0;
        }
        return $real_epoch >= $now_epoch ? 1 : 0;
    }

    # If real_expiry is "permanent", contract check is enough
    return 1;
}

# Extracts contract expiry, real expiry, and form factor from a license file.
sub extract_license_expiry_and_form_factor {
    my ($license_file) = @_;
    my ($contract_expiry, $real_expiry, $form_factor);
    my $valid_license_file = 0;

    open(my $fh, "<", $license_file) or do {
        log_status "WARNING: Can't open license file $license_file: $!";
        return ("", "", "", 0);
    };

    while (my $line = <$fh>) {
        next unless $line =~ /^INCREMENT/;
        my @fields = split(/\s+/, $line);
        $contract_expiry  = $fields[3] // '';
        $real_expiry = $fields[4] // '';
        my $feature = $fields[1] // '';
        if ($feature =~ /^CNS_V\d+/) {
            $form_factor = "VPX";
            $valid_license_file = 1;
        } elsif ($feature =~ /^CNS_\d{1,2}VCPU[SEP]_SSERVER$/) {
            # VPX with vCPU specification: CNS_<1-2 digit num>VCPU<S/E/P>_SSERVER
            $form_factor = "VPX";
            $valid_license_file = 1;
        } elsif ($feature =~ /^CNS_\d+/) {
            $form_factor = "MPX";
            $valid_license_file = 1;
        } else {
            $form_factor = "";
            next; # Continue to the next iteration of the while loop
        }
        last; # Stop after the first occurrence
    }
    close($fh);
    return ($contract_expiry, $real_expiry, $form_factor, $valid_license_file);
}

# Checks for a valid license file matching the system form factor.
sub has_valid_flex_based_license {
    my $license_dir = "/nsconfig/license";
    my $valid_license_file_found = 0;
    opendir(my $dh, $license_dir) or die "Cannot open $license_dir: $!";
    my $form_factor = $is_vpx == 1 ? "VPX" : "MPX";
    while (my $file = readdir($dh)) {
        next unless lc($file) =~ /\.lic$/;
        my $path = "$license_dir/$file";
        my ($contract_expiry, $real_expiry, $lic_form_factor, $valid_license_file) = extract_license_expiry_and_form_factor($path);
        next unless $lic_form_factor eq $form_factor;
        if ($valid_license_file) {
            $valid_license_file_found = 1;
        }
        if ($contract_expiry && $real_expiry) {
            if (is_license_valid($real_expiry, $contract_expiry)) {
                closedir($dh);
                return 1; # Valid license found
            } else {
                log_status "WARNING: License $file is expired or invalid.";
            }
        } else {
            log_status "WARNING: Could not find expiry dates in license file $file.";
        }
    }
    closedir($dh);
    return $valid_license_file_found == 1 ? 0 : 1; # Return 0 if valid license file found but expired, else 1 if no valid license file found
}

# Checks if offline activation blob is present AND valid.
sub has_valid_las_based_license {
    my $blob_file = "/nsconfig/license/offline_activation_blob";
    return 0 unless -e $blob_file;  # No blob present
    my $python_script = "./check_license_extension.py";
    my $cmd = "/var/python/bin/python $python_script --check_las_activation_blob >/dev/null 2>&1";
    system($cmd);
    my $ecode = $? >> 8;
    return $ecode == 0 ? 1 : 0;
}

# Checks validity of local license and prompts user or exits if invalid.
sub check_local_license_validity {
    if ($options{skip_lic_exp_chk}) {
        log_status "Skipping License Expiry Checks.";
        return;
    }
    if (is_local_license_mode()) {
        if (has_valid_flex_based_license()) {
            log_status "Valid Flexera based local license found.";
            return;
        } elsif (has_valid_las_based_license()) {
            log_status "Valid LAS offline activation blob found.";
            return;
        } else {
            log_status "Upgrade Blocked: Valid license not found";
            log_status "Upgrade has been blocked since there is no valid local license found on the NetScaler. Your current license files have expired Support Agreement (SA) date or contract expiry date, making them invalid for this upgrade. Please apply a valid license to proceed with upgrade. Contact Citrix Support or your ATS representative for further information.";
            exit($LIC_EXPIRED_EXIT_CODE); # NO - don't do the install
        }
    } else {
        log_status "Not in local license mode.";
        return; # Not local license mode, so proceed
    }
}

sub is_coleto() {
    my $sysid = `sysctl -n netscaler.sysid`;
    if( $sysid == 520300 || $sysid == 520200 || $sysid == 520310 ||
        $sysid == 520420 || $sysid == 520400 || $sysid == 520100 ||
        $sysid == 30010 || $sysid == 30002 || $sysid == 30130 ||
        $sysid == 30120 || $sysid == 30040 || $sysid == 520430 ||
        $sysid == 520460) {
        return 1;
    }
    return 0;
}

sub install_spa{
  my $this_subroutine_name = ( caller(0) )[3];
    log_trace " \nBEGIN $this_subroutine_name";
    log_status "Installing SPA scripts...";

    # List of files to be copied
    my @files_to_copy = ("spa_registration.py", "ztna-migration-script.py");

    foreach my $file (@files_to_copy) {
        if ( -e $file ) {
            log_status "Copying $file...";
            if ( -d "$spapath" ) {
                log_status "rm -f $spapath/$file";
                system("rm -f $spapath/$file") == 0 || ns_die("Error: Can't remove existing $spapath/$file");
            }
            else {
                log_status "mkdir -p $spapath";
                system("mkdir -p $spapath") == 0 || ns_die("Error: Can't create $spapath");
            }
            log_status "cp $file $spapath/$file";
            system("cp $file $spapath/$file") == 0 || ns_die("Error: Can't copy $file");
        }
        else {
            log_status "File $file does not exist, skipping...";
        }
    }
}

open STATE_LOG, ">$state_file";
openlog $syslog_ident, $syslog_opt, $syslog_facility;

$date      = time();
$localdate = localtime();

log_state "BEGIN_TIME $date $localdate";
log_state "VERSION $kernel";
log_state "VARIANT $variant ";

Getopt::Long::Configure('no_ignore_case');
GetOptions(
    'e'                  => \$options{e},
    'g'                  => \$options{g},
    'R'                  => \$options{R},
    'L'                  => \$options{L},
    'A'                  => \$options{A},
    'c'                  => \$options{c},
    'h'                  => \$options{h},
    'Y'                  => \$options{Y},
    'y'                  => \$options{y},
    'n'                  => \$options{n},
    'F'                  => \$options{F},
    'G'                  => \$options{G},
    'N'                  => \$options{N},
    'S'                  => \$options{S},
    'D'                  => \$options{D},
    'M'                  => \$options{M},
    'a'                  => \$options{a},
    'p'                  => \$options{p},
    'skip_lic_exp_chk'   => \$options{skip_lic_exp_chk},
);

while (($okey, $ovalue) = each %options)
{
  $opt_string .= "$okey, ";
}
$log_string="Options are: ";
if ($opt_string ne "" ) {
        log_status_no_console "$log_string $opt_string";
}else {
        log_status_no_console "No options"
}

if ( $options{h} ) {
    print_help();
}

if ( $options{M} ) {
    $run_nspepi_tool = 1;
}

if ( $options{p} ) {
    $flag_nsapi = 1;
}

if ( $options{Y} ) {
    $flag_Y = 1;
    $nsconf_check = 0;
    $user_nsconf_check = 0;
    $invalid_config_check = 0;
    $run_nspepi_tool = 0;
}

# Initialize Interrupt Handling
$SIG{'INT'}  = 'interrupt_int';
$SIG{'QUIT'} = 'interrupt_int';
$SIG{'HUP'}  = 'interrupt_other';
$SIG{'ABRT'} = 'interrupt_other';
$SIG{'STOP'} = 'interrupt_other';
$SIG{'KILL'} = 'interrupt_other';

# FIPS
if ( $options{F} ) {
    if ( is_coleto() ) {
        ns_die( "ERROR: -F not supported on this platform" );
    }

    else {
        $fips_platform = 1;
        log_status_no_console "detected fips platform install";
    }
}

if ( $options{G} ) {
    $nocurses = 1;
}

if ( $options{L} ) {
    $callhome_flag = 1;
}

if ( $options{A} ) {
    $admserviceconnect_flag = 1;
}

if ( $options{e} ) {
    $enh_flag=1;
}

if ( $options{R} ) {
    $resize_flag=1;
}

# This is for internal use. GUI need to run installns
# with this flag to skip any user interaction during upgrade.
if ( $options{g} ) {
    $gui_flag = 1;
}

if ( $options{N} ) {
    $nsconf_check = 0;
    $user_nsconf_check = 0;
    if ($gui_flag == 0) {
        $invalid_config_check = 0;
    }
}

#nsconf utility can't run on 4.9 BSD. It also has issues on versions less than 9.2
if ( $version =~ '4.9-NETSCALER' || $version =~ 'NETSCALER-9.0' || $version =~ 'NETSCALER-9.1') {
    $nsconf_check = 0;
}

if ( $options{c} ) {
    print
"-c Force Clean up option is now obsolete and will not do anything\n";
"Please discontinue the use of -c option and clean up file system manually if needed.\n";
}

if ( $options{S} ) {
	# Use at your own risk. not recommended.
	$ignore_platform_checks = "yes";
}
if ( $options{a} ){
  $nsconf_advanced = 1;
}

print_banner();

if ( $version =~ '4.4-RELEASE' ) {
    log_status_no_console "detected  Version < NS6.0";
    ns_die(
"This version of installns is not supported on software versions prior to 6.0"
    );
}
else {
    log_status_no_console "detected  Version >= NS6.0";
    log_status_no_console "Installation path for kernel is /flash";
    check_system_before_install();
    cloud_prevent_bootstrap();
}
find_old_server_cert();
remove_all_old_signature_and_associated_kernel_images();
copy_kernel();
install_kernel_signature();
install_rsakey();
copy_default_keys();
mod_loaderconf();
mod_loaderconflocal();
install_doc();
install_help();
install_help_cisco();
install_LogonPoint_workspace();
install_LogonPoint();
install_LoginSchemaFiles();
install_OTPToolFiles();
install_app_catalog_files();
install_gui();
install_epa_package();
install_nitro();
install_openapi_specification();
install_mastools();
install_nsconnector();
install_lom();
install_bios();
install_nic_firmware();
install_mstflint_package();
install_config_wipe();
install_linux_package();
install_root_certs();
install_callhome_cert();
install_geoipdb();
install_nfast();
install_safenet();
install_inCompass_db_files();
install_python();
remove_golang();
install_perl();
install_pgsql();
extract_nextgen();
install_nskrb();
remove_gnostic();
fix_kek_perms();
fix_nssynclog_proplog_perms();
fix_system_backup_perms();
copy_metrics_json_files();
copy_analytics_json_files();
fix_sensitive_tempfile_perms();
fix_ssl_private_key_perms();
create_apispec_directory();
install_default_apispecs();
install_spa();
copy_wasm_files();

chomp($ret = `sysctl netscaler.vpx_on_cloud`);
if ( $ret =~ /3$/ ) {
# Remove the .mdinfo file so that a fresh skuid is fecthed from IMDS.
# This is needed to avoid license decryption failure when we change UUID scheme.
    `rm -rf /nsconfig/.AZURE/.mdinfo`
}

# Create dhclient.conf to override MTU on AWS when upgrading to 13.1 builds
if ( $ret =~ /1$/ && $vers =~ /13\.[1-9][0-9]*/ ) {
    system("echo \"supersede interface-mtu 1500;\" > /flash/nsconfig/dhclient.conf");
}

fix_ntp();
nsclfsyncd_stop();
upgrade_before_pe_start();
after_upgrade();
nextgen_upgrade();
$ret = `sysctl netscaler.sysid`;
if ( $ret =~ /45001[0|1]$/) {
    install_vmtools();
}

fix_downgrade_issues();

log_status_no_console "prompting for reboot";

$date      = time();
$localdate = localtime();

log_state "END_TIME $date $localdate";
close STATE_LOG;

prompt_epaProfileWarning();

#turn off file sync, turned on automatically after ns reboot
system("chmod 777 /etc/crontab");
system("sed -i .temp '/[[:<:]]nsfsyncd[[:>:]]/s/^/#/g' /etc/crontab");

# This function is called to convert all the imported objects to lower
# case before upgrade.
convert_import_object_to_lower_case();

#Convert learn db files to lower case before upgrade
handle_learn_db_before_upgrade();

# print the message about the secure on for the rpc nodes
print_secure_rpc_on();

prompt_reboot();

closelog;
### End installns script