#!/usr/bin/bash

foreman_proxy_uri () {
  echo "https://$SERVER:$PORT"
}

# Params: download_path
download_uri () {
  local uri
  uri=$(foreman_proxy_uri)
  echo "$uri$1"
}

upload_uri () {
  echo "$(foreman_proxy_uri)/compliance/arf/$POLICY_ID"
}

# Ensures that a file exists by downloading it if necessary
# Params: filename_path, download_path, type_humanized
ensure_file () {
  if [ -f "$1" ]; then
    return 0
  else
    echo "File $1 is missing. Downloading it from proxy."
    mkdir -p "$(dirname "$1")"
    local uri
    uri=$(download_uri "$2")
    echo "Download $3 xml from: $uri"
    local curl_ssl_opts=(--silent --show-error --cacert "$CA_FILE" --cert "$HOST_CERTIFICATE" --key "$HOST_PRIVATE_KEY")
    if [ -n "$CIPHERS" ]; then
      curl_ssl_opts=("${curl_ssl_opts[@]}" --ciphers "${CIPHERS[*]}")
    fi
    local result
    if ! result=$(curl "${curl_ssl_opts[@]}" --output "$1" "$uri" 2>&1); then
      echo "$3 is missing and download failed with error: $result"
      exit 5
    fi
  fi
}

ensure_scan_files () {
  ensure_file "$POLICY_CONTENT_PATH" "$POLICY_DOWNLOAD_PATH" "SCAP content"
  if [ -n "$POLICY_TAILORING_PATH" ]; then
    ensure_file "$POLICY_TAILORING_PATH" "$POLICY_TAILORING_DOWNLOAD_PATH" "Tailoring file"
  fi
}

normalize_string_value() {
  echo "$1" | sed -e "s/^\s*//" -e "s/\s*$//" | sed -e "s/^'//" -e "s/'$//"
}

load_config () {
  # To use this function in tests with different config files
  local config_file
  config_file="$1"

  if [ -f "$config_file" ]; then
    eval "$(awk -v policy_id="$POLICY_ID" -F": " '
    BEGIN { policy_found="false"; }
    /^:server\s*:/ {
      print "SERVER=" "\"" $2 "\"";
    }
    /^:port\s*:/ {
      print "PORT=" "\"" $2 "\"";
    }
    /^:timeout\s*:/ {
      print "TIMEOUT=" "\"" $2 "\"";
    }
    /^:fetch_remote_resources\s*:/ {
      print "FETCH_REMOTE_RESOURCES=" "\"" $2 "\"";
    }
    /^:http_proxy_server\s*:/ {
      print "HTTP_PROXY_SERVER=" "\"" $2 "\"";
    }
    /^:http_proxy_port\s*:/ {
      print "HTTP_PROXY_PORT=" "\"" $2 "\"";
    }
    /^:ca_file\s*:/ {
      print "CA_FILE=" "\"" $2 "\"";
    }
    /^:host_certificate\s*:/ {
      print "HOST_CERTIFICATE=" "\"" $2 "\"";
    }
    /^:host_private_key\s*:/ {
      print "HOST_PRIVATE_KEY=" "\"" $2 "\"";
    }
    /^:ciphers\s*:/ {
      print "CIPHERS=" "\"" $2 "\"";
    }

    /^([0-9]+)\s*:/ {
      sub(/\s*:\s*/, "");
      current_policy_id=$1;
      if (current_policy_id == policy_id) { policy_found="true"; }
    }
    /^  :profile\s*:/ {
      print "POLICY_" current_policy_id "_PROFILE=" "\"" $2 "\"";
    }
    /^  :content_path\s*:/ {
      print "POLICY_" current_policy_id "_CONTENT_PATH=" "\"" $2 "\"";
    }
    /^  :download_path\s*:/ {
      print "POLICY_" current_policy_id "_DOWNLOAD_PATH=" "\"" $2 "\"";
    }
    /^  :tailoring_path\s*:/ {
      print "POLICY_" current_policy_id "_TAILORING_PATH=" "\"" $2 "\"";
    }
    /^  :tailoring_download_path\s*:/ {
      print "POLICY_" current_policy_id "_TAILORING_DOWNLOAD_PATH=" "\"" $2 "\"";
    }
    END { print "POLICY_FOUND=" "\"" policy_found "\""; }
    ' "$config_file")"
  else
    echo "Config file could not be loaded"
    echo "$config_file does not exist"
    exit 1
  fi

  if [ "$POLICY_FOUND" == "false" ]; then
    echo "Policy id $POLICY_ID not found"
    exit 1
  fi

  # Remove surrounding spaces and single quotes for all values
  SERVER=$(normalize_string_value "$SERVER")
  PORT=$(normalize_string_value "$PORT")
  TIMEOUT=$(normalize_string_value "$TIMEOUT")
  FETCH_REMOTE_RESOURCES=$(normalize_string_value "$FETCH_REMOTE_RESOURCES")
  HTTP_PROXY_SERVER=$(normalize_string_value "$HTTP_PROXY_SERVER")
  HTTP_PROXY_PORT=$(normalize_string_value "$HTTP_PROXY_PORT")
  CA_FILE=$(normalize_string_value "$CA_FILE")
  HOST_CERTIFICATE=$(normalize_string_value "$HOST_CERTIFICATE")
  HOST_PRIVATE_KEY=$(normalize_string_value "$HOST_PRIVATE_KEY")

  # Remove brackets and spaces from ciphers list
  # Turn ["cipher1", "cipher2"] into "cipher1,cipher2" or
  # Turn ["cipher1:cipher2"] into "cipher1:cipher2"
  CIPHERS=$(echo "$CIPHERS" | tr -d ' ' | sed -e "s/^\[//" -e "s/\]$//")

  POLICY_PROFILE="POLICY_${POLICY_ID}_PROFILE"
  POLICY_PROFILE=${!POLICY_PROFILE}
  POLICY_PROFILE=$(normalize_string_value "$POLICY_PROFILE")
  POLICY_CONTENT_PATH="POLICY_${POLICY_ID}_CONTENT_PATH"
  POLICY_CONTENT_PATH=${!POLICY_CONTENT_PATH}
  POLICY_CONTENT_PATH=$(normalize_string_value "$POLICY_CONTENT_PATH")
  POLICY_DOWNLOAD_PATH="POLICY_${POLICY_ID}_DOWNLOAD_PATH"
  POLICY_DOWNLOAD_PATH=${!POLICY_DOWNLOAD_PATH}
  POLICY_DOWNLOAD_PATH=$(normalize_string_value "$POLICY_DOWNLOAD_PATH")
  POLICY_TAILORING_PATH="POLICY_${POLICY_ID}_TAILORING_PATH"
  POLICY_TAILORING_PATH=${!POLICY_TAILORING_PATH}
  POLICY_TAILORING_PATH=$(normalize_string_value "$POLICY_TAILORING_PATH")
  POLICY_TAILORING_DOWNLOAD_PATH="POLICY_${POLICY_ID}_TAILORING_DOWNLOAD_PATH"
  POLICY_TAILORING_DOWNLOAD_PATH=${!POLICY_TAILORING_DOWNLOAD_PATH}
  POLICY_TAILORING_DOWNLOAD_PATH=$(normalize_string_value "$POLICY_TAILORING_DOWNLOAD_PATH")
}

http_proxy_uri () {
  if [ -n "$HTTP_PROXY_SERVER" ] && [ -n "$HTTP_PROXY_PORT" ]; then
    echo "http://$HTTP_PROXY_SERVER:$HTTP_PROXY_PORT"
  fi
}

scan_command_env_vars () {
  local env_vars=""
  if [ -n "$(http_proxy_uri)" ]; then
    env_vars="HTTP_PROXY=$(http_proxy_uri) HTTPS_PROXY=$(http_proxy_uri)"
  fi
  echo "$env_vars"
}

# Params: version1 version2
ver_ge () {
  printf '%s\n%s' "$1" "$2" | sort -C -V -r
}

supports_local_file_option () {
  local versions
  versions=$(oscap --version)
  local status=$?
  if [ $status -ne 0 ]; then
    return 1
  fi
  local version
  version=$(echo "$versions" | awk '{print $NF}' | head -n 1)
  # OpenSCAP 1.3.6 and newer requires the `--local-files` option to use local copies of remote SDS components
  ver_ge "$version" '1.3.6'
}

local_files_subcommand () {
  # shellcheck disable=SC2153
  if supports_local_file_option && [ "$FETCH_REMOTE_RESOURCES" == "false" ]; then
    echo "--local-files /root"
  fi
}

tailoring_subcommand () {
  if [ -n "$POLICY_TAILORING_PATH" ]; then
    echo "--tailoring-file $POLICY_TAILORING_PATH"
  fi
}

results_path () {
  echo "$TMP_DIR/results.xml"
}

results_bzip_path () {
  echo "$(results_path).bz2"
}

bzip_command () {
  echo "/usr/bin/env bzip2 $(results_path)"
}

scan_command () {
  local profile=""
  if [ -n "$POLICY_PROFILE" ]; then
    profile="--profile $POLICY_PROFILE"
  fi
  local fetch_remote_resources=""
  if [ "$FETCH_REMOTE_RESOURCES" == "true" ]; then
    fetch_remote_resources="--fetch-remote-resources"
  fi
  echo "oscap xccdf eval $fetch_remote_resources $(local_files_subcommand) $profile $(tailoring_subcommand) --results-arf $(results_path) $POLICY_CONTENT_PATH"
}

run_scan () {
  local env_vars
  env_vars=$(scan_command_env_vars)
  local command
  command=$(scan_command)
  local output=""
  local status=0
  if [ -n "$env_vars" ]; then
    output=$(env "$env_vars" bash -c "$command" 2>&1)
    status=$?
  else
    output=$(bash -c "$command" 2>&1)
    status=$?
  fi
  if [ $status -eq 0 ] || [ $status -eq 2 ]; then
    printf "%s" "$output" | awk '/^WARNING:.*$/ || /^Downloading.*$/{print}'
  else
    echo "Scan failed"
    printf "%s" "$output"
    exit 2
  fi
}

scan () {
  echo "DEBUG: running: $(scan_command)"
  if [ -n "$(scan_command_env_vars)" ]; then
    echo "with ENV vars: $(scan_command_env_vars)"
  fi

  run_scan
}

bzip () {
  echo "DEBUG: running: $(bzip_command)"
  local output

  if ! output=$(eval "$(bzip_command)"); then
    echo "bzip failed"
    printf "%s" "$output"
    exit 2
  fi
}

# Params: response
print_upload_result () {
  if [ -n "$1" ]; then
    if echo "$1" | grep -Poq '"[iI][dD]":\s*"\d+"'; then
      echo "Report uploaded, report id: $(echo "$1" | grep -Po '"[iI][dD]":\s*"\d+"' | grep -Po '\d+')"
    else
      local msg
      msg=$(echo "$1" | grep -Po '"result":(\s*["]|["])+.+("}|",)+' | sed 's/".$//' | sed 's/"result":"//' | sed 's/\\//g')
      echo "Report not uploaded from proxy to Foreman server, cause: $msg"
    fi
  fi
}

upload () {
  local uri
  uri=$(upload_uri)
  echo "Uploading results to $uri"
  local curl_ssl_opts=(--silent --show-error --cacert "$CA_FILE" --cert "$HOST_CERTIFICATE" --key "$HOST_PRIVATE_KEY")
  if [ -n "$CIPHERS" ]; then
    curl_ssl_opts=("${curl_ssl_opts[@]}" --ciphers "${CIPHERS[*]}")
  fi
  local curl_headers=(--header Content-Type:text/xml --header Content-Encoding:x-bzip2)
  if [ -n "$TIMEOUT" ]; then
    curl_headers=("${curl_headers[@]}" --max-time "$TIMEOUT") # TODO: Change to --connect-timeout?
  fi
  local result
  if ! result=$(curl "${curl_ssl_opts[@]}" "${curl_headers[@]}" --data-binary "@$(results_bzip_path)" "$uri" 2>&1); then
    echo "Upload failed: $result"
    exit 4
  fi
  print_upload_result "$result"
}

# Params: skip_upload
run_in_tmpdir () {
  TMP_DIR=$(mktemp -d)
  scan
  bzip
  if [ "$1" == "false" ]; then
    upload
    # TODO: Temp dir is only removed if upload is successful.
    # If there was any error during previous steps, the temp dir is left around.
    # Doesn't seem like it's being re-used. Should we remove it in all cases?
    rm -rf "$TMP_DIR"
  fi
}

# Params: policy_id, skip_upload
run_client () {
  POLICY_ID=$1
  load_config "/etc/foreman_scap_client/config.yaml"
  ensure_scan_files
  run_in_tmpdir "$2"
}

# MAIN ENTRY

# This is Bash-only
if [ "${*: -1}" == "--skip-upload" ]; then
  SKIP_UPLOAD=true
  set -- "${@:1:$(($#-1))}" # Remove the last argument
else
  SKIP_UPLOAD=false
fi

if [ $# -eq 1 ]; then
  run_client "$1" $SKIP_UPLOAD
# Left around for compatibility
elif [ $# -eq 2 ] && [ "$1" == "ds" ]; then
  run_client "$2" $SKIP_UPLOAD
else
  echo "Usage: $0 [ds] policy_id [--skip-upload]"
  echo "  where policy_id is a key used in config file"
  exit 2
fi
