#!/usr/bin/env bash
# =============================================================================
# onx-mailbox-create — Provision a virtual mailbox (Postfix + Dovecot + SQL)
#
# Purpose:
#   Creates the on-disk Maildir tree under /var/vmail/<domain>/<local>/Maildir
#   and inserts a row into dovecot_users so Postfix (virtual_mailbox_maps via
#   the dovecot-sql lookup) and Dovecot can deliver/serve the account.
#
# Input (stdin JSON):
#   {
#     "email":     "user@example.com",   -- required
#     "password":  "<plaintext>",        -- required (hashed before storage)
#     "quota_mb":  1024,                 -- optional; default 1024 (-1 = none)
#     "uid":       5000,                 -- optional; default vmail uid 5000
#     "gid":       5000                  -- optional; default vmail gid 5000
#   }
#
# Output (stdout JSON):
#   {
#     "email":         "...",
#     "maildir":       "/var/vmail/.../Maildir",
#     "password_hash": "{SHA512-CRYPT}...",
#     "quota_mb":      1024,
#     "uid":           5000,
#     "gid":           5000,
#     "created":       true
#   }
#
# Exit codes: 0=ok 1=invalid 2=preflight 3=exec 4=rolled-back 5=rollback-fail
#
# Production requirements:
#   - `vmail` user (uid 5000) with `vmail` group (gid 5000)
#     useradd -r -u 5000 -g 5000 -s /sbin/nologin -d /var/vmail vmail
#   - `dovecot_users` SQL table created (see schema in /docs/mail-stack.md)
#   - SELinux: mail_spool_t context on /var/vmail recursively
#   - secrets in /etc/onoxsoft/secrets.env: ONX_MAIL_DB_* / DOVECOT_DB_*
#
# Deployed to: /usr/local/onoxsoft/bin/onx-mailbox-create
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

# ── Constants ────────────────────────────────────────────────────────────────
readonly VMAIL_ROOT="/var/vmail"
readonly DEFAULT_UID=5000
readonly DEFAULT_GID=5000
readonly DEFAULT_QUOTA_MB=1024

# ── Dependencies ─────────────────────────────────────────────────────────────
require_root
# doveadm + mysql are required for production; in MOCK_MODE the helpers
# fall back to openssl + a syslog stub respectively.
if [[ "${MOCK_MODE}" != "1" ]]; then
    command -v doveadm >/dev/null 2>&1 || onx_die 2 "doveadm not found (install dovecot)"
    command -v mysql   >/dev/null 2>&1 || onx_die 2 "mysql client not found"
fi

# ── Read stdin ───────────────────────────────────────────────────────────────
onx_json_input

EMAIL_RAW=$(onx_json_field "email")
PASSWORD=$(onx_json_field  "password")
QUOTA_MB=$(onx_json_field  "quota_mb" "${DEFAULT_QUOTA_MB}")
UID_VAL=$(onx_json_field   "uid" "${DEFAULT_UID}")
GID_VAL=$(onx_json_field   "gid" "${DEFAULT_GID}")

# ── Input validation ─────────────────────────────────────────────────────────
# Side-effect call (no $(...)): sets ONX_EMAIL{,_LOCAL,_DOMAIN} in our scope.
onx_validate_email "${EMAIL_RAW}" >/dev/null
EMAIL="${ONX_EMAIL}"
LOCAL="${ONX_EMAIL_LOCAL}"
DOMAIN="${ONX_EMAIL_DOMAIN}"

[[ -z "${PASSWORD}" ]] && onx_die 1 "password is required"
[[ "${QUOTA_MB}" =~ ^-?[0-9]+$ ]] || onx_die 1 "quota_mb must be integer (got '${QUOTA_MB}')"
[[ "${UID_VAL}"  =~ ^[0-9]+$ ]]   || onx_die 1 "uid must be positive integer"
[[ "${GID_VAL}"  =~ ^[0-9]+$ ]]   || onx_die 1 "gid must be positive integer"

# Hard floor on uid/gid — refuse to run as root (uid 0) or another system uid.
if [[ "${UID_VAL}" -lt 1000 && "${UID_VAL}" != "${DEFAULT_UID}" ]]; then
    onx_die 1 "uid '${UID_VAL}' is in reserved range (<1000) and not the default vmail uid"
fi

MAILBOX_HOME="${VMAIL_ROOT}/${DOMAIN}/${LOCAL}"
MAILDIR="${MAILBOX_HOME}/Maildir"
QUOTA_BYTES=$(( QUOTA_MB * 1024 * 1024 ))
[[ "${QUOTA_MB}" -lt 0 ]] && QUOTA_BYTES=0  # 0 = unlimited in Dovecot's quota_rule

# Reject mailbox path traversal
onx_validate_vmail_path "${MAILBOX_HOME}" >/dev/null

# ── Preflight ────────────────────────────────────────────────────────────────
if [[ "${MOCK_MODE}" != "1" ]]; then
    # vmail user must exist with the requested uid (best-effort check; some
    # deployments use different account names — only fail when uid is the
    # default and is genuinely missing).
    if [[ "${UID_VAL}" == "${DEFAULT_UID}" ]] && ! id -u vmail >/dev/null 2>&1; then
        onx_die 2 "vmail system user not found — see install docs"
    fi
fi

# Maildir presence implies the box already exists — refuse to clobber.
if [[ -d "${MAILDIR}/cur" ]]; then
    onx_die 2 "mailbox already exists on disk: ${MAILDIR}"
fi

# DB presence check (MOCK_MODE just logs and returns 0 rows).
EXIST_COUNT=$(mysql_exec "${DOVECOT_DB_NAME}" \
    "SELECT COUNT(*) FROM dovecot_users WHERE email='${EMAIL}';" 2>/dev/null | tail -1)
EXIST_COUNT="${EXIST_COUNT:-0}"
if [[ "${EXIST_COUNT}" =~ ^[0-9]+$ ]] && [[ "${EXIST_COUNT}" -gt 0 ]]; then
    onx_die 2 "mailbox already exists in dovecot_users: ${EMAIL}"
fi

# ── Wire rollback trap before any side-effects ───────────────────────────────
trap 'onx_rollback_run' ERR

# ── Hash password ────────────────────────────────────────────────────────────
PASSWORD_HASH=$(onx_doveadm_pw "${PASSWORD}")
[[ -z "${PASSWORD_HASH}" ]] && onx_die 3 "password hash empty (doveadm failure)"

# ── Create Maildir tree ──────────────────────────────────────────────────────
# Postfix/Dovecot expect the {cur,new,tmp} trio; create them with the right
# perms straight away so a half-created tree never lands a delivery.
mkdir -p "${MAILDIR}/cur" "${MAILDIR}/new" "${MAILDIR}/tmp"
onx_rollback_register "rm -rf '${MAILBOX_HOME}' 2>/dev/null || true"

if [[ "${MOCK_MODE}" != "1" ]]; then
    chown -R "${UID_VAL}:${GID_VAL}" "${MAILBOX_HOME}"
fi
chmod 700 "${MAILBOX_HOME}"
chmod 700 "${MAILDIR}"
chmod 700 "${MAILDIR}/cur" "${MAILDIR}/new" "${MAILDIR}/tmp"

onx_log "maildir created: ${MAILDIR} (uid=${UID_VAL} gid=${GID_VAL})"

# ── SELinux context (best-effort; harmless on Debian) ────────────────────────
if [[ "${MOCK_MODE}" != "1" ]] && command -v semanage >/dev/null 2>&1; then
    # Only add the fcontext rule once — running it repeatedly is idempotent
    # but noisy in logs. We swallow errors because some hosts run SELinux
    # in permissive mode without policycoreutils-python-utils installed.
    if ! semanage fcontext -l 2>/dev/null | grep -q "^${VMAIL_ROOT}(/.\*)?"; then
        semanage fcontext -a -t mail_spool_t "${VMAIL_ROOT}(/.*)?" 2>/dev/null || \
            onx_log "semanage fcontext add failed (continuing)"
    fi
    if command -v restorecon >/dev/null 2>&1; then
        restorecon -R "${MAILBOX_HOME}" 2>/dev/null || \
            onx_log "restorecon failed for ${MAILBOX_HOME} (continuing)"
    fi
fi

# ── Insert into dovecot_users ────────────────────────────────────────────────
# We let MariaDB do the parameter escape via the my.cnf path + literal escape.
# `email` and `home` are the only string columns we accept — both already
# regex-validated above, so they cannot contain SQL meta-chars.
PASSWORD_HASH_ESC="${PASSWORD_HASH//\'/\\\'}"

mysql_exec "${DOVECOT_DB_NAME}" "$(cat <<SQL
INSERT INTO dovecot_users
  (email, password, uid, gid, home, quota_bytes, enabled, created_at)
VALUES
  ('${EMAIL}', '${PASSWORD_HASH_ESC}', ${UID_VAL}, ${GID_VAL},
   '${MAILBOX_HOME}', ${QUOTA_BYTES}, 1, NOW());
SQL
)" || onx_die 3 "dovecot_users INSERT failed for ${EMAIL}"

onx_rollback_register "mysql --defaults-extra-file=\"\${_MYCNF_TMP:-/dev/null}\" --batch --silent ${DOVECOT_DB_NAME} -e \"DELETE FROM dovecot_users WHERE email='${EMAIL}';\" 2>/dev/null || true"

# ── Postfix virtual_mailbox_maps refresh ─────────────────────────────────────
# Postfix-Dovecot SQL backend reads the same dovecot_users table via
# virtual_mailbox_maps. No postmap step needed for SQL lookups, but we kick
# `postfix reload` only if the install hasn't done it elsewhere — skipped
# here to keep this script idempotent and side-effect-light.

# ── Audit + output ───────────────────────────────────────────────────────────
onx_audit "onx-mailbox" "create email=${EMAIL} maildir=${MAILDIR} quota_mb=${QUOTA_MB} uid=${UID_VAL}"

# Disarm rollback (we succeeded)
_ONX_ROLLBACK_STACK=()
trap - ERR

onx_json_out \
    "email"         "${EMAIL}" \
    "maildir"       "${MAILDIR}" \
    "password_hash" "${PASSWORD_HASH}" \
    "quota_mb"      "${QUOTA_MB}" \
    "uid"           "${UID_VAL}" \
    "gid"           "${GID_VAL}" \
    "created"       "true"
