#!/bin/sh
#
# synology-nas-helper — privileged mount/unmount helper for the Synology NAS
# plasmoid. It is invoked as root via pkexec (see the accompanying
# sk.houmeres.synologynas.policy). The CALLER IS UNPRIVILEGED, so every argument
# is treated as hostile and strictly validated before any mount happens.
#
# Usage (all arguments positional):
#   synology-nas-helper mount   <source> <mountpoint> <credfile> <vers> <opts> <fmode> <dmode>
#   synology-nas-helper unmount <mountpoint>
#
# Guarantees enforced below:
#   * mountpoint must resolve to a path under the CALLING user's $HOME or
#     /run/media/<user> (rejects .. traversal via realpath -m).
#   * source must look like //host/share.
#   * credfile must live under /run/user/<caller-uid>/, be a regular file,
#     owned by the caller, mode 0600.
#   * vers, permission modes and extra options are charset/whitelist checked.
#
set -eu

die() { echo "synology-nas-helper: $1" >&2; exit 1; }

# --- identify the calling (unprivileged) user ---------------------------------
[ -n "${PKEXEC_UID:-}" ] || die "must be run through pkexec (PKEXEC_UID unset)"
case "$PKEXEC_UID" in
    ''|*[!0-9]*) die "invalid PKEXEC_UID" ;;
esac

pwline=$(getent passwd "$PKEXEC_UID") || die "unknown caller uid $PKEXEC_UID"
CALLER_USER=$(printf '%s\n' "$pwline" | cut -d: -f1)
CALLER_GID=$(printf '%s\n' "$pwline" | cut -d: -f4)
CALLER_HOME=$(printf '%s\n' "$pwline" | cut -d: -f6)
[ -n "$CALLER_HOME" ] || die "caller has no home directory"

# --- shared validators --------------------------------------------------------
validate_mountpoint() {
    # Canonicalise (resolves .. lexically, no need for the path to exist yet).
    canon=$(realpath -m -- "$1") || die "cannot canonicalise mountpoint"
    case "$canon" in
        "$CALLER_HOME"/*) : ;;
        "/run/media/$CALLER_USER"/*) : ;;
        *) die "mountpoint must be under \$HOME or /run/media/$CALLER_USER" ;;
    esac
    printf '%s' "$canon"
}

# --- subcommands --------------------------------------------------------------
do_mount() {
    [ $# -eq 7 ] || die "mount: expected 7 arguments"
    source=$1; mountpoint=$2; credfile=$3; vers=$4; extra=$5; fmode=$6; dmode=$7

    case "$source" in
        //?*/?*) : ;;
        *) die "source must be //host/share" ;;
    esac

    mp=$(validate_mountpoint "$mountpoint")

    # Credentials file: under the caller's runtime dir, their file, mode 0600.
    cf=$(realpath -m -- "$credfile") || die "cannot canonicalise credfile"
    case "$cf" in
        "/run/user/$PKEXEC_UID"/*) : ;;
        *) die "credfile must be under /run/user/$PKEXEC_UID" ;;
    esac
    [ -f "$cf" ] || die "credfile not found"
    [ "$(stat -c %u "$cf")" = "$PKEXEC_UID" ] || die "credfile not owned by caller"
    [ "$(stat -c %a "$cf")" = "600" ] || die "credfile must be mode 0600"

    case "$vers" in
        default|1.0|2.0|2.1|3.0|3.02|3.1.1) : ;;
        *) die "unsupported SMB version: $vers" ;;
    esac
    case "$fmode" in 0[0-7][0-7][0-7]) : ;; *) die "bad file mode" ;; esac
    case "$dmode" in 0[0-7][0-7][0-7]) : ;; *) die "bad dir mode" ;; esac
    case "$extra" in
        '') : ;;
        *[!A-Za-z0-9=,_.:-]*) die "extra mount options contain illegal characters" ;;
    esac

    if findmnt -rn -o TARGET --mountpoint "$mp" >/dev/null 2>&1; then
        die "already mounted: $mp"
    fi

    # Create the mount point and hand the freshly-created dirs to the caller.
    mkdir -p "$mp" || die "cannot create mount point"
    chown "$PKEXEC_UID:$CALLER_GID" "$mp" 2>/dev/null || true
    p=$(dirname "$mp")
    while [ "$p" != "$CALLER_HOME" ] && [ "$p" != "/" ] && [ "$p" != "/run/media" ]; do
        chown "$PKEXEC_UID:$CALLER_GID" "$p" 2>/dev/null || true
        p=$(dirname "$p")
    done

    opts="credentials=$cf,uid=$PKEXEC_UID,gid=$CALLER_GID,forceuid,forcegid,file_mode=$fmode,dir_mode=$dmode,vers=$vers"
    [ -n "$extra" ] && opts="$opts,$extra"

    exec mount.cifs "$source" "$mp" -o "$opts"
}

do_unmount() {
    [ $# -eq 1 ] || die "unmount: expected 1 argument"
    mp=$(validate_mountpoint "$1")
    findmnt -rn -t cifs -o TARGET --mountpoint "$mp" >/dev/null 2>&1 \
        || die "not a cifs mount: $mp"
    umount "$mp" || die "umount failed"
    # Tidy up empty leaf + host dirs (never recursive, never touches files).
    rmdir "$mp" 2>/dev/null || true
    rmdir "$(dirname "$mp")" 2>/dev/null || true
}

[ $# -ge 1 ] || die "no subcommand"
cmd=$1
shift
case "$cmd" in
    mount)   do_mount "$@" ;;
    unmount) do_unmount "$@" ;;
    *) die "unknown subcommand: $cmd" ;;
esac
