#!/bin/bash 
#
# ps_analyze.sh - analyze a program and transform it for peasoupification to prevent exploit.
#
# This script depends on having many environment variables defined, but it should check that they are defined properly for you.
#
# Usage:
#     peasoup_analyze.sh <original_binary> <new_binary> <options>
#

source $(dirname $0)/ps_wrapper.source $0

realpath() {
  \cd "$1"
  /bin/pwd
}


##################################################################################
# set default values for 
##################################################################################

initial_on_phases="stratafy_with_pc_confine create_binary_script is_so gather_libraries meds_static pdb_register fill_in_cfg fill_in_indtargs clone fix_calls generate_spri spasm fast_annot fast_spri preLoaded_ILR1 preLoaded_ILR2"

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

ulimit -s unlimited > /dev/null 2>&1 || true

# default watchdog value is 30 seconds
#watchdog_val=30
errors=0
warnings=0

# record statistics in database?
record_stats=0

# DEFAULT TIMEOUT VALUE
INTEGER_TRANSFORM_TIMEOUT_VALUE=1800
TWITCHER_TRANSFORM_TIMEOUT_VALUE=1800
# Setting PN timeout to 6 hours for TNE. 
# PN_TIMEOUT_VALUE=21600

export backend=strata

# 
# set default values for 
#

CONCOLIC_DIR=concolic.files_a.stratafied_0001

# JOBID

JOBID="$(basename $1).$$"

user_critical_steps=""

# 
# By default, big data approach is off
# To turn on the big data approach: modify check_options()
#

# alarm handler
THIS_PID=$$
handle_alarm()
{
	# reset handler
	trap - ALRM

	#
	# create a report for all of ps_analyze.
	#
	report_logs

	# go back to original directory
	cd - > /dev/null 2>&1

	# stop ps_analyze
	kill -9 $THIS_PID

	# exit timer process: SIGALRM + 128
	exit 142
}

set_timer()
{
	# set handler
	trap "handle_alarm" ALRM

	# wait
	sleep $1& wait

	# signal the alarm
	kill -ALRM $$
}

fail_gracefully()
{
	if [ ! -z $TIMER_PID ]; then
		kill -9 $TIMER_PID
	fi
	echo $1
	echo
	# display usage too.
	usage
	exit 255
}

adjust_lib_path()
{
	NEWPATH=
	for i in `echo $LD_LIBRARY_PATH | sed 's/:/ /g'`
	do
		alp_newdir=`realpath $i	 2> /dev/null`
		if [ $? = 0  ] ; then 
			NEWPATH=$NEWPATH:$alp_newdir
		fi
	done


	# also, add newdir to the ld-library path for analysis.
	export LD_LIBRARY_PATH=$NEWPATH:$PWD/$newdir
}

check_step_option()
{
	echo $1|egrep "=off$|=on$" > /dev/null
	if [ $? -ne 0 ]; then
		echo Malformed option: $1;
		exit -4;
	fi
	
}


set_step_option()
{
	step=`echo "$1" | cut -d: -f1` 
	option=`echo "$1" | cut -s -d: -f2-` 

	# echo "Found step-option for '$step':'$option'"
	if [[ -z "$option" ]]; then
		echo "Cannot parse step:option pair out of '$1'"
		exit 2
	fi

	#
	# this sets step_options_$step to have the new option
	# you can now, when writing your step, just add $step_options_<stepname> where you want the options passed to your step.
	#
	var="step_options_$step"
	old_value="${!var}"
	new_value="$old_value $option"
	eval "step_options_$step=\"$new_value\""
}

usage()
{
	echo "Protect an input program, generating a new executable."
	echo "ps_analyze.sh <input> <output> <options>  "
	echo 
	echo "Where options can be any of"
	echo "   --step <stepname>=(on|off) 		Turn the <stepname> step on or off"
	echo "   -s <stepname>=(on|off)			Same as --step"
	echo "   --critical-step <stepname>=(on|off)    Same as --step, but exits with error code if step fails."
	echo "   -c <stepname>=(on|off)    		Same as --critical-step"
	echo "   --step-option <stepname>:<option>	Pass additional option to step <stepname>"
	echo "   -o <stepname>:<option>			Same as --step-option"
	echo "   --timeout				Specify a timeout for ps_analyze.sh."
	echo "   -t					Same as --timeout"
	echo "   --watchdog				Specify a watchdog timer for the protected program."
	echo "   -w					Same as --watchdog"
	echo "   --help					Print this page."
	echo "   --usage				Same as --help"
	echo "   --id <jobid>				Unsupported.  Ask an7s."
	echo "   --name <dbname>			Unsupported.  Ask an7s."
	echo "   --manual_test_script <scriptname>	Specify how to test to the program.  API documentation incomplete."
	echo "   --manual_test_coverage_file <file>	Specify a profile for the program.  API documentation incomplete."
	echo "   --tempdir <dir>			Specify where the temporary analysis files are stored, default is peasoup_executable_directory.<exe>.<pid>"
	echo "   --backend <zipr|strata>		Specify the backend rewriting technology to use.  Default: Strata"
	echo "   -b <zipr|strata>			same as --backend "
	echo "   --stop-after <step>			Stop ps_analyze after completeling the specified step."
	echo "   --stop-before <step>			Stop ps_analyze before starting the specified step."
	echo "   --dump-after <step>			Dump IR after completeling the specified step."
	echo "   --dump-before <step>			Dump IR before starting the specified step."

}



#
# check that the remaining options are validly parsable, and record what they are.
#
check_options()
{

	#
	# turn on initial default set of phases
	#
	for phase in $initial_on_phases
	do
		echo $phases_spec|egrep "$phase=" > /dev/null
		if [ ! $? -eq 0 ];
		then
			phases_spec="$phases_spec $phase=on"
		fi
	done

	# 
	# loop to process options.
	# 

	# Note that we use `"$@"' to let each command-line parameter expand to a 
	# separate word. The quotes around `$@' are essential!
	# We need TEMP as the `eval set --' would nuke the return value of getopt.
	short_opts="s:c:t:w:b:o:h"
	long_opts="--long step-option: 
		   --long step: 
		   --long critical-step: 
		   --long timeout: 
		   --long id:  				
		   --long name:	  			
		   --long manual_test_script: 
		   --long manual_test_coverage_file: 
		   --long watchdog: 
		   --long backend:  			
		   --long tempdir:  			
		   --long help
		   --long usage
		   --long stop-after:
		   --long stop-before:
		   --long dump-after:
		   --long dump-before:
		"

	# solaris does not support long option names
	if [ `uname -s` = "SunOS" ]; then
		TEMP=`getopt $short_opts "$@"`
	else
		TEMP=`getopt -o $short_opts $long_opts -n 'ps_analyze.sh' -- "$@"`
	fi


	# error check #
	if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit -1 ; fi

	# Note the quotes around `$TEMP': they are essential!
	eval set -- "$TEMP"

	while true ; do
		case "$1" in
			--tempdir)
				tempdir_opt="$2"
				if [ -e "$tempdir_opt"  ]; then
					echo "$tempdir_opt already exists, cannot continue."
					exit 1
				fi
            			shift 2
				;;
			-b|--backend)
				if [ "X$2" = "Xzipr" ]; then
					echo "Using Zipr backend."
					export backend="zipr"
					phases_spec=" $phases_spec stratafy_with_pc_confine=off generate_spri=off spasm=off fast_annot=off zipr=on preLoaded_ILR1=off  preLoaded_ILR2=off fast_spri=off create_binary_script=off is_so=off"
					phases_spec=${phases_spec/preLoaded_ILR1=on/}
					phases_spec=${phases_spec/preLoaded_ILR2=on/}
					step_options_gather_libraries="$step_options_gather_libraries --main_exe_only"
				elif [ "X$2" = "Xstrata" ]; then
					echo "Using Strata backend."
					export backend="strata"
					#  strata is default, do nothing.
				fi
            			shift 2
			;;
			-o|--step-option)
           			set_step_option "$2"
            			shift 2
            		;;
            		# This is the watchdog value
#        		-w|--watchdog)
#            			watchdog_val=$2
#            			shift 2
#            		;;
			-s|--step) 
				check_step_option $2
				phases_spec=" $phases_spec $2 "
				shift 2 
			;;
			-c|--critical-step) 
				check_step_option $2
				phases_spec=" $phases_spec $2 "
				step_name=$(echo "$2" | sed "s/=on *$//"|sed "s/=off *$//")
				user_critical_steps="$user_critical_steps $step_name "
				shift 2 
			;;
			--manual_test_script) 
				manual_test_script=$2
				shift 2 
			;;
			--manual_test_coverage_file) 
				manual_test_coverage_file=$2
				shift 2 
			;;
			-t|--timeout) 
				set_timer $2 & TIMER_PID=$!
				shift 2 
			;;
			--id) 
				JOBID=$2
				shift 2 
			;;
			--name) 
				DB_PROGRAM_NAME=$2
				shift 2 
			;;
			-h|--help|--usage)
				usage
				exit 1
			;;
			--stop-before)
				stop_before_step=$2
				shift 2
			;;
			--stop-after)
				stop_after_step=$2
				shift 2
			;;
			--dump-before)
				dump_before_step=$2
				shift 2
			;;
			--dump-after)
				dump_after_step=$2
				shift 2
			;;
			--) 	shift 
				break 
			;;
			*) 	echo "Internal error!" 
		 		exit -2 
			;;
		esac
	done

	# report errors if found
	if [ ! -z $1 ]; then
		echo Unparsed parameters:
	fi
	for arg do echo '--> '"\`$arg'" ; done
	if [ ! -z $1 ]; then
		exit -3;	
	fi

	# turn off heaprand, signconv_func_monitor, and watchdog double_free if twitcher is on for now
	is_step_on twitchertransform
	if [[ $? = 1 && "$TWITCHER_HOME" != "" ]]; then
		phases_spec="$phases_spec heaprand=off signconv_func_monitor=off watchdog=off double_free=off"
	fi

	#
	# turn on/off recording of statistics
	#
	is_step_on stats
	if [[ $? = 1 ]]; then
		record_stats=1
	fi
}


#
# subroutine to determine if a particular phase of ps_analyze is on.
#
is_step_on()
{
	local step=$1

	# check for phases explicitly turned off
	echo "$phases_spec"|egrep " $step=off" > /dev/null
	grep_res=$?
	if [ $grep_res -eq 0 ] ; then
		return 0
	fi

	# determine whether phase is on
	echo "$phases_spec"|egrep " $step=on" > /dev/null
	grep_res=$?
	if [ $grep_res -eq 0 ] ; then
		return 1
	fi

	# all steps are off unless explicitly set to on
	return 0
}

#
# is_step_error decide based on the step (in $1) and the exit code (in $2) if there was a failure.
#
is_step_error()
{
	my_step=$1
	my_error=$2


	case $my_step in
		*)
			if [ $my_error -eq 0 ]; then
				# if not otherwise specified, programs should return 0
				return 0;
			fi
			return 1;
	esac
}

#
# return the severity of the error for the step in $1
#
stop_if_error()
{
	my_step=$1

	# check for a step the user specified as critical.
	echo "$user_critical_steps"|egrep " $step " > /dev/null
	grep_res=$?
	if [ $grep_res -eq 0 ] ; then
		return 4;
	fi

	case $my_step in
		# getting the annotation file right is necessary-ish
		meds_static|rida)
			return 1;
		;;
		# DB operations are necessary 
		pdb_register|clone|fix_calls|fill_in_cfg|fill_in_indtargs|spasm|fast_spri|generate_spri|spasm|stratafy_with_pc_confine)
			return 2;
		;;
		gather_libraries)
			return 3;
		;;
		# other steps are optional
		*)
			return 0;
	esac
}

#
# Check dependencies
#
check_dependencies()
{
	# format is:  step1,step2,step3
	local dependency_list=$1

	# extract each step, make sure step is turned on
	local steps=$(echo $dependency_list | tr "," "\n")
	for s in $steps
	do
		if [[ "$s" != "none" && "$s" != "mandatory" ]]; then
			is_step_on $s
			if [ $? -eq 0 ]; then
				return 0
			fi
		fi
	done

	return 1
}

check_steps_completed()
{
	#echo "Checking steps: $phases_spec"
	for step_spec in $phases_spec
	do
		# if step is on.
		sn=$step_sepc
		sn=$(basename $sn =on)
		sn=$(basename $sn =off)
		is_step_on $sn
		if [ $? = 1 ]; then
			if [ ! -f logs/$sn.log ] ; then
				echo "*********************************************************"
				echo "*********************************************************"
				echo "  Warning! Step requested, but not performed: $step_name "
				echo "*********************************************************"
				echo "*********************************************************"
				warnings=1
			fi
			
		fi
		
	done
}

#
# Detect if this step of the computation is on, and execute it.
#
perform_step()
{
	step=$1
	shift
	mandatory=$1
	shift
	command="$*"

	performed_steps="$performed_steps $step"

	logfile=logs/$step.log

	if [ "$step" = "$stop_before_step" ]; then 
		echo "ps_analyze has been asked to stop before step $step."
		echo "command is:  $command"

		printf "COMMIT_ALL" > $input_pipe
                read -r commit_res < $output_pipe
                if [ "$commit_res" != "COMMIT_ALL_OK"  ]; then
                        echo A critical step was necessary, but failed.
                        echo To know exactly which step failed, source set the DEBUG_STEPS env var.
                        exit -1;
                fi
		printf "TERMINATE" > $input_pipe

		exit 1
	fi
	if [ "$step" = "$dump_before_step" ]; then 
		echo " ---- ps_analyze has been asked to dump before step $step."

		printf "COMMIT_ALL" > $input_pipe
                read -r commit_res < $output_pipe
                if [ "$commit_res" != "COMMIT_ALL_OK"  ]; then
			echo A critical step was necessary, but failed.  Exiting ps_analyze early.
			echo To know exactly which step failed, source set the DEBUG_STEPS env var.
                        exit -1;                        
		fi

		$SECURITY_TRANSFORMS_HOME/plugins_install/dump_map.exe $cloneid > logs/dump_before.log
	fi

	is_step_on $step
	if [ $? -eq 0 ]; then 
		#echo Skipping step $step. [dependencies=$mandatory]
		return 0
	fi

	starttime=`$PS_DATE`

	# optionally record stats
	if [ $record_stats -eq 1 ]; then
		$PEASOUP_HOME/tools/db/job_status_report.sh "$JOBID" "$step" "$stepnum" started "$starttime" inprogress
	fi

	if [[ "$mandatory" != "none" && "$mandatory" != "mandatory" ]]; then
		check_dependencies $mandatory
		if [ $? -eq 0 ]; then 
			echo Skipping step $step because of failed dependencies. [dependencies=$mandatory] "*************************************************"
			errors=1
			if [ $record_stats -eq 1 ]; then
				$PEASOUP_HOME/tools/db/job_status_report.sh "$JOBID" "$step" "$stepnum" completed "$starttime" error
			fi
			return 0
		fi
	fi

	echo -n Performing step "$step" [dependencies=$mandatory] ...
	starttime=`$PS_DATE`

	# First, check if using Transform_Step plugin architecture
	echo "$command"|grep "lib$step\.so " > /dev/null
        grep_res=$?
        if [ $grep_res -eq 0 ] ; then
                if [[ "$(stop_if_error $step)" != "0" ]]; then
			printf "SET_LOGFILE %s" $(pwd)/$logfile > $input_pipe
			read -r log_set_res < $output_pipe
			echo "EXECUTE_STEP CRITICAL $command" > $input_pipe
                else
			printf "SET_LOGFILE %s" $(pwd)/$logfile > $input_pipe
			read -r log_set_res < $output_pipe
                        echo "EXECUTE_STEP OPTIONAL $command" > $input_pipe
                fi

                read -r thanos_res < $output_pipe

                if [ "$thanos_res" = "ERR_INVALID_CMD"  ]; then
                        echo Internal Transform_Step plugin architecture error.
                        echo Unrecognized EXECUTE_STEP command. Exiting ps_analyze early.
                        exit -1
                elif [ "$thanos_res" = "STEP_UNSUPPORTED" ]; then
                        command_exit=127 # command not found
                else
                        command_exit=$thanos_res
		fi
        else
		# Otherwise, do things the old way (step is its own process)
		printf "COMMIT_ALL" > $input_pipe
		read -r commit_res < $output_pipe
		if [ "$commit_res" != "COMMIT_ALL_OK"  ]; then
			command_exit=$commit_res
		else
			# If verbose is on, tee to a file 
			if [ ! -z "$DEBUG_STEPS" ]; then
				$command 
				command_exit=$?
			elif [ ! -z "$VERBOSE" ]; then
				$command 2>&1 | tee $logfile
				command_exit=${PIPESTATUS[0]} # this funkiness gets the exit code of $command, not tee
			else
				$command > $logfile 2>&1 
				command_exit=$?
			fi
		fi
	fi

	endtime=`$PS_DATE`
	
	echo "#ATTRIBUTE start_time=$starttime" >> $logfile
	echo "#ATTRIBUTE end_time=$endtime" >> $logfile
	echo "#ATTRIBUTE step_name=$step" >> $logfile
	echo "#ATTRIBUTE step_number=$stepnum" >> $logfile
	echo "#ATTRIBUTE step_command=$command " >> $logfile
	echo "#ATTRIBUTE step_exitcode=$command_exit" >> $logfile

	# report job status
	if [ $command_exit -eq 0 ]; then
		if [ $record_stats -eq 1 ]; then
			$PEASOUP_HOME/tools/db/job_status_report.sh "$JOBID" "$step" "$stepnum" completed "$endtime" success $logfile
		fi
	else
		if [ $record_stats -eq 1 ]; then
			$PEASOUP_HOME/tools/db/job_status_report.sh "$JOBID" "$step" "$stepnum" completed "$endtime" error $logfile
		fi
	fi

	is_step_error $step $command_exit
	if [ $? -ne 0 ]; then
		echo "Done.  Command failed! ***************************************"

		# check if we need to exit
		stop_if_error $step
		if [ $? -gt $error_threshold ]; then 
			echo The $step step is necessary, but failed.  Exiting ps_analyze early.
			echo If DEBUG_STEPS is NOT on, this failure may be from a previously
			echo executed critical step.
			exit -1;
		fi
		errors=1
	elif [ -f warning.txt ]; then
		# report warning to user.
		warnings=1
		echo "Done.  Command had serious warnings! ***************************************"
		cat warning.txt
		# report warning in log file, line by line, as an attribute.
		while IFS= read -r line; do
			echo
			echo "#ATTRIBUTE serious_warning_text=\"$line\""  >> $logfile
		done < "warning.txt"
		# remove warning.txt so we don't report these warnings again.
		rm -f warning.txt
	else
		echo Done.  Successful.
	fi

	# move to the next step 
	stepnum=`expr $stepnum + 1`

	all_logs="$all_logs $logfile"

	if [ "$step" = "$stop_after_step" ]; then 
		echo "ps_analyze has been asked to stop after step $step."
		echo "command is:  $command"

		printf "COMMIT_ALL" > $input_pipe
                read -r commit_res < $output_pipe
                if [ "$commit_res" != "COMMIT_ALL_OK"  ]; then
                        echo A critical step was necessary, but failed.
                        echo To know exactly which step failed, source set the DEBUG_STEPS env var.
                        exit -1;
                fi
		printf "TERMINATE" > $input_pipe

		exit 1
	fi
	if [ "$step" = "$dump_after_step" ]; then 
		echo " ---- ps_analyze has been asked to dump after step $step."

		printf "COMMIT_ALL" > $input_pipe
                read -r commit_res < $output_pipe
                if [ "$commit_res" != "COMMIT_ALL_OK"  ]; then
                        echo A critical step was necessary, but failed.  Exiting ps_analyze early.
                        echo To know exactly which step failed, source set the DEBUG_STEPS env var.
                        exit -1;
                fi

		$SECURITY_TRANSFORMS_HOME/plugins_install/dump_map.exe $cloneid > logs/dump_after.log
	fi
	return $command_exit
}

do_plugins()
{

	builtin_steps="
		gather_libraries
		meds_static
		pdb_register
		fill_in_cfg
		fill_in_indtargs
		clone
		fix_calls
		manual_test
		zipr
		generate_spri
		preLoaded_ILR1
		preLoaded_ILR2
		spasm
		fast_annot
		fast_spri
		rida
	"

	for i in $phases_spec
	do
		stepname=$i
		stepname=$(basename $stepname =on)
		stepname=$(basename $stepname =off)
		
		echo $builtin_steps | grep $stepname  > /dev/null 2> /dev/null 
	
		if [ $? = 0 ]; then
			# skip builtin steps so we don't get errors.
			continue;
		fi
		is_step_on $stepname
		if [ $? = 0 ]; then
			# if step isn't on, don't do it.
			continue
		fi

		# get step options
		this_step_options_name=step_options_$stepname
		value="${!this_step_options_name}"

		plugin_path=$SECURITY_TRANSFORMS_HOME/plugins_install/
		
		# invoke .exe or .sh as a plugin step.
		if [ -x $plugin_path/$stepname.exe ]; then
			perform_step $stepname none $plugin_path/$stepname.exe  $cloneid  $value
		elif [ -x $plugin_path/$stepname.sh ]; then
			perform_step $stepname none $plugin_path/$stepname.sh $cloneid  $value
		elif [ -x $plugin_path/lib$stepname.so ]; then
			perform_step $stepname none lib$stepname.so $cloneid $value
		else
			echo "*********************************************************"
			echo "*********************************************************"
			echo "  Warning! Step requested, but not performed: $stepname "
			echo "*********************************************************"
			echo "*********************************************************"
			warnings=1
		fi
	done
		

# old style -- scan plugins in alphabetical order.
#	# do plugins directory
#	for i in $SECURITY_TRANSFORMS_HOME/plugins_install/*.exe $SECURITY_TRANSFORMS_HOME/plugins_install/*.sh;
#	do
#		stepname=`basename $i .exe`
#		stepname=`basename $stepname .sh`
#		this_step_options_name=step_options_$stepname
#		value="${!this_step_options_name}"
#		perform_step $stepname none $i $cloneid  $value
#	done

}


#
# create a log for ps_analyze
#
report_logs()
{
	logfile=logs/ps_analyze.log

	myhost=$(hostname)
	echo "#ATTRIBUTE start_time=$ps_starttime" >> $logfile
	echo "#ATTRIBUTE end_time=$ps_endtime" >> $logfile
	echo "#ATTRIBUTE hostname=$myhost" >> $logfile
	echo "#ATTRIBUTE step_name=all_helix" >> $logfile

#	for i in $all_logs
#	do
#		stepname=`basename $i .log`
#		echo >> $logfile
#		echo ------------------------------------------------------- >> $logfile
#		echo ----- From $i ------------------- >> $logfile
#		echo ------------------------------------------------------- >> $logfile
#		cat $i |sed "s/^# ATTRIBUTE */# ATTRIBUTE ps_$i_/" >> $logfile
#		echo ------------------------------------------------------- >> $logfile
#		echo >> $logfile
#	done
}



#
# check if the list of environment variables passed are all defined.
#
check_environ_vars()
{

	while [ true ]; 
	do

		# done?
		if [ -z $1 ]; then
			return;
		fi

        	# create the $ENVNAME string in varg
        	varg="\$$1"

        	# find out the environment variable's setting
        	eval val=$varg

		if [ -z $val ]; then echo Please set $1; exit 1; fi

		shift 
	done

}

#
# Check that the filenames passed are valid.
#
check_files()
{

	while [ true ]; 
	do

		# done?
		if [ -z $1 ]; then
			return;
		fi

		if [ ! -f $1 ]; then 
			fail_gracefully "PEASOUP ERROR:  $1  not found.  Is there an environment var set incorrectly?"
		fi

		shift 
	done

}

check_for_bad_funcs()
{
	my_name=$1
	bad_funcs="" # previously "iconv_open"
#	bad_funcs=""

	for ducs_i in $bad_funcs
	do
		nm $my_name 2>&1 |grep $ducs_i  > /dev/null 2> /dev/null 
	
		if [ $? = 0 ]; then
			echo "Found bad function ($ducs_i) in executable, we should skip this test."
			echo SKIP
			echo Skip
			echo skip
			exit 255
		fi
	done
}

compatcheck()
{
	infile=$1

	if [ ! -f $infile ]; then
		echo File not found: $infile
		usage
		exit 2
	fi

	file $1 |egrep  "ELF.*executable" > /dev/null 2>&1 
	if [ $? = 0 ]; then
		echo Detected ELF file.
		return
	fi
	file $1 |egrep  "ELF.*shared object" > /dev/null 2>&1 
	if [ $? = 0 ]; then
		echo Detected ELF shared object.
		return
	fi
	file $1 |egrep  "CGC.*executable" > /dev/null 2>&1 
	if [ $? = 0 ]; then
		echo Detected CGCEF file.
		return
	fi


	echo ------------------------
	echo "Input file ($infile) failed compatability check.  Cannot protect this file:"
	file $infile
	echo ------------------------
	usage
	exit 2

}


#
# turn on debugging output if it's requested.
#
if [ ! -z "$VERBOSE" ]; then
	set -x
fi


#
# set the threshold value.  if a step errors with a more severe error (1=most severe, >1 lesser severe)
# than the error_threshold, we exit.
#
error_threshold=0

#
# record when we started processing:
#
ps_starttime=$($PS_DATE)


#
# stepnum used for counting how many steps peasoup executes
# 
stepnum=0


#
# Check for proper environment variables and files that are necessary to peasoupify a program.
#
check_environ_vars PEASOUP_HOME SMPSA_HOME SECURITY_TRANSFORMS_HOME IDAROOT



if [ ! -x $SMPSA_HOME/SMP-analyze.sh ] &&  [ ! -x $SMPSA_HOME/SMP-analyze.sh ] ; then
	echo "SMP-analyze script (local or remote) not found"
	exit 1
fi

#
# Check/parse options
#
if [ -z $2 ]; then
  fail_gracefully "Usage: $0 <original_binary> <new_binary> <options>"
fi

#
# record the original program's name
#
orig_exe=$1
newname=a
shift


#
# sanity check incoming arg.
#
if [ ! -f $orig_exe ]; then
	fail_gracefully "ps_analyze cannot find file named $orig_exe."
fi

#
# record the new program's name
#
export protected_exe=$1
shift

#
# finish argument parsing
#
check_options "$@"


#
# check for input file existance and file type
#
compatcheck $orig_exe

#
# new program
#
name=`basename $orig_exe`

#
# create a new working directory.  default to something that allows parallelism unless asked by the user.
#
if [ "X$tempdir_opt" != "X" ]; then
	newdir="$tempdir_opt"
else
	newdir=peasoup_executable_directory.$JOBID
fi
export newdir

# create a working dir for all our files using the pid
mkdir $newdir

# store the original executable as a.ncexe
cp $orig_exe $newdir/$newname.ncexe

file $orig_exe|grep 32-bit >/dev/null 2>&1 
if [ $? = 0 ]; then 
	if [ `uname -p` = 'x86_64' ]; then
		STRATA_HOME=$STRATA_HOME32
		STRATA=$STRATA32
	fi
	arch_bits=32
else
	arch_bits=64
fi


if [ $backend = "strata" ]; then
	check_environ_vars STRATA_HOME 
	check_files $PEASOUP_HOME/tools/getsyms.sh $STRATA_HOME/tools/pc_confinement/stratafy_with_pc_confine.sh 
elif [ $backend = "zipr" ]; then
	check_environ_vars ZIPR_INSTALL
	check_files $ZIPR_INSTALL/bin/zipr.exe
else
	echo "Unknown backend!"
	exit 1
fi

#
# setup libstrata.so.  We'll setup two versions, one with symbols so we can debug, and a stripped, faster-loading version.
# by default, use the faster version.  copy in the .symbosl version for debugging
#
if [ -f $STRATA_HOME/lib/libstrata.so  -a $backend = "strata" ]; then
	cp $STRATA_HOME/lib/libstrata.so $newdir/libstrata.so.symbols
	cp $STRATA_HOME/lib/libstrata.so $newdir/libstrata.so.nosymbols
	$PS_STRIP $newdir/libstrata.so.nosymbols
	cp $newdir/libstrata.so.nosymbols $newdir/libstrata.so
fi


adjust_lib_path 



# make sure we overwrite out output file one way or another
rm -f $protected_exe

# and switch to that dir
cd $newdir

check_for_bad_funcs $newname.ncexe

# next, create a location for our log files
mkdir logs 	


#
# turn off runtime protections for BED. turn off runtime prrotections for BED. turn off runtime prrotections for BED.
#
STRATA_DOUBLE_FREE=0
STRATA_HEAPRAND=0
STRATA_PC_CONFINE=0
STRATA_PC_CONFINE_XOR=0


# start thanos 
input_pipe="thanos_input"
[ -p $input_pipe ] || mkfifo $input_pipe
output_pipe="thanos_output"
[ -p $output_pipe ] || mkfifo $output_pipe

$SECURITY_TRANSFORMS_HOME/plugins_install/thanos.exe $input_pipe $output_pipe &
thanos_pid=$!

# set thanos execution mode
if [ ! -z "$DEBUG_STEPS" ]; then
        printf "SET_MODE DEBUG" > $input_pipe
elif [ ! -z "$VERBOSE" ]; then
	printf "SET_MODE VERBOSE" > $input_pipe
else
	printf "SET_MODE DEFAULT" > $input_pipe
fi

read -r mode_set_res < $output_pipe

if [ "$mode_set_res" != "MODE_SET_OK" ]; then
	echo Internal Transform_Step plugin architecture error.
	echo Mode set failed. Exiting ps_analyze early.
	exit -1
fi

# Make sure thanos is always exited
function exit_thanos {
	# will do the job for emergency exits
	kill $thanos_pid &> /dev/null
	wait $thanos_pid &> /dev/null
	rm -f $input_pipe
	rm -f $output_pipe
}
trap exit_thanos EXIT

#
# copy the .so files for this exe into a working directory.
#
perform_step gather_libraries mandatory $PEASOUP_HOME/tools/do_gatherlibs.sh $step_options_gather_libraries

#
# Running IDA Pro static analysis phase ...
#
perform_step meds_static mandatory $PEASOUP_HOME/tools/do_idapro.sh $name $step_options_meds_static
perform_step rida mandatory $SECURITY_TRANSFORMS_HOME/plugins_install/rida.exe ./a.ncexe ./a.ncexe.annot ./a.ncexe.infoannot ./a.ncexe.STARSxrefs $step_options_rida
touch a.ncexe.annot
cp a.ncexe.annot a.ncexe.annot.full

##
## Populate IR Database
##

#
# get some simple info for the program
#	
if [ -z $DB_PROGRAM_NAME ]; then
#	DB_PROGRAM_NAME=`basename $orig_exe | sed "s/[^a-zA-Z0-9]/_/g"`
	DB_PROGRAM_NAME=`basename $protected_exe | sed "s/[^a-zA-Z0-9]/_/g"`
fi
MD5HASH=`$PS_MD5SUM $newname.ncexe | cut -f1 -d' '`

INSTALLER=`pwd`

#
# register the program
#
perform_step pdb_register mandatory "$PEASOUP_HOME/tools/db/pdb_register.sh $DB_PROGRAM_NAME `pwd`" registered.id
is_step_on pdb_register
if [ $? = 1 ]; then
	varid=`cat registered.id`
	if [ ! $varid -gt 0 ]; then
		fail_gracefully "Failed to write Variant into database. Exiting early.  Is postgres running?  Can $PGUSER access the db?"
	fi
fi

if [ $record_stats -eq 1 ]; then
	$PEASOUP_HOME/tools/db/job_spec_register.sh "$JOBID" "$DB_PROGRAM_NAME" "$varid" 'submitted' "$ps_starttime"
fi


if [ $record_stats -eq 1 ]; then
	$PEASOUP_HOME/tools/db/job_spec_update.sh "$JOBID" 'pending' "$ps_starttime"
fi

# build basic IR
perform_step fill_in_cfg mandatory libfill_in_cfg.so $varid $step_options_fill_in_cfg
perform_step fill_in_safefr mandatory $SECURITY_TRANSFORMS_HOME/bin/fill_in_safefr.exe $varid 
perform_step fill_in_indtargs mandatory $SECURITY_TRANSFORMS_HOME/bin/fill_in_indtargs.exe $varid $step_options_fill_in_indtargs

# finally create a clone so we can do some transforms 
perform_step clone mandatory $SECURITY_TRANSFORMS_HOME/bin/clone.exe $varid clone.id
is_step_on clone
if [ $? = 1 ]; then
	cloneid=`cat clone.id`
	#	
	# we could skip this check and simplify ps_analyze if we say that cloning is necessary in is_step_error
	#
	if [ -z "$cloneid" -o  ! "$cloneid" -gt 0 ]; then
		fail_gracefully "Failed to create variant.  Is postgres running properly?"
	fi
fi

# do the basic tranforms we're performing for peasoup 
perform_step fix_calls mandatory $SECURITY_TRANSFORMS_HOME/bin/fix_calls.exe $cloneid	$step_options_fix_calls
# look for strings in the binary 
perform_step find_strings none $SECURITY_TRANSFORMS_HOME/bin/find_strings.exe $cloneid $step_options_find_strings

#
# analyze binary for string signatures
#
perform_step appfw find_strings $PEASOUP_HOME/tools/do_appfw.sh $arch_bits $newname.ncexe logs/find_strings.log $step_optoins_appfw

#
# protect_pov
#
perform_step protect_pov fill_in_indtargs $PEASOUP_HOME/tools/do_protect_pov.sh $PWD/a.ncexe $name $PWD/crash.pov.cso $step_options_protect_pov
if [ -f crash.pov.cso  ]; then
	step_options_watch_allocate="$step_options_watch_allocate --warning_file=crash.pov.cso"
fi

#
# check signatures to determine if we know which program this is.
#
perform_step determine_program find_strings $PEASOUP_HOME/tools/match_program.sh 

# If we ran determine program and got a log, then see if we were successful.
if [ -f logs/determine_program.log ]; then
	program=$(cat logs/determine_program.log |grep "Program is a version of "|sed -e "s/Program is a version of .//" -e "s/.$//")
fi

if [[ "$program" != "" ]]; then
	echo "Detected program is a version of '$program'"

	manual_test_script=$PEASOUP_HOME/tests/$program/test_script.sh

	if [[ -f "$manual_test_script" ]];then
		#check if the selected script succeeds
		#I'm currently capping the validation run to 6 minutes
		#to avoid the case where every test times out, but doesn't
		#invalidate the test. 
		eval timeout 360 $manual_test_script `pwd`/$newname.ncexe `pwd`/$newname.ncexe &>logs/script_validation.log
		
		if [[ ! $? -eq 0 ]]; then
			echo "Manual Script Failure: test script fails to validate original program, ignoring selected script."
			manual_test_script=""
		fi
	else
		echo "Manual Test Script: $manual_test_script Not Found."
		manual_test_script=""
	fi
else
	echo "Program not detected in signature database."
fi

#At this point we will know if manual testing should be turned off automatically
#i.e., we will know if a manual_test_script file exists.
if [ -z $manual_test_script ]; then
	phases_spec=" $phases_spec manual_test=off"
else
	phases_spec=" $phases_spec manual_test=on"
fi

#
# Run script to setup manual tests
#
perform_step manual_test none $PEASOUP_HOME/tools/do_manualtests.sh $name $protected_exe $manual_test_script $manual_test_coverage_file

#
# remove the parts of the annotation file not needed at runtime
#
perform_step fast_annot meds_static $PEASOUP_HOME/tools/fast_annot.sh


#
# sfuzz: simple fuzzing to find crashes and record crashing instruction
# @todo: 2nd arg is the benchmark name but we're currently passing in
#        the binary in
# 
perform_step sfuzz none $PEASOUP_HOME/tools/do_sfuzz.sh $newname.ncexe $orig_exe crash.sfuzz.cso
# if crash found, feed the cso file to the watch allocate step
if [ -f crash.sfuzz.cso  ]; then
	step_options_watch_allocate="$step_options_watch_allocate --warning_file=crash.sfuzz.cso"
fi

#
# cinderella: infer malloc and other libc functions
#
perform_step cinderella clone,fill_in_indtargs,fill_in_cfg $PEASOUP_HOME/tools/do_cinderella.sh $cloneid

#
# For CGC, pad malloc
#
perform_step cgc_hlx cinderella $SECURITY_TRANSFORMS_HOME/bin/cgc_hlx.exe --varid=$cloneid $step_options_cgc_hlx

#
# Do P1/Pn transform.
#
#perform_step p1transform meds_static,clone $PEASOUP_HOME/tools/do_p1transform.sh $cloneid $newname.ncexe $newname.ncexe.annot $PEASOUP_HOME/tools/bed.sh $PN_TIMEOUT_VALUE $step_options_p1transform
		
#
# Do integer transform.
#
if [ -z "$program" ]; then
   program="unknown"
fi

perform_step integertransform meds_static,clone $PEASOUP_HOME/tools/do_integertransform.sh $cloneid $program $CONCOLIC_DIR $INTEGER_TRANSFORM_TIMEOUT_VALUE $step_options_integertransform

#
# perform step to instrument pgm with return shadow stack
#
perform_step ret_shadow_stack meds_static,clone $PEASOUP_HOME/tools/do_rss.sh --varid $cloneid  $step_options_ret_shadow_stack

#
# Do Twitcher transform step if twitcher is present
#
if [[ "$TWITCHER_HOME" != "" && -d "$TWITCHER_HOME" ]]; then
	perform_step twitchertransform none $TWITCHER_HOME/twitcher-transform/do_twitchertransform.sh $cloneid $program $CONCOLIC_DIR $TWITCHER_TRANSFORM_TIMEOUT_VALUE
fi

# input filtering
perform_step input_filtering clone,fill_in_indtargs,fill_in_cfg $SECURITY_TRANSFORMS_HOME/bin/watch_syscall.exe  --varid $cloneid --do_input_filtering $step_options_input_filtering

# watch syscalls
perform_step watch_allocate clone,fill_in_indtargs,fill_in_cfg,pdb_register $SECURITY_TRANSFORMS_HOME/bin/watch_syscall.exe  --varid $cloneid --do_sandboxing $step_options_watch_allocate

#
# check for any steps turned on by the --step option that aren't explicitly mentioned.
# if found, run the step as a plugin to $PS
#
do_plugins

# generate aspri, and assemble it to bspri
perform_step generate_spri mandatory $SECURITY_TRANSFORMS_HOME/bin/generate_spri.exe $($PEASOUP_HOME/tools/is_so.sh a.ncexe) $cloneid a.irdb.aspri

# hack to work with cgc file size restrictions.
stratafier_file=`ls -1 *nostrip 2>/dev/null |head -1` 
if [ "X$stratafier_file" = "X" ]; then 
	stratafier_file=stratafier.o.exe
fi
perform_step spasm mandatory $SECURITY_TRANSFORMS_HOME/bin/spasm a.irdb.aspri a.irdb.bspri a.ncexe $stratafier_file libstrata.so.symbols 

perform_step fast_spri spasm $PEASOUP_HOME/tools/fast_spri.sh a.irdb.bspri a.irdb.fbspri 

# preLoaded_ILR step
perform_step preLoaded_ILR1 fast_spri $STRATA_HOME/tools/preLoaded_ILR/generate_hashfiles.exe a.irdb.fbspri 
perform_step preLoaded_ILR2 preLoaded_ILR1 $PEASOUP_HOME/tools/generate_relocfile.sh a.irdb.fbspri


# put a front end in front of a.stratafied which opens file 990 for strata to read.
perform_step spawner stratafy_with_pc_confine  $PEASOUP_HOME/tools/do_spawner.sh 

# put a front end in front of a.stratafied which opens file 990 for strata to read.
perform_step get_pins spasm,fast_spri  $PEASOUP_HOME/tools/get_pins.sh 

# zipr
perform_step zipr clone,fill_in_indtargs,fill_in_cfg,pdb_register env LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ZIPR_INSTALL/lib $ZIPR_INSTALL/bin/zipr.exe --variant $cloneid --zipr:objcopy $PS_OBJCOPY $step_options_zipr

# copy TOCTOU tool here if it exists
if [[ "$CONCURRENCY_HOME/toctou_tool" != "" && -d "$CONCURRENCY_HOME/toctou_tool" ]]; then
	perform_step toctou none $CONCURRENCY_HOME/do_toctou.sh
fi

if [[ "$CONCURRENCY_HOME/deadlock" != "" && -d "$CONCURRENCY_HOME/deadlock" ]]; then
    # copy deadlock tool here if it exists
	perform_step deadlock none $CONCURRENCY_HOME/do_deadlock.sh
    # enable some jitter in the scheduling
	perform_step schedperturb none $CONCURRENCY_HOME/do_schedperturb.sh
fi

#
#select the output file name to use -- b.out.addseg if zipr is on.
#
is_step_on zipr
zipr_on=$?
if [ $zipr_on -eq 0 ]; then 
	my_outfile=$newdir/a.sh
else
	my_outfile=$newdir/c.out
fi

# AT 
perform_step cgc_at_string none $DAFFY_HOME/anti_tamper/string_table_trick.sh $(basename $my_outfile)

# Basic sanity check to make sure protected CB is ok
perform_step cgc_sanity_check none $PEASOUP_HOME/tools/cgc_sanity_check.sh $PWD/a.ncexe ${PWD}/$(basename $my_outfile)

#
# create a report for all of ps_analyze.
#
ps_endtime=`$PS_DATE` 
report_logs


# go back to original directory
cd - > /dev/null 2>&1



# copy output file into requested location.
cp $my_outfile $protected_exe

cd $newdir

# gather stats into JSON format
python $PEASOUP_HOME/tools/gather_stats.py logs/*.log > logs/stats.json

# make sure we only do this once there are no more updates to the peasoup_dir
perform_step installer none $PEASOUP_HOME/tools/do_installer.sh $PWD $protected_exe

# exit thanos cleanly
printf "COMMIT_ALL" > $input_pipe
read -r commit_res < $output_pipe
if [ "$commit_res" != "COMMIT_ALL_OK"  ]; then
        echo A critical step was necessary, but failed.
        echo To know exactly which step failed, source set the DEBUG_STEPS env var.
        errors=1;
fi
printf "TERMINATE" > $input_pipe 

cd - > /dev/null 2>&1


# we're done; cancel timer
if [ ! -z $TIMER_PID ]; then
	kill -9 $TIMER_PID
fi

check_steps_completed

#
# return success if we created a script to invoke the pgm and zipr is off. 
#
if [ -f $protected_exe ]; then 
	if [ $errors = 1 ]; then
		echo
		echo
		echo "*******************************"
		echo "* Warning: Some steps failed! *"
		echo "*******************************"
		if [ $record_stats -eq 1 ]; then
			$PEASOUP_HOME/tools/db/job_spec_update.sh "$JOBID" 'partial' "$ps_endtime" 
		fi
		exit 2;
	elif [ $warnings = 1 ]; then
		echo
		echo
		echo "**********************************************"
		echo "* Warning: Some steps had critical warnings! *"
		echo "**********************************************"
		if [ $record_stats -eq 1 ]; then
			$PEASOUP_HOME/tools/db/job_spec_update.sh "$JOBID" 'partial' "$ps_endtime" 
		fi
		exit 1;
	
	else
		if [ $record_stats -eq 1 ]; then
			$PEASOUP_HOME/tools/db/job_spec_update.sh "$JOBID" 'success' "$ps_endtime" 
		fi
		exit 0;
	fi

else
		echo "**************************************"
		echo "*Error: failed to create output file!*"
		echo "*    Cannot protect this program.    *"
		echo "**************************************"
	if [ $record_stats -eq 1 ]; then
		$PEASOUP_HOME/tools/db/job_spec_update.sh "$JOBID" 'error' "$ps_endtime"
	fi
	exit 255;
fi
