#!/bin/zsh --no-rcs

#######################################################################################
#
#    THIS SCRIPT IS PROVIDED BY THE MACOS SECURITY COMPLIANCE PROJECT (MSCP) "AS IS". 
#    IN NO EVENT SHALL MSCP BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
#    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, LOSS OF USE, 
#    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
#    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
#    OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SCRIPT, EVEN IF ADVISED 
#    OF THE POSSIBILITY OF SUCH DAMAGE.
#    
#######################################################################################
#    
#    MSCP COMPLIANCE SCRIPT BUILD INFORMATION
#
#    RELEASE: {{ mscp_release }}
#    BASELINE: {{ baseline_name }}
#    DATE: {{ todays_date }}
#    
#    MSCP VERSION: {{ mscp_version.get('version') }} (build {{ mscp_version.get('build') }})
#
#    The information above represents the version and build of MSCP that this script
#    was generated from along with which baseline was used and the date it was generated. 
#    
#######################################################################################
#    
#    Purpose and Use
#
#    The purpose of this script is to restore the default macOS state for the settings 
#    applied using the baseline generated by {{ mscp_release }}.
#    Implementation of this script should undergo thorough testing and understanding 
#    prior to deployment in production environments.
#
#    The script can be run interactively or silently. 
#
#    For additional information on MSCP, please visit the project's homepage at:
#    https://github.com/usnistgov/macos_security
#      
#######################################################################################


###################  DEBUG MODE - hold shift when running the script  ###################

shiftKeyDown=$(osascript -l JavaScript -e "ObjC.import('Cocoa'); ($.NSEvent.modifierFlags & $.NSEventModifierFlagShift) > 1")

if [[ $shiftKeyDown == "true" ]]; then
  echo "-----DEBUG-----"
  set -o xtrace -o verbose
fi

###################  COMMANDS START BELOW THIS LINE  ###################

# Check if the current shell is Zsh
if [[ -z "$ZSH_NAME" ]]; then
  echo "ERROR: This script must be run in Zsh."
  exit 1
fi

## Must be run as root
if [[ $EUID -ne 0 ]]; then
  echo "This script must be run as root"
  exit 1
fi

# path to PlistBuddy
plb="/usr/libexec/PlistBuddy"

# get the currently logged in user
CURRENT_USER=$( /usr/bin/defaults read /Library/Preferences/com.apple.loginwindow lastUserName )
CURR_USER_UID=$(/usr/bin/id -u $CURRENT_USER)

# get system architecture
arch=$(/usr/bin/arch)

# configure colors for text
RED='\e[31m'
STD='\e[39m'
GREEN='\e[32m'
YELLOW='\e[33m'

audit_log="/Library/Logs/{{ audit_name }}_baseline.log"

baseline_name="{{ baseline_name }}"

# pause function
pause(){
  vared -p "Press [Enter] key to continue..." -c fackEnterKey
}

# logging function
logmessage(){
  local level="${2:-INFO}"
  local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  local message="$1"

  # Format: [LEVEL] [TIMESTAMP] MESSAGE
  local log_entry="[$level] [$timestamp] $message"

  # Always log to file
  echo "$log_entry" >> "$audit_log"

  # Handle quiet levels for console output
  if [[ ! $quiet ]]; then
    echo "$log_entry"
  fi
}


ask() {
  # if fix flag is passed, assume YES for everything
  if [[ $all ]]; then
    return 0
  fi

  while true; do
    if [ "${2:-}" = "Y" ]; then
      prompt="Y/n"
      default=Y
    elif [ "${2:-}" = "N" ]; then
      prompt="y/N"
      default=N
    else
      prompt="y/n"
      default=
    fi

    # Ask the question - use /dev/tty in case stdin is redirected from somewhere else
    printf "${YELLOW} $1 [$prompt] ${STD}"
    read REPLY

    # Default?
    if [ -z "$REPLY" ]; then
      REPLY=$default
    fi

    # Check if the reply is valid
    case "$REPLY" in
      Y*|y*) return 0 ;;
      N*|n*) return 1 ;;
    esac
  done
}

rule_default_state(){
  if [[ "${RULE_TO_RESTORE}" ]]; then
    if [[ "${RULE_TO_RESTORE}" == "$1" ]]; then
      logmessage "Running the command to restore the settings for: $1 ..."
      eval "$2"
    else
      NO_MATCH=0
    fi
  else 
    ask "$1 - Run the command(s)-> $2" N
    if [[ $? == 0 ]]; then
      logmessage "Running the command to restore the settings for: $1 ..."
      eval "$2"
    fi
  fi
}

run_default_state(){
  # append to existing logfile
  if [[ "${RULE_TO_RESTORE}" ]]; then
    logmessage "Beginning restoration of default settings for ${RULE_TO_RESTORE}"
  else
    logmessage "Beginning restoration of default settings"
  fi

  if [[ ! $all && -z $RULE_TO_RESTORE ]]; then
    ask 'THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. WOULD YOU LIKE TO CONTINUE? ' N

    if [[ $? != 0 ]]; then
      exit 1
    fi
  fi

  {% for profile in baseline.profile %}
  {% for rule in profile.rules %}
    {% if "manual" not in rule.tags and "Excluded" not in rule.section %}
      {% include "restore.jinja" %}
    {% endif %}
  {% endfor %}
  {% endfor %}

} 2>/dev/null

usage=(
    "Usage: ${CMD:=${0##*/}} [--all] [--quiet=<value>] [--rule <rule_id>]"
    " "
    "Optional parameters:"
    "--all              :   run the restore to default state on all rules"
    "--quiet            :   do not display log messages to stdout"
    "--rule <rule_id>   :   restore default state for rule_id"
  )

set -- "$@" "${EOL:=$(printf '\1\3\3\7')}"

# Look for managed arguments for compliance script
if [[ $# -eq 0 ]];then
    compliance_args=$(/usr/bin/osascript -l JavaScript << 'EOS'
var defaults = $.NSUserDefaults.alloc.initWithSuiteName('org.{{ baseline_name }}.audit');
var args = defaults.objectForKey('compliance_args');
if (args && args.count > 0) {
    var result = [];
    for (var i = 0; i < args.count; i++) {
        result.push(ObjC.unwrap(args.objectAtIndex(i)));
    }
    result.join(' ');
    }
EOS
)
    if [[ -n "$compliance_args" ]]; then
        logmessage "Managed arguments found for compliance script, setting: $compliance_args"
        set -- ${(z)compliance_args}
    fi
fi
  
zparseopts -D -E -help=flag_help -all=all -quiet:=quiet || { print -l $usage && return }

[[ -z "$flag_help" ]] || { print -l $usage && return }

{% raw %}
# check to see if a rule_id was passed, if so define it for processing
if (( ${#rule} >= 2 )); then
  RULE_TO_RESTORE=${rule[2]}
else
  RULE_TO_RESTORE=''
fi
{% endraw %}

run_default_state

if [[ $NO_MATCH ]]; then logmessage "Could not restore default state for $RULE_TO_RESTORE : no matching rule found." ERROR; fi