#!/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 # synology-nas-helper unmount # # Guarantees enforced below: # * mountpoint must resolve to a path under the CALLING user's $HOME or # /run/media/ (rejects .. traversal via realpath -m). # * source must look like //host/share. # * credfile must live under /run/user//, 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