HEX
Server: LiteSpeed
System: Linux s3512.bom1.stableserver.net 4.18.0-513.11.1.lve.el8.x86_64 #1 SMP Thu Jan 18 16:21:02 UTC 2024 x86_64
User: surajaut (1797)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: //proc/self/root/proc/thread-self/root/proc/thread-self/root/opt/sp_scripts/audit.sh
#!/bin/bash
set -euo pipefail

# Pre-migration audit for a cPanel/WHM server: account totals, nameserver
# locality, custom reseller nameservers, and accounts on dedicated IPs.

for cmd in whmapi1 jq dig; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Error: required command '$cmd' not found." >&2
    exit 1
  fi
done

if [ "$(id -u)" -ne 0 ]; then
  echo "Error: this script must be run as root." >&2
  exit 1
fi

whm_call() {
  local json
  json=$(whmapi1 --output=json "$@")

  if [ "$(jq -r '.metadata.result' <<<"$json")" != "1" ]; then
    echo "Error: whmapi1 $1 failed: $(jq -r '.metadata.reason' <<<"$json")" >&2
    exit 1
  fi

  printf '%s' "$json"
}

acct_json=$(whm_call listaccts want=user,domain,owner,ip,diskused)
ns_json=$(whm_call get_nameserver_config)
ip_json=$(whm_call listips)

printf 'Accounts: %s\nCustomer data: %.2f GiB\nServer nameservers:\n' \
"$(jq '.data.acct | length' <<<"$acct_json")" \
"$(jq '[.data.acct[].diskused | sub("M$";"") | (tonumber? // 0)] | (add // 0) / 1024' <<<"$acct_json")"

resolve_host() {
  {
    dig +short A "$1" || true
    dig +short AAAA "$1" || true
  } |
  sed 's/\.$//' |
  { grep -E '^[0-9A-Fa-f.:]+$' || true; } |
  sort -u
}

# IPv4 comes from the WHM IP inventory (includes public IPs on NAT'd
# servers); listips is IPv4-only, so IPv6 still comes from the kernel.
server_ips=$(
  {
    jq -r '.data.ip[]?.ip' <<<"$ip_json"

    ip -o -6 addr show scope global 2>/dev/null |
      awk '{print $4}' |
      cut -d/ -f1 ||
      true
  } |
  sed '/^$/d' |
  sort -u
)

# Our global DNS cluster (StableDNS) is reachable under two brand
# names; both resolve to the same cluster IPs.
stabledns_ips=$(
  for i in 1 2 3 4; do
    resolve_host "ns${i}.stableserver.net"
    resolve_host "ns${i}.mysecurecloudhost.com"
  done |
  sort -u
)

jq -r '.data.nameservers[]?' <<<"$ns_json" |
while read -r ns; do
  resolved=$(resolve_host "$ns")

  if [ -z "$resolved" ]; then
    status="UNRESOLVED"
  elif grep -Fqx -f \
      <(printf '%s\n' "$server_ips") \
      <(printf '%s\n' "$resolved"); then
    status="LOCAL"
  elif [ -n "$stabledns_ips" ] &&
    grep -Fqx -f \
      <(printf '%s\n' "$stabledns_ips") \
      <(printf '%s\n' "$resolved"); then
    status="STABLEDNS"
  else
    status="REMOTE/CLUSTER"
  fi

  printf '  %-35s %-15s %s\n' \
    "$ns" \
    "$status" \
    "$(paste -sd, <<<"$resolved")"
done

# No WHM API getter exists for reseller nameservers (only
# setresellernameservers), so read cPanel's backing file directly.
custom_reseller_ns=$(
  awk -F: '{
    ns=$2
    gsub(/[[:space:]]/, "", ns)

    # The file has four fixed slots, so collapse the empty entries
    # left behind by unused slots (e.g. "ns1,ns2,,").
    gsub(/,+/, ",", ns)
    sub(/^,/, "", ns)
    sub(/,$/, "", ns)

    if (ns != "")
      printf "  %s: %s\n", $1, ns
  }' /var/cpanel/resellers-nameservers 2>/dev/null ||
  true
)

# One line per reseller, so the line count is the reseller count.
if [ -n "$custom_reseller_ns" ]; then
  custom_reseller_ns_count=$(wc -l <<<"$custom_reseller_ns" | tr -d ' ')
else
  custom_reseller_ns_count=0
fi

printf '\nCustom reseller nameservers (%s reseller(s)):\n' \
  "$custom_reseller_ns_count"

if [ -n "$custom_reseller_ns" ]; then
  printf '%s\n' "$custom_reseller_ns"
else
  printf '  None\n'
fi

# listips only flags an IP as "dedicated" when a single account uses it,
# which misses IPs delegated to resellers as their shared IP. Instead,
# report every account that is not on the server's main shared IP and
# classify how its IP is used.
server_shared_ip=$(whm_call get_shared_ip | jq -r '.data.ip // empty')

if [ -z "$server_shared_ip" ] || [ "$server_shared_ip" = "~" ]; then
  # listips can flag several addresses as main on multi-interface
  # servers; only ever use one so later exact-match comparisons work.
  server_shared_ip=$(
    jq -r 'first(.data.ip[]? | select((.mainaddr | tonumber) == 1) | .ip) // empty' <<<"$ip_json"
  )
fi

# Map of IP -> list of resellers using it as their shared IP. An IP
# with multiple resellers is effectively a shared pool IP.
reseller_shared_map=$(
  whm_call listresellers |
  jq -r '.data.reseller[]?' |
  while read -r reseller; do
    reseller_ip=$(whm_call get_shared_ip user="$reseller" | jq -r '.data.ip // empty')

    if [ -n "$reseller_ip" ] &&
        [ "$reseller_ip" != "~" ] &&
        [ "$reseller_ip" != "$server_shared_ip" ]; then
      printf '%s\t%s\n' "$reseller_ip" "$reseller"
    fi
  done |
  jq -Rn '
    [inputs | split("\t") | {ip: .[0], reseller: .[1]}]
    | group_by(.ip)
    | map({key: .[0].ip, value: map(.reseller)})
    | from_entries
  '
)

# Grouped by IP: an "H" line per qualifying IP followed by an "A" line
# per account on it.
dedicated_ip_groups=$(
  jq -rn \
    --argjson acct "$acct_json" \
    --argjson reseller_shared "$reseller_shared_map" \
    --arg shared_ip "$server_shared_ip" '
    [
      $acct.data.acct[]
      | select(.ip != $shared_ip)
      | ($reseller_shared[.ip] // []) as $resellers
      # IPs shared between multiple resellers are treated as shared IPs
      # and skipped, like the main shared IP.
      | select(($resellers | length) <= 1)
    ]
    | group_by(.ip)[]
    | ($reseller_shared[.[0].ip] // []) as $resellers
    # With no reseller attached, the IP only counts as dedicated when a
    # single account uses it.
    | select(($resellers | length) == 1 or length == 1)
    | (
        [
          "H",
          .[0].ip,
          (if ($resellers | length) == 1 then "reseller-shared (" + $resellers[0] + ")"
           else "dedicated (owner: " + .[0].owner + ")"
           end),
          (length | tostring)
        ],
        (.[] | ["A", .user, .domain])
      )
    | @tsv
  '
)

if [ -n "$dedicated_ip_groups" ]; then
  dedicated_ip_count=$(grep -c '^H' <<<"$dedicated_ip_groups")
else
  dedicated_ip_count=0
fi

printf '\nDedicated IPs (single account or single reseller): %s\n' \
  "$dedicated_ip_count"

if [ "$dedicated_ip_count" -gt 0 ]; then
  while IFS=$'\t' read -r tag field1 field2 field3; do
    case "$tag" in
      H)
        printf '\n  %s  %s  (%s account(s))\n' "$field1" "$field2" "$field3"
        printf '    %-16s %s\n' 'USER' 'DOMAIN'
        ;;
      A)
        printf '    %-16s %s\n' "$field1" "$field2"
        ;;
    esac
  done <<<"$dedicated_ip_groups"
else
  printf '  None\n'
fi