#!/usr/bin/env python3
"""Dovecot Lua passdb helper: verify primary + app passwords.

Usage:
  dovecot-auth-verify <username>

Reads the plaintext password from stdin (not argv — avoids process-list leaks).
Exit codes (checkpassword-compatible):
  0   — success
  1   — authentication failure
  111 — temporary failure (DB/import errors)
"""

from __future__ import annotations

import os
import sys

try:
    import pymysql
    from passlib.context import CryptContext
except ImportError:
    sys.exit(111)

pwd_context = CryptContext(
    schemes=["argon2", "bcrypt", "pbkdf2_sha512"],
    deprecated="auto",
    default="argon2",
    argon2__type="ID",
)


def connect():
    return pymysql.connect(
        host=os.environ.get("LIMRISTEM_MAIL_DB_HOST", "127.0.0.1"),
        port=int(os.environ.get("LIMRISTEM_MAIL_DB_PORT", "3306")),
        user=os.environ.get("LIMRISTEM_MAIL_DB_RO_USER")
        or os.environ.get("LIMRISTEM_MAIL_DB_USER", "limristem-mail"),
        password=os.environ.get("LIMRISTEM_MAIL_DB_RO_PASS")
        or os.environ.get("LIMRISTEM_MAIL_DB_PASS", ""),
        database=os.environ.get("LIMRISTEM_MAIL_DB_NAME", "limristem-mail"),
        charset="utf8mb4",
        connect_timeout=5,
        read_timeout=5,
        write_timeout=5,
        cursorclass=pymysql.cursors.DictCursor,
    )


def main() -> int:
    if len(sys.argv) < 2:
        return 1
    username = sys.argv[1].strip()
    if not username or "@" not in username:
        return 1
    try:
        password = sys.stdin.read()
    except OSError:
        return 111
    # Accept optional trailing newline from pipe writers.
    if password.endswith("\n"):
        password = password[:-1]
    if password.endswith("\r"):
        password = password[:-1]
    if password is None:
        return 1

    try:
        conn = connect()
    except Exception:
        return 111

    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT a.id AS account_id, a.password_hash,
                       COALESCE(a.require_app_password, 0) AS require_app_password
                FROM accounts a
                JOIN domains d ON a.domain_id = d.id
                WHERE CONCAT(a.local_part, '@', d.name) = %s
                  AND a.is_active = 1 AND d.is_active = 1
                LIMIT 1
                """,
                (username,),
            )
            account = cur.fetchone()
            if not account:
                return 1

            candidates: list[str] = []
            if not int(account["require_app_password"] or 0):
                if account.get("password_hash"):
                    candidates.append(account["password_hash"])

            cur.execute(
                """
                SELECT password_hash
                FROM mailbox_app_passwords
                WHERE account_id = %s AND revoked_at IS NULL
                ORDER BY id ASC
                """,
                (account["account_id"],),
            )
            for row in cur.fetchall() or []:
                if row.get("password_hash"):
                    candidates.append(row["password_hash"])

            for password_hash in candidates:
                try:
                    if pwd_context.verify(password, password_hash):
                        return 0
                except (ValueError, TypeError):
                    continue
            return 1
    except Exception:
        return 111
    finally:
        try:
            conn.close()
        except Exception:
            pass


if __name__ == "__main__":
    raise SystemExit(main())
