#!/bin/bash
# ======================================================================
# Archivo:       quirinux-autologin-helper
# Ruta:          /usr/lib/quirinux-autologin/
# Autor:         Charlie Martínez <cmartinez@quirinux.org>
# Licencia:      https://www.gnu.org/licenses/gpl-2.0.txt
# Propósito:     Activa o desactiva el inicio de sesión automático
#                (LightDM, GDM3 y SLiM). Se ejecuta como root a través
#                de polkit (pkexec); nunca vía sudo sin contraseña.
# Distribución:  Quirinux 2.x
# Revisión:      1.4.3+q1 (reescritura de seguridad)
# ======================================================================
#
# Copyright (c) 2019-2025 Charlie Martínez, derechos reservados.
# Licencia: https://www.gnu.org/licenses/gpl-2.0.txt
# Usos autorizados y no autorizados de la marca Quirinux:
# Ver https://www.quirinux.org/aviso-legal
#
# Uso:   quirinux-autologin-helper enable|disable [usuario]
# El usuario SOLO se acepta como argumento si se ejecuta como root real;
# vía pkexec/sudo se toma el UID verificado del llamante.
#
# Códigos de salida: 0 ok · 1 error · 2 sin gestor de sesión compatible
#                    3 la verificación final falló · 4 usuario no válido

set -u
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
export LC_ALL=C
umask 022

DROPIN_NAME="50-quirinux-autologin.conf"
BACKUP_DIR="/var/backups/quirinux-autologin/$(date +%Y%m%d-%H%M%S)"
HANDLED=()
BACKED_UP=0

die() { echo "quirinux-autologin: $1" >&2; exit "${2:-1}"; }

[ "$(id -u)" -eq 0 ] || die "debe ejecutarse como root (use pkexec)." 1

ACTION="${1:-}"
case "$ACTION" in enable|disable) ;; *) die "uso: $0 enable|disable [usuario]" 1 ;; esac

# Un solo proceso a la vez
LOCK=/run/quirinux-autologin.lock
exec 9>"$LOCK" || die "no se pudo crear el bloqueo $LOCK" 1
flock -w 15 9 || die "otra instancia está en ejecución." 1

TMPFILES=()
trap 'rm -f "${TMPFILES[@]}" 2>/dev/null' EXIT

# ---------------------------------------------------------------- usuario
TARGET_USER=""
if [ "$ACTION" = enable ]; then
    uid=""
    if   [ -n "${PKEXEC_UID:-}" ]; then uid="$PKEXEC_UID"
    elif [ -n "${SUDO_UID:-}" ];   then uid="$SUDO_UID"
    fi
    if [ -n "$uid" ]; then
        [[ "$uid" =~ ^[0-9]+$ ]] || die "UID no válido." 4
        TARGET_USER="$(getent passwd "$uid" | cut -d: -f1)"
    else
        TARGET_USER="${2:-}"
    fi
    [ -n "$TARGET_USER" ] || die "no se pudo determinar el usuario (como root use: autologin USUARIO)." 4
    [[ "$TARGET_USER" =~ ^[A-Za-z_][A-Za-z0-9_.-]{0,31}$ ]] || die "nombre de usuario no válido." 4
    pw="$(getent passwd "$TARGET_USER")" || die "el usuario '$TARGET_USER' no existe." 4
    IFS=: read -r _ _ puid _ _ phome pshell <<<"$pw"
    { [ "$puid" -ge 1000 ] && [ "$puid" -lt 60000 ]; } || die "no se permite autologin para cuentas del sistema ni root." 4
    [ -d "$phome" ] || die "el usuario '$TARGET_USER' no tiene directorio personal." 4
    case "$pshell" in */nologin|*/false|"") die "el usuario '$TARGET_USER' no tiene un shell de inicio de sesión." 4 ;; esac
fi

# ------------------------------------------------------- utilidades de edición
backup() {
    [ -e "$1" ] || return 0
    mkdir -p "$BACKUP_DIR" && chmod 700 "$(dirname "$BACKUP_DIR")" "$BACKUP_DIR"
    cp -a --parents "$1" "$BACKUP_DIR"/ && BACKED_UP=1
}

# apply_awk FICHERO [args de awk...]: reemplazo atómico, solo si hay cambios
apply_awk() {
    local f tmp; f="$(readlink -f "$1")"; shift
    tmp="$(mktemp "${f}.qXXXXXX")" || return 1
    TMPFILES+=("$tmp")
    if awk "$@" "$f" >"$tmp"; then
        if cmp -s "$f" "$tmp"; then rm -f "$tmp"; return 0; fi
        backup "$f"
        chmod --reference="$f" "$tmp"; chown --reference="$f" "$tmp"
        mv -f "$tmp" "$f"
        echo "  modificado: $f"
    else
        rm -f "$tmp"; return 1
    fi
}

# write_file RUTA CONTENIDO: crea/reemplaza de forma atómica (0644)
write_file() {
    local f="$1" tmp
    if [ -f "$f" ] && [ "$(cat "$f")" = "$(printf '%b' "$2")" ]; then return 0; fi
    backup "$f"
    tmp="$(mktemp "${f}.qXXXXXX")" || return 1
    TMPFILES+=("$tmp")
    printf '%b' "$2" >"$tmp"; chmod 644 "$tmp"; mv -f "$tmp" "$f"
    echo "  escrito: $f"
}

remove_file() {
    [ -e "$1" ] || return 0
    backup "$1"; rm -f "$1"; echo "  eliminado: $1"
}

AWK_DEL='
function trim(s){ sub(/^[ \t]+/,"",s); sub(/[ \t\r]+$/,"",s); return s }
BEGIN{ n=split(keys, K, "|") }
/^[ \t]*\[/ { cur=trim($0) }
{
  if ((sec=="" || cur==sec) && match($0, /^[ \t]*[A-Za-z0-9_.-]+[ \t]*=/)) {
    k=substr($0, RSTART, RLENGTH); sub(/^[ \t]+/,"",k); sub(/[ \t]*=$/,"",k)
    for (i=1;i<=n;i++) if (K[i]==k) next
  }
  print
}'

AWK_SET='
function trim(s){ sub(/^[ \t]+/,"",s); sub(/[ \t\r]+$/,"",s); return s }
function is_key(line, k,   x){
  if (match(line, /^[ \t]*[A-Za-z0-9_.-]+[ \t]*=/)) {
    x=substr(line,RSTART,RLENGTH); sub(/^[ \t]+/,"",x); sub(/[ \t]*=$/,"",x); return x==k }
  return 0 }
/^[ \t]*\[/ {
  if (insec && !done) { print key "=" val; done=1 }
  insec=(trim($0)==sec); print; next }
{ if (insec && is_key($0,key)) { if (!done) { print key "=" val; done=1 } ; next }
  print }
END{
  if (insec && !done) { print key "=" val; done=1 }
  if (!done) { if (NR>0) print ""; print sec; print key "=" val } }'

AWK_SLIM='
$1==key { if (!done) { print key " " val; done=1 } ; next }
{ print }
END{ if (!done) print key " " val }'

# del_keys FICHERO "[sección]"|"" "clave1|clave2"
del_keys()  { apply_awk "$1" -v sec="$2" -v keys="$3" "$AWK_DEL"; }
# set_key FICHERO "[sección]" clave valor
set_key()   { apply_awk "$1" -v sec="$2" -v key="$3" -v val="$4" "$AWK_SET"; }
slim_set()  { apply_awk "$1" -v key="$2" -v val="$3" "$AWK_SLIM"; }

# ---------------------------------------------------------------- LightDM
handle_lightdm() {
    [ -d /etc/lightdm ] || command -v lightdm >/dev/null 2>&1 || return 1
    echo "LightDM:"
    mkdir -p /etc/lightdm/lightdm.conf.d
    local f dropin="/etc/lightdm/lightdm.conf.d/$DROPIN_NAME"
    # Quitar las claves de autologin de TODOS los ficheros de /etc (sin tocar secciones)
    for f in /etc/lightdm/lightdm.conf /etc/lightdm/lightdm.conf.d/*.conf; do
        [ -f "$f" ] || continue
        [ "$(basename "$f")" = "$DROPIN_NAME" ] && continue
        del_keys "$f" "" "autologin-user|autologin-user-timeout|autologin-guest"
    done
    if [ "$ACTION" = enable ]; then
        write_file "$dropin" "[Seat:*]\nautologin-user=$TARGET_USER\nautologin-user-timeout=0\n"
    elif grep -Eqs '^[[:space:]]*autologin-user[[:space:]]*=[[:space:]]*[^[:space:]]' \
            /usr/share/lightdm/lightdm.conf.d/*.conf; then
        # Un fichero del sistema (/usr/share) activa autologin: se anula con un override vacío
        write_file "$dropin" "[Seat:*]\nautologin-user=\n"
    else
        remove_file "$dropin"
    fi
    HANDLED+=(lightdm)
}

# ------------------------------------------------------------------- GDM3
handle_gdm() {
    local f found=0 files=(/etc/gdm3/daemon.conf /etc/gdm3/custom.conf /etc/gdm/custom.conf)
    for f in "${files[@]}"; do [ -f "$f" ] && found=1; done
    if [ "$found" -eq 0 ]; then
        { [ "$ACTION" = enable ] && [ -d /etc/gdm3 ]; } || return 1
        write_file /etc/gdm3/daemon.conf "[daemon]\n"
        files=(/etc/gdm3/daemon.conf)
    fi
    echo "GDM3:"
    for f in "${files[@]}"; do
        [ -f "$f" ] || continue
        if [ "$ACTION" = enable ]; then
            set_key "$f" "[daemon]" AutomaticLoginEnable true
            set_key "$f" "[daemon]" AutomaticLogin "$TARGET_USER"
        else
            del_keys "$f" "[daemon]" "AutomaticLoginEnable|AutomaticLogin"
        fi
    done
    HANDLED+=(gdm3)
}

# ------------------------------------------------------------------- SLiM
handle_slim() {
    [ -f /etc/slim.conf ] || return 1
    echo "SLiM:"
    if [ "$ACTION" = enable ]; then
        slim_set /etc/slim.conf default_user "$TARGET_USER"
        slim_set /etc/slim.conf auto_login yes
    else
        slim_set /etc/slim.conf auto_login no
    fi
    HANDLED+=(slim)
}

# ------------------------------------------------------------ verificación
verify_disabled() {
    local bad=0
    grep -Eqs '^[[:space:]]*autologin-user[[:space:]]*=[[:space:]]*[^[:space:]]' \
        /etc/lightdm/lightdm.conf /etc/lightdm/lightdm.conf.d/*.conf \
        && { echo "  ¡aún hay autologin-user activo en /etc/lightdm!" >&2; bad=1; }
    grep -Eqs '^[[:space:]]*AutomaticLoginEnable[[:space:]]*=[[:space:]]*(true|1|yes)' \
        /etc/gdm3/daemon.conf /etc/gdm3/custom.conf /etc/gdm/custom.conf \
        && { echo "  ¡aún hay AutomaticLoginEnable activo en GDM!" >&2; bad=1; }
    grep -Eqs '^[[:space:]]*auto_login[[:space:]]+yes' /etc/slim.conf \
        && { echo "  ¡aún hay auto_login activo en SLiM!" >&2; bad=1; }
    return $bad
}

handle_lightdm; handle_gdm; handle_slim

if [ "${#HANDLED[@]}" -eq 0 ]; then
    die "no se encontró ningún gestor de sesión compatible (LightDM, GDM3 o SLiM); no se cambió nada." 2
fi
if [ "$ACTION" = disable ]; then
    verify_disabled || die "la verificación final falló; revise la configuración manualmente." 3
fi
[ "$BACKED_UP" -eq 1 ] && echo "Copias de seguridad en: $BACKUP_DIR"
echo "Hecho (${HANDLED[*]}). Los cambios se aplican en el próximo inicio de sesión."
exit 0
