#!/bin/sh
set -u

: "${RELAY_AUDIT_LOG_DIR:=/var/log/relay-shell}"
: "${RELAY_AUDIT_RSYNC_DEST:?RELAY_AUDIT_RSYNC_DEST is required}"
: "${RELAY_AUDIT_SSH_KNOWN_HOSTS:=/var/lib/relay-shell/.ssh/known_hosts}"
# Plain rsync on the destination: the backup account must own (or be able to write) the
# destination directory. Running rsync as root there (`sudo -n /usr/bin/rsync`) gives a
# compromised audited host root-equivalent write on the backup host, so it is opt-in.
: "${RELAY_AUDIT_RSYNC_PATH:=rsync}"
: "${RELAY_AUDIT_VERIFY_BIN:=/usr/local/libexec/relay-audit-evidence}"
: "${RELAY_AUDIT_RSYNC_BIN:=/usr/bin/rsync}"
: "${RELAY_AUDIT_COMPARE_BIN:=$RELAY_AUDIT_VERIFY_BIN}"
# pull (default): read the remote latest-anchor.json first and refuse to publish a local
# chain that is behind it or has forked from it. off: skip the check (use it only when the
# destination is write-only and cannot be read back).
: "${RELAY_AUDIT_REMOTE_CHECK:=pull}"

# The remote check guards audit integrity, so an unknown value must not fall through to
# the same path as the deliberate `off`.
case "$RELAY_AUDIT_REMOTE_CHECK" in
  pull|off) ;;
  *)
    echo "relay-audit-evidence-sync: RELAY_AUDIT_REMOTE_CHECK must be 'pull' or 'off', got '$RELAY_AUDIT_REMOTE_CHECK'" >&2
    exit 2
    ;;
esac

set --
if [ -n "${RELAY_AUDIT_RSYNC_CHOWN:-}" ]; then
  set -- "--chown=$RELAY_AUDIT_RSYNC_CHOWN"
fi

"$RELAY_AUDIT_VERIFY_BIN"
verify_status=$?
[ "$verify_status" -eq 0 ] || exit "$verify_status"

if [ "$RELAY_AUDIT_REMOTE_CHECK" = "pull" ]; then
  remote_dir=$(mktemp -d) || exit 1
  trap 'rm -rf "$remote_dir"' EXIT
  # The anchor is a few hundred bytes. --max-size keeps a hostile or broken destination from
  # filling the temporary directory; an oversized file is skipped and then fails the compare.
  LC_ALL=C "$RELAY_AUDIT_RSYNC_BIN" \
    --archive --protect-args --max-size=65536 \
    --rsync-path="$RELAY_AUDIT_RSYNC_PATH" \
    -e "/usr/bin/ssh -o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$RELAY_AUDIT_SSH_KNOWN_HOSTS -o ControlMaster=no -o ControlPath=none" \
    "${RELAY_AUDIT_RSYNC_DEST%/}/latest-anchor.json" "$remote_dir/latest-anchor.json" \
    2> "$remote_dir/pull.err"
  pull_status=$?
  cat "$remote_dir/pull.err" >&2
  case "$pull_status" in
    0)
      "$RELAY_AUDIT_COMPARE_BIN" --check-remote-anchor "$remote_dir/latest-anchor.json"
      compare_status=$?
      [ "$compare_status" -eq 0 ] || exit "$compare_status"
      ;;
    23)
      # Exit 23 is rsync's generic "partial transfer" status, so it only means "no anchor yet"
      # (a first sync) when every reported error is a missing source file. A permission or
      # I/O failure must stop the run rather than disable the check. Write-only destinations
      # use RELAY_AUDIT_REMOTE_CHECK=off instead.
      if grep -q 'link_stat .* failed: No such file or directory (2)' "$remote_dir/pull.err" \
        && ! grep -E '^rsync' "$remote_dir/pull.err" \
          | grep -qvE 'link_stat .* failed: No such file or directory \(2\)|^rsync error: some files/attrs were not transferred'; then
        echo "relay-audit-evidence-sync: remote anchor does not exist yet; skipping the rollback check" >&2
      else
        echo "relay-audit-evidence-sync: reading the remote anchor failed; refusing to publish (use RELAY_AUDIT_REMOTE_CHECK=off for a write-only destination)" >&2
        exit 23
      fi
      ;;
    *) exit "$pull_status" ;;
  esac
fi

"$RELAY_AUDIT_RSYNC_BIN" \
  --archive --checksum --compress --protect-args --delay-updates \
  --chmod=F640,D750 \
  "$@" \
  --include='audit.jsonl*' \
  --include='chain-verification.jsonl' \
  --include='retained-checkpoints.json' \
  --exclude='*' \
  --rsync-path="$RELAY_AUDIT_RSYNC_PATH" \
  -e "/usr/bin/ssh -o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$RELAY_AUDIT_SSH_KNOWN_HOSTS -o ControlMaster=no -o ControlPath=none" \
  "$RELAY_AUDIT_LOG_DIR/" "$RELAY_AUDIT_RSYNC_DEST"
sync_status=$?
[ "$sync_status" -eq 0 ] || exit "$sync_status"

"$RELAY_AUDIT_RSYNC_BIN" \
  --archive --checksum --compress --protect-args --delay-updates \
  --chmod=F640,D750 \
  "$@" \
  --include='latest-anchor.json' \
  --exclude='*' \
  --rsync-path="$RELAY_AUDIT_RSYNC_PATH" \
  -e "/usr/bin/ssh -o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$RELAY_AUDIT_SSH_KNOWN_HOSTS -o ControlMaster=no -o ControlPath=none" \
  "$RELAY_AUDIT_LOG_DIR/" "$RELAY_AUDIT_RSYNC_DEST"
