#!/bin/bash
# OSx OSSEC Agent Installer
# Name: osx-agent-installer.sh
# Copyright Atomicorp 2025
# License: Commercial. Unauthorized redistribution prohibited.

# Branding
PRODUCT_NAME="Atomic OSSEC Agent"
BANNER_TEXT="Atomic OSSEC Agent Installer"
VERSION="6.3.5"
PACKAGE_NAME="ossec-hids-agent"

# Variables
PROTOCOL=udp
PORT=1514  # Agent communication port
ENROLLMENT_PORT=1518  # HTTPS enrollment port
ROOT_UID=0
IS_UPDATE=0
ARCH=$(arch)
GROUP="default"

#####################
# Functions
#####################
usage_menu () {
  echo "${BANNER_TEXT}"
  echo "Version: ${VERSION}"
  echo "Usage: osx-agent-installer.sh <param>"
  echo "    -h: Shows this message."
  echo
  echo "Mandatory"
  echo "    --server_ip:   IP address of Manager."
  echo
  echo "Optional"
  echo "    --agent_name: Specify the name to use when registering the agent. [Default: hostname]"
  echo "    --port: Specify the agent communication port. [Default: 1514]"
  echo "    --udp/--tcp: Protocol to use to communicate with Manager. [Default: udp]"
  echo "    --group: Group to assign agent to [Default: default]"
  echo "    --password: Registration authentication password"
  echo "    --uninstall: Removes the ${PRODUCT_NAME} from this device."
  echo "    --update: Updates the ${PRODUCT_NAME} on this device."
  echo
}

password_auth() {
    if [ -n "$PASSWORD" ]; then
        echo "$PASSWORD" > /var/ossec/etc/authd.pass
        chmod 640 /var/ossec/etc/authd.pass
        chown root:ossec /var/ossec/etc/authd.pass
        echo "  INFO: Password added to /var/ossec/etc/authd.pass"
    fi
}

request_agent_key() {
    server_ip="$1"
    debug_log "Requesting agent key from $server_ip using request_agent_key"

    # URL encode the password if it exists
    if [ -n "$PASSWORD" ]; then
        # Basic URL encoding for spaces and special characters
        encoded_password=$(echo "${PRODUCT_NAME} PASS: ${PASSWORD}" | sed 's/ /%20/g' | sed 's/\[/%5B/g' | sed 's/\]/%5D/g' | sed 's/|/%7C/g' | sed 's/&/%26/g' | sed 's/\+/%2B/g' | sed 's/:/%3A/g' | sed 's/;/%3B/g' | sed 's/=/%3D/g' | sed 's/\?/%3F/g' | sed 's/@/%40/g' | sed 's/#/%23/g' | sed 's/\$/%24/g' | sed 's/(/%28/g' | sed 's/)/%29/g' | sed 's/,/%2C/g' | sed 's/</%3C/g' | sed 's/>/%3E/g' | sed 's/\\/%5C/g' | sed 's/\^/%5E/g' | sed 's/`/%60/g' | sed 's/{/%7B/g' | sed 's/}/%7D/g' | sed 's/\[/%5B/g' | sed 's/\]/%5D/g' | sed 's/|/%7C/g' | sed 's/~/%7E/g')
    fi

    # Build the base URL with common parameters
    local base_url="https://${server_ip}:${ENROLLMENT_PORT}/agent_register/?key=none"
    
    # Add hostname parameter
    if [ -n "$AGENT_NAME" ]; then
        base_url="${base_url}&hostname=${AGENT_NAME}"
    else
        base_url="${base_url}&hostname=$(hostname)"
    fi
    
    # Add group parameter if specified
    if [ -n "$GROUP" ] && [ "$GROUP" != "default" ]; then
        base_url="${base_url}&group=${GROUP}"
    fi
    
    # Add password parameter if specified
    if [ -n "$PASSWORD" ]; then
        base_url="${base_url}&password=${encoded_password}"
    fi

    curl -k -o /var/ossec/etc/client.keys.tmp "$base_url"
    local status=$?

    if [[ $status -eq 0 ]]; then
        # Check if the response contains an error message
        if grep -q "ERROR:" /var/ossec/etc/client.keys.tmp; then
            error_msg=$(grep "ERROR:" /var/ossec/etc/client.keys.tmp)
            echo "  ERROR: $error_msg"
            rm -f /var/ossec/etc/client.keys.tmp
            return 1
        fi
        
        # Check if the file is empty or doesn't contain a valid key
        if [ ! -s /var/ossec/etc/client.keys.tmp ] || ! grep -q "'" /var/ossec/etc/client.keys.tmp; then
            echo "  ERROR: Invalid or empty response from server"
            rm -f /var/ossec/etc/client.keys.tmp
            return 1
        fi
        
        echo "Request successful. Response saved to client.keys.tmp"
        awk -F\' '{print $2}' /var/ossec/etc/client.keys.tmp > /var/ossec/etc/client.keys
        chmod 640 /var/ossec/etc/client.keys
        chown root:ossec /var/ossec/etc/client.keys
        rm -f /var/ossec/etc/client.keys.tmp
        return 0
    else
        echo "  ERROR: Request failed with status code $status."
        rm -f /var/ossec/etc/client.keys.tmp
        return 1
    fi
}

osx_configure () {
  O_CONF="/var/ossec/etc/ossec.conf"

  # Check if the config file exists
  if [ ! -f "$O_CONF" ]; then
      echo "  ERROR: Configuration file $O_CONF not found!"
      exit 1
  fi

  # Update <server> section. Each tag lives on its own line in the default
  # ossec.conf, so match per-line rather than trying to span <server>...</server>
  # (BSD sed has no multiline mode).
  sed -i '' "s|<address>[^<]*</address>|<address>${SERVER_IP}</address>|g" "$O_CONF"
  sed -i '' "s|<port>[^<]*</port>|<port>${PORT}</port>|g" "$O_CONF"
  sed -i '' "s|<protocol>[^<]*</protocol>|<protocol>${PROTOCOL}</protocol>|g" "$O_CONF"
  # Update the <enrollment> section
  sed -i '' "s|<manager_address>.*</manager_address>|<manager_address>${SERVER_IP}</manager_address>|g" "$O_CONF"

  # Verify if the changes were applied successfully
  if ! grep -q "<address>${SERVER_IP}</address>" "$O_CONF" \
     || ! grep -q "<port>${PORT}</port>" "$O_CONF" \
     || ! grep -q "<protocol>${PROTOCOL}</protocol>" "$O_CONF"; then
      echo "  ERROR: Agent configuration failed!"
      exit 1
  fi

  echo "  INFO: Agent configuration updated successfully."
}

# Add debug function near the top with other functions
debug_log() {
    if [ "$DEBUG" = "true" ]; then
        echo "DEBUG: $1"
    fi
}

verify_package() {
    local package="$1"
    echo "Verifying package: $package"
    
    # Check if package exists
    if [ ! -f "$package" ]; then
        echo "ERROR: Package file not found: $package"
        return 1
    fi
    
    # Check package size
    local size=$(stat -f%z "$package")
    echo "Package size: $size bytes"
    
    # Verify package signature
    echo "Checking package signature..."
    pkgutil --check-signature "$package"
    local sig_status=$?
    if [ $sig_status -ne 0 ]; then
        echo "WARNING: Package signature verification failed"
    fi
    
    # List package contents
    echo "Listing package contents..."
    pkgutil --expand "$package" /tmp/pkg_expand
    if [ $? -eq 0 ]; then
        echo "Package structure appears valid"
        rm -rf /tmp/pkg_expand
    else
        echo "ERROR: Failed to expand package for verification"
        return 1
    fi
    
    return 0
}

osx_register () {
    debug_log "Starting agent registration"
    debug_log "Group setting: ${GROUP}"
    debug_log "Agent name setting: ${AGENT_NAME}"
    debug_log "Authentication IP: ${AUTH_IP}"
    debug_log "Protocol: ${PROTOCOL}"
    
    debug_log "Using HTTPS enrollment"
    request_agent_key ${AUTH_IP}
    if [ $? -ne 0 ]; then
        echo "  ERROR: Failed to register agent using HTTPS enrollment"
        exit 1
    fi
}

osx_install() {
  VERSION=$(curl -sk https://${SERVER_IP}/channels/awp-hub-repo/osx/VERSION | grep "^VERSION=" | awk -F'=' '{print $2}' | tr -d ' \r\n')
  RELEASE=$(curl -sk https://${SERVER_IP}/channels/awp-hub-repo/osx/VERSION | grep "^RELEASE=" | awk -F'=' '{print $2}' | tr -d ' \r\n')
  AWP_VERSION=$(curl -sk https://${SERVER_IP}/channels/awp-hub-repo/osx/VERSION | grep "^AWP_AGENT_VERSION=" | awk -F'=' '{print $2}' | tr -d ' \r\n')
  PACKAGE="${PACKAGE_NAME}-${VERSION}-${RELEASE}.osx.${ARCH}.pkg"
  AWP_PACKAGE="awp-agent-${AWP_VERSION}.${ARCH}.pkg"

  debug_log "VERSION: ${VERSION}"
  debug_log "RELEASE: ${RELEASE}"
  debug_log "AWP_VERSION: ${AWP_VERSION}"
  debug_log "PACKAGE: ${PACKAGE}"
  debug_log "AWP_PACKAGE: ${AWP_PACKAGE}"

  # Download and install OSSEC agent
  if [ ! -f "${PACKAGE}" ]; then
    echo "Downloading  https://${SERVER_IP}/channels/awp-hub-repo/osx/${PACKAGE}... "
    curl -sk https://${SERVER_IP}/channels/awp-hub-repo/osx/${PACKAGE} -O
    echo
  fi

  if  pkgutil --packages | egrep -q atomicorp.ossec.agent ; then
    echo
    echo "    ERROR: ${PRODUCT_NAME} installation already detected."
    echo "    exiting ..."
    echo
    exit 1
  fi

  echo "Installing ${PACKAGE}..."
  installer -pkg "${PACKAGE}" -target /

  if [ "$?" -ne "0" ]; then
    echo "   ERROR: Installing ${PACKAGE}"
    installer -pkg "${PACKAGE}" -target /
    echo "   exiting ..."
    echo
    exit 1
  fi

  echo "VERSION=${VERSION}" > /var/ossec/etc/.version
  echo "RELEASE=${RELEASE}" >> /var/ossec/etc/.version

  # Configure the agent
  echo "Configuring ${PRODUCT_NAME} ... "
  osx_configure

  # Register the agent
  echo "Registering agent with manager ${SERVER_IP} "
  password_auth
  osx_register

  if [ $? -ne 0 ];then
      echo
      echo "  ERROR: agent-auth failed during registration"
      echo "  exiting ..."
      exit 1
  fi

  # Download and install AWP agent
  echo "Installing AWP agent..."
  
  # Download AWP agent package if not present
  if [ ! -f "${AWP_PACKAGE}" ]; then
    echo "Downloading https://${SERVER_IP}/channels/awp-hub-repo/osx/${AWP_PACKAGE}..."
    curl -sk https://${SERVER_IP}/channels/awp-hub-repo/osx/${AWP_PACKAGE} -O
    if [ $? -ne 0 ]; then
      echo "  ERROR: Failed to download AWP agent package from ${SERVER_IP}"
      echo "  Please verify the server IP and network connectivity"
      exit 1
    fi
  fi

  # Install AWP agent package
  echo "Installing ${AWP_PACKAGE}..."
  installer -pkg "${AWP_PACKAGE}" -target /
  if [ "$?" -ne "0" ]; then
    echo "  ERROR: Failed to install AWP agent package"
    echo "  Please check the package integrity and try again"
    exit 1
  fi

  echo "  INFO: AWP agent installed successfully"

  cat > /etc/ossec-init.conf << EOF
DIRECTORY="/var/ossec"
NAME="${PRODUCT_NAME}"
VERSION="v${VERSION}"
RELEASE="${RELEASE}"
DATE="$(date)"
TYPE="agent"
EOF
  chmod 644 /etc/ossec-init.conf
  chown root:wheel /etc/ossec-init.conf

  echo
  echo "Installation Complete!"
}


################
# Main
################


if [[ "$1" == "--uninstall" ]]; then
    echo "    INFO: Uninstalling ${PRODUCT_NAME} and awp-agent..."
    echo "    Stopping service..."
    /var/ossec/bin/ossec-control stop

    echo "    Removing com.atomicorp.awp.agent package..."
    if pkgutil --packages | grep -q com.atomicorp.awp.agent; then
        pkgutil --forget com.atomicorp.awp.agent
    fi

    echo "    Removing com.atomicorp.ossec.agent package..."
    pkgutil --forget com.atomicorp.ossec.agent

    echo "    Removing /var/ossec directory..."
    rm -rf /var/ossec

    echo "    Removing downloaded package..."
    rm -f ossec-hids-agent-*.pkg

    echo "    Removing downloaded AWP package..."
    rm -f awp-agent-*.pkg

    echo "Uninstall Complete!"
    exit 0
fi


while [[ $# -gt 0 ]]; do
  key="$1"

  case $key in
    --server_ip)
      SERVER_IP="$2"
      shift 2
      ;;
    --auth_ip)
      AUTH_IP="$2"
      shift 2
      ;;
    --agent_name)
      AGENT_NAME="$2"
      shift 2
      ;;
    --update)
      IS_UPDATE=1
      shift
      ;;
    --tcp)
      PROTOCOL="tcp"
      shift
      ;;
    --udp)
      PROTOCOL="udp"
      shift
      ;;
    --group)
      GROUP="$2"
      shift 2
      ;;
    --password)
      PASSWORD="$2"
      shift 2
      ;;
    --port)
      PORT="$2"
      shift 2
      ;;
    --debug)
      DEBUG="true"
      shift
      ;;
    *)
      echo "Unknown option: $1"
      usage_menu
      exit 1
      ;;
  esac
done


if [[ -z "$SERVER_IP" ]]; then
  echo "    ERROR: Manager IP address was not specified."
  usage_menu
  exit 1
fi


if [ ! ${AUTH_IP} ]; then
  AUTH_IP=$SERVER_IP
fi

if [ ! "$UID" ]; then
  UID=$(id -u)
fi

if [ "$UID" -ne "$ROOT_UID" ] ; then
  echo
  echo "    ERROR: You must be root to run this program."
  echo "    exiting..."
  echo
  exit 1
fi

if [[ "$IS_UPDATE" -eq "1" ]]; then
  VERSION=$(curl -s -k https://${SERVER_IP}/channels/awp-hub-repo/osx/VERSION | grep VERSION | awk -F'=' '{print $2}')
  RELEASE=$(curl -s -k https://${SERVER_IP}/channels/awp-hub-repo/osx/VERSION | grep RELEASE | awk -F'=' '{print $2}')

  PACKAGE="${PACKAGE_NAME}-${VERSION}-${RELEASE}.osx.${ARCH}.pkg"

  if [ ! -f ${PACKAGE} ]; then
    echo "Downloading https://${SERVER_IP}/channels/awp-hub-repo/osx/${PACKAGE} ... "
    curl -s -kf https://${SERVER_IP}/channels/awp-hub-repo/osx/${PACKAGE} -o ${PACKAGE}
    if [ $? -ne 0 ]; then
      echo "  ERROR:  could not download https://${SERVER_IP}/channels/awp-hub-repo/osx/${PACKAGE}"
    fi
    echo
  fi

  cp /var/ossec/etc/ossec.conf .

  echo "Installing ${PACKAGE} ..."
  installer -pkg ${PACKAGE} -target /

  if [ "$?" -ne "0" ]; then
    echo "   ERROR: Installing ${PACKAGE}"
    installer -pkg ${PACKAGE} -target /
    echo "   exiting ..."
    echo
    exit 1
  fi

  echo "VERSION="${VERSION} > /var/ossec/etc/.version
  echo "RELEASE="${RELEASE} >> /var/ossec/etc/.version

  mv ./ossec.conf /var/ossec/etc/ossec.conf

  /var/ossec/bin/ossec-control restart
  echo "Update Complete!"
  exit 0

fi

osx_install
