#!/bin/sh

set -eu

DEFAULTS=/etc/default/rekono
CONFDIR=/etc/rekono
STAGE=/var/lib/rekono/kbx
DUMP="${STAGE}/dump.sql"
ORIGIN="${STAGE}/origin"
CONFIG_IMPORTED="${STAGE}/.config-imported"
DUMP_ENTRYPOINT=/usr/libexec/rekono/rekono-kbx-dump-entrypoint
DUMPED=.rekono-2.x-dumped
DISCARDED=.rekono-2.x-discarded
IMPORTED=.rekono-2.x-imported
POSTGRESQL_VERSIONS='14 15 16'

if [ -r "${DEFAULTS}" ]; then
    . "${DEFAULTS}"
fi

fail() {
    printf '%s\n' "$*" >&2
    exit 2
}

as_owner() {
    directory="$1"
    shift
    user="$(stat -c %U "${directory}" 2>/dev/null || true)"
    [ -n "${user}" ] || fail "Cannot read the owner of ${directory}"
    runuser -u "${user}" -- "$@"
}

homes() {
    if [ -n "${REKONO_KBX_HOME:-}" ]; then
        printf '%s\n' "${REKONO_KBX_HOME}"
        return 0
    fi
    # Kaboxer mounted ~/.local/share/rekono, so the data belongs to the user who ran it
    getent passwd 2>/dev/null | awk -F: '
        ($3 >= 1000 && $3 < 65534) || $3 == 0 {
            if ($6 != "" && $6 != "/" && $7 !~ /(nologin|false)$/) {
                print $6 "/.local/share/rekono"
            }
        }' | sort -u
}

handled() {
    [ -e "$1/${DISCARDED}" ] || [ -e "$1/${DUMPED}" ] || [ -e "$1/${IMPORTED}" ]
}

detect() {
    if [ "${REKONO_KBX_SKIP:-0}" = 1 ] || [ -f "${ORIGIN}" ]; then
        return 1
    fi

    directory="$(homes | while IFS= read -r home; do
        if [ -d "${home}/data" ] && ! handled "${home}"; then
            printf '%s\n' "${home}"
            break
        fi
    done)"

    [ -n "${directory}" ] || return 1
    printf '%s\n' "${directory}"
}

leftover() {
    directory="$(homes | while IFS= read -r home; do
        if [ -e "${home}/${IMPORTED}" ] && [ -d "${home}/data" ]; then
            printf '%s\n' "${home}"
            break
        fi
    done)"

    [ -n "${directory}" ] || return 1
    printf '%s\n' "${directory}"
}

docker_available() {
    command -v docker >/dev/null 2>&1 \
        || fail "Docker is not installed, and it is the only way to read the PostgreSQL data directory that rekono-kbx left behind, since that PostgreSQL version is not packaged in Kali anymore. Install it with:
    sudo apt install docker.io"

    docker info >/dev/null 2>&1 \
        || fail "The Docker daemon is not running, and it is needed to read the PostgreSQL data directory that rekono-kbx left behind. Start it with:
    sudo systemctl start docker"
}

legacy_version() {
    directory="$1"
    [ -r "${directory}/data/PG_VERSION" ] || fail "Cannot read ${directory}/data/PG_VERSION, so the PostgreSQL version of the rekono-kbx data is unknown"

    version="$(cat "${directory}/data/PG_VERSION")"
    for supported in ${POSTGRESQL_VERSIONS}; do
        if [ "${version}" = "${supported}" ]; then
            printf '%s\n' "${version}"
            return 0
        fi
    done
    fail "The rekono-kbx data was written by PostgreSQL ${version}, and only ${POSTGRESQL_VERSIONS} can be migrated automatically. Migrate it by hand following https://github.com/pablosnt/rekono/blob/main/UPGRADE.md"
}

image() {
    if docker image inspect "${IMAGE}" >/dev/null 2>&1; then
        return 0
    fi

    echo "Downloading the ${IMAGE} Docker image, needed to read the rekono-kbx data"
    log="$(mktemp)"
    if docker pull --quiet "${IMAGE}" >"${log}" 2>&1; then
        rm -f "${log}"
        return 0
    fi
    reason="$(cat "${log}")"
    rm -f "${log}"
    fail "Could not download the ${IMAGE} Docker image:

${reason}

Check the internet connection of this machine, or download it by hand with:
    sudo docker pull ${IMAGE}"
}

stage_files() {
    directory="$1"
    subdirectory="$2"
    source="${directory}/${subdirectory}"
    target="${REKONO_HOME}/${subdirectory}"

    [ -d "${source}" ] || return 0

    echo "Copying the ${subdirectory} of rekono-kbx to ${target}"
    install -d -o rekono -g rekono -m 0750 "${target}"
    find "${source}" -maxdepth 1 -type f -exec install -o rekono -g rekono -m 0640 -t "${target}" {} +
}

run() {
    # Nothing to migrate
    directory="$(detect)" || exit 0

    # The data belongs to the PostgreSQL user of the Kaboxer container
    [ "$(id -u)" -eq 0 ] || fail "The data of rekono-kbx can only be imported by root."

    docker_available
    version="$(legacy_version "${directory}")"
    IMAGE="postgres:${version}-bookworm"
    install -d -o root -g rekono -m 0770 "${STAGE}"
    image

    scratch="${STAGE}/scratch"
    rm -rf "${scratch}"
    install -d -m 0711 "${scratch}"
    trap 'rm -rf "${scratch}"' EXIT INT TERM

    echo "Dumping the PostgreSQL ${version} database of rekono-kbx from ${directory}/data"
    docker run --rm --network none --entrypoint /bin/sh \
        --env "REKONO_DB_NAME=${RKN_DB_NAME:-rekono}" \
        --volume "${directory}/data:/legacy:ro" \
        --volume "${scratch}:/scratch" \
        --volume "${STAGE}:/dump" \
        --volume "${DUMP_ENTRYPOINT}:/dump-database:ro" \
        "${IMAGE}" /dump-database

    [ -s "${DUMP}" ] || fail "The dump of the rekono-kbx database is empty."

    chmod 0600 "${DUMP}"
    printf '%s\n' "${directory}" > "${ORIGIN}"
    as_owner "${directory}" touch "${directory}/${DUMPED}"

    if [ -f "${directory}/config.yaml" ] && [ ! -e "${CONFDIR}/config.yaml" ] && [ ! -e "${REKONO_HOME}/config.yaml" ] && [ ! -L "${REKONO_HOME}/config.yaml" ]
    then
        install -o rekono -g rekono -m 0640 /dev/null "${REKONO_HOME}/config.yaml"
        if as_owner "${directory}" cat "${directory}/config.yaml" > "${REKONO_HOME}/config.yaml"
        then
            install -o rekono -g rekono -m 0640 /dev/null "${CONFIG_IMPORTED}"
            echo "The configuration of rekono-kbx was copied to ${REKONO_HOME}/config.yaml"
        else
            rm -f "${REKONO_HOME}/config.yaml"
            echo "The configuration of rekono-kbx could not be read, so the default one is used" >&2
        fi
    fi

    stage_files "${directory}" wordlists
    stage_files "${directory}" reports

    rm -rf "${scratch}"
    trap - EXIT INT TERM
    echo "The rekono-kbx database is ready to be imported. Its data has not been modified."
}

discard() {
    directory="${1:?usage: rekono-kbx-dump discard <directory>}"
    [ -d "${directory}" ] || fail "${directory} does not exist."
    as_owner "${directory}" touch "${directory}/${DISCARDED}"
    echo "Rekono will not offer to import the data in ${directory} again."
    echo "Remove ${directory}/${DISCARDED} to be asked once more."
}

case "${1:?usage: rekono-kbx-dump <detect|leftover|run|discard>}" in
    detect)   detect ;;
    leftover) leftover ;;
    run)      run ;;
    discard)  shift; discard "$@" ;;
    *)        fail "usage: rekono-kbx-dump <detect|leftover|run|discard>" ;;
esac
