#!/bin/bash
# alpha-ftp — privileged Pure-FTPd virtual-user management for the Alpha panel.
# Fixed action keys only; validates every argument. Invoked via sudo by www-data.
# Passwords arrive on STDIN (never in argv, so they stay out of the process list).
# NOTE: a quota of 0 must be OMITTED (-N absent) to mean unlimited — pure-pw -N 0
# enforces a literal 0-byte quota. Same idea for sessions; bandwidth 0 = unlimited.
set -u
PASSWD=/etc/pure-ftpd/pureftpd.passwd
PDB=/etc/pure-ftpd/pureftpd.pdb
FTPUSER=ftpuser; FTPGROUP=ftpgroup; HOMEBASE=/srv/ftp
LOG=/var/log/pure-ftpd/transfer.log

vuser() { [[ "$1" =~ ^[a-z][a-z0-9_-]{1,31}$ ]] && [ "$1" != "$FTPUSER" ]; }
num()   { [[ "${1:-}" =~ ^[0-9]+$ ]]; }
mkdb()  { pure-pw mkdb "$PDB" -f "$PASSWD" >/dev/null 2>&1; }

cmd="${1:-}"; shift 2>/dev/null || true
case "$cmd" in
  info)
    while IFS=$'\t' read -r u dir; do
      [ -z "$u" ] && continue
      s=$(pure-pw show "$u" 2>/dev/null)
      q=$(echo "$s"  | awk -F: '/Max size/{gsub(/[^0-9]/,"",$2);print $2}')
      [[ "$s" == *"Max size"*"unlimited"* ]] && q=0
      dl=$(echo "$s" | awk -F: '/Download bandwidth/{gsub(/[^0-9]/,"",$2);print $2}')
      ul=$(echo "$s" | awk -F: '/Upload .* bandwidth/{gsub(/[^0-9]/,"",$2);print $2}')
      se=$(echo "$s" | awk -F: '/Max sim sessions/{gsub(/[^0-9]/,"",$2);print $2}')
      d=${dir%/./}
      used=$(du -sb "$d" 2>/dev/null | awk '{print $1}'); num "$used" || used=0
      echo "$u|$d|${q:-0}|${dl:-0}|${ul:-0}|${se:-0}|$used"
    done < <(pure-pw list 2>/dev/null) ;;
  add)
    u="${1:-}"; q="${2:-0}"; y="${3:-0}"; dl="${4:-0}"; ul="${5:-0}"; home="${6:-}"
    vuser "$u" || { echo BAD_USER; exit 2; }
    num "$q" && num "$y" && num "$dl" && num "$ul" || { echo BAD_NUM; exit 2; }
    pure-pw show "$u" >/dev/null 2>&1 && { echo EXISTS; exit 3; }
    [ -z "$home" ] && home="$HOMEBASE/$u"
    case "$home" in
      "$HOMEBASE"/*)
        leaf=${home#"$HOMEBASE"/}
        [[ "$leaf" =~ ^[a-z0-9_-]+$ ]] || { echo BAD_HOME; exit 7; }
        owner=$FTPUSER; group=$FTPGROUP; mkhome=1 ;;
      /var/www/sites/*)
        sub=${home#/var/www/sites/}
        [[ "$sub" =~ ^[a-z0-9._-]+$ ]] || { echo BAD_HOME; exit 7; }
        [ -d "$home" ] || { echo NO_SITE; exit 8; }
        owner=www-data; group=www-data; mkhome=0 ;;
      *) echo BAD_HOME; exit 7 ;;
    esac
    pw=$(head -c 256); [ -n "$pw" ] || { echo EMPTY_PW; exit 4; }
    if [ "$mkhome" = 1 ]; then install -d -m 750 -o "$owner" -g "$group" "$home" || { echo MKHOME_FAIL; exit 5; }; fi
    opts=(-u "$owner" -g "$group" -d "$home")
    [ "$q"  -gt 0 ] && opts+=(-N "$q")     # 0 omitted => unlimited
    [ "$y"  -gt 0 ] && opts+=(-y "$y")
    [ "$dl" -gt 0 ] && opts+=(-t "$dl")
    [ "$ul" -gt 0 ] && opts+=(-T "$ul")
    printf "%s\n%s\n" "$pw" "$pw" | pure-pw useradd "$u" "${opts[@]}" >/dev/null 2>&1 || { echo ADD_FAIL; exit 6; }
    mkdb; echo OK ;;
  passwd)
    u="${1:-}"; vuser "$u" || { echo BAD_USER; exit 2; }
    pure-pw show "$u" >/dev/null 2>&1 || { echo NO_USER; exit 3; }
    pw=$(head -c 256); [ -n "$pw" ] || { echo EMPTY_PW; exit 4; }
    printf "%s\n%s\n" "$pw" "$pw" | pure-pw passwd "$u" >/dev/null 2>&1 || { echo FAIL; exit 6; }
    mkdb; echo OK ;;
  quota)
    u="${1:-}"; q="${2:-}"; vuser "$u" || { echo BAD_USER; exit 2; }; num "$q" || { echo BAD_NUM; exit 2; }
    if [ "$q" -gt 0 ]; then pure-pw usermod "$u" -N "$q" >/dev/null 2>&1; else pure-pw usermod "$u" -N "" >/dev/null 2>&1; fi || { echo FAIL; exit 6; }
    mkdb; echo OK ;;
  bw)
    u="${1:-}"; dl="${2:-}"; ul="${3:-}"; vuser "$u" || { echo BAD_USER; exit 2; }; num "$dl" && num "$ul" || { echo BAD_NUM; exit 2; }
    dv=""; uv=""; [ "$dl" -gt 0 ] && dv="$dl"; [ "$ul" -gt 0 ] && uv="$ul"
    pure-pw usermod "$u" -t "$dv" -T "$uv" >/dev/null 2>&1 || { echo FAIL; exit 6; }; mkdb; echo OK ;;
  sessions)
    u="${1:-}"; n="${2:-}"; vuser "$u" || { echo BAD_USER; exit 2; }; num "$n" || { echo BAD_NUM; exit 2; }
    if [ "$n" -gt 0 ]; then pure-pw usermod "$u" -y "$n" >/dev/null 2>&1; else pure-pw usermod "$u" -y 0 >/dev/null 2>&1; fi || { echo FAIL; exit 6; }
    mkdb; echo OK ;;
  delete)
    u="${1:-}"; vuser "$u" || { echo BAD_USER; exit 2; }
    pure-pw userdel "$u" >/dev/null 2>&1 || { echo FAIL; exit 6; }; mkdb; echo OK ;;
  who)
    pure-ftpwho 2>/dev/null || true ;;
  ls)
    # ls <user> [relpath] — list one folder of the user's home. Read-only.
    # Output: one entry per line  type|bytes|mtime|name   (type d/f/l)
    u="${1:-}"; rel="${2:-}"
    vuser "$u" || { echo BAD_USER; exit 2; }
    home=$(pure-pw show "$u" 2>/dev/null | awk -F': *' '/^Directory/{print $2}')
    home=${home%/./}; home=${home%/}
    [ -n "$home" ] && [ -d "$home" ] || { echo NO_USER; exit 3; }
    case "$rel" in *..*|/*) echo BAD_PATH; exit 7 ;; esac
    tgt="$home"; [ -n "$rel" ] && tgt="$home/$rel"
    tgt=$(realpath -e "$tgt" 2>/dev/null) || { echo NO_PATH; exit 8; }
    case "$tgt/" in "$home"/*) ;; *) echo BAD_PATH; exit 7 ;; esac
    [ -d "$tgt" ] || { echo NOT_DIR; exit 9; }
    find "$tgt" -mindepth 1 -maxdepth 1 -printf '%y|%s|%T@|%f\n' 2>/dev/null ;;
  log)
    n="${1:-50}"; num "$n" || n=50; [ "$n" -gt 500 ] && n=500
    [ -f "$LOG" ] && tail -n "$n" "$LOG" || true ;;
  *) echo BAD_CMD; exit 1 ;;
esac
