feat: Implement a PIN-based authentication system for sudo using a shell script, C wrapper, and Arch Linux package for PAM integration.

This commit is contained in:
Zeev Diukman 2026-01-20 18:10:14 +02:00
commit 2f4023886e
4 changed files with 276 additions and 0 deletions

30
PKGBUILD Normal file
View file

@ -0,0 +1,30 @@
# Maintainer: Zeev <zeev@example.com>
pkgname=z-auth
pkgver=0.1.4
pkgrel=1
pkgdesc="Secure Z-Authentication Script (Hidden Source)"
arch=('any')
url="local"
license=('custom')
depends=('bash' 'zenity' 'sudo')
makedepends=('gcc')
install=z-auth.install
source=("z-auth.sh" "z-auth-wrapper.c")
md5sums=('SKIP' 'SKIP')
build() {
# Compile the setuid wrapper
gcc -o z-auth-wrapper z-auth-wrapper.c
}
package() {
# 1. Install the wrapper binary as /usr/bin/z-auth
# -D creates directories
# -m4755 sets permissions to rwsr-xr-x (Setuid Root)
install -Dm4755 z-auth-wrapper "$pkgdir/usr/bin/z-auth"
# 2. Install the actual script to a protected location
# /usr/lib/z-auth/core.sh
# -m0700 sets permissions to rwx------ (Root only)
install -Dm0700 z-auth.sh "$pkgdir/usr/lib/z-auth/core.sh"
}

27
z-auth-wrapper.c Normal file
View file

@ -0,0 +1,27 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char *argv[]) {
/*
FIX: Bash drops privileges if Real UID != Effective UID.
We are running setuid root (EUID=0), but Real UID is the user.
We must promote ourselves to full root (Real UID = E-UID = 0)
so Bash treats us as root and allows reading the 0700 script.
*/
if (setuid(0) != 0) {
perror("Failed to setuid(0)");
return 1;
}
char *script_path = "/usr/lib/z-auth/core.sh";
char *new_argv[] = { "/bin/bash", script_path, NULL };
extern char **environ;
execve("/bin/bash", new_argv, environ);
perror("Failed to exec z-auth core script");
return 1;
}

42
z-auth.install Normal file
View file

@ -0,0 +1,42 @@
PAM_FILE="/etc/pam.d/sudo"
AUTH_LINE="auth sufficient pam_exec.so quiet stdout /usr/bin/z-auth"
post_install() {
echo ":: Configuring PAM for z-auth..."
# Check if line already exists
if grep -q "/usr/bin/z-auth" "$PAM_FILE"; then
echo " PAM configuration already exists. Skipping."
else
# Backup
cp "$PAM_FILE" "$PAM_FILE.bak_zauth"
# Insert after the first line (usually #%PAM-1.0)
# This places it at the very top of the rules, which is what we want for 'sufficient'
sed -i "2i$AUTH_LINE" "$PAM_FILE"
echo " Added z-auth to $PAM_FILE"
fi
# Optional: Warning if the OLD manual line still exists
if grep -q "/usr/local/bin/z-auth.sh" "$PAM_FILE"; then
echo " WARNING: You have an old configuration pointing to /usr/local/bin/z-auth.sh."
echo " You should remove it manually to avoid redundancy."
fi
}
post_upgrade() {
post_install
}
post_remove() {
echo ":: Removing z-auth from PAM configuration..."
if grep -q "/usr/bin/z-auth" "$PAM_FILE"; then
# Create backup before verifying
cp "$PAM_FILE" "$PAM_FILE.bak_zauth_remove"
# Remove the exact line (or lines containing the binary path)
sed -i "\|/usr/bin/z-auth|d" "$PAM_FILE"
echo " Removed z-auth configuration."
fi
}

177
z-auth.sh Executable file
View file

@ -0,0 +1,177 @@
#!/bin/bash
exec 2>/dev/null
PINCODE="2606"
# Detect the target user (default to current user or UID 1000 user if running as root)
get_target_user() {
if [ -n "$PAM_USER" ]; then
echo "$PAM_USER"
elif [ -n "$SUDO_USER" ]; then
echo "$SUDO_USER"
else
echo "$USER"
fi
}
# Helper to find the correct Xauthority file
get_xauthority() {
local target_user="$1"
local target_uid
target_uid=$(id -u "$target_user")
# Common locations for Xauthority
local candidates=(
"/run/user/$target_uid/gdm/Xauthority"
"/run/user/$target_uid/.mutter-Xwaylandauth.*"
"/home/$target_user/.Xauthority"
)
for pattern in "${candidates[@]}"; do
# Expand glob pattern
for file in $pattern; do
if [ -r "$file" ]; then
echo "$file"
return 0
fi
done
done
return 1
}
# Identify a usable TTY device
identify_tty() {
# 1. Try standard /dev/tty (standard controlling terminal)
# Use subshell to test opening it without exiting script on failure
if ( : < /dev/tty ) 2>/dev/null; then
echo "/dev/tty"
return 0
fi
# 2. Try PAM_TTY if set (provided by PAM module)
if [ -n "$PAM_TTY" ]; then
# Use simple pattern matching
case "$PAM_TTY" in
/dev/*)
if [ -c "$PAM_TTY" ]; then
echo "$PAM_TTY"
return 0
fi
;;
ssh|:*)
# Not a char device (X11 or SSH)
return 1
;;
*)
# Potentially a device name without /dev/ (e.g. tty1)
if [ -c "/dev/$PAM_TTY" ]; then
echo "/dev/$PAM_TTY"
return 0
fi
;;
esac
fi
return 1
}
verify_pin() {
local input="$1"
if [ "$input" = "$PINCODE" ]; then
exit 0
else
return 1
fi
}
z_auth_gui() {
local target_user
target_user=$(get_target_user)
# Fallback/Guess display if needed
if [ -z "$DISPLAY" ]; then
export DISPLAY=:0
fi
# Try to find authority file
local auth_file
auth_file=$(get_xauthority "$target_user")
if [ -n "$auth_file" ]; then
export XAUTHORITY="$auth_file"
fi
local input_pincode
input_pincode=$(sudo -u "$target_user" zenity --entry \
--title="Security Check" \
--text="Enter Sudo PIN for $target_user:" \
--hide-text \
--width=300 2>/dev/null)
# If zenity failed/cancelled, return 1 to allow fallback
# DO NOT exit 1 here, otherwise we kill the script before trying TTY
if [ $? -ne 0 ]; then
return 1
fi
if verify_pin "$input_pincode"; then
exit 0
else
sudo -u "$target_user" zenity --error --text="Wrong PIN!" --no-wrap 2>/dev/null
exit 1
fi
}
z_auth_no_gui() {
local tty_device="$1"
if [ -z "$tty_device" ] || [ ! -c "$tty_device" ]; then
# Check failed, but silenced now
exit 1
fi
local input_pincode
# Use the identified TTY device for input and output
echo -n "Enter Security PIN: " > "$tty_device"
read -s input_pincode < "$tty_device"
echo > "$tty_device"
if verify_pin "$input_pincode"; then
exit 0
else
echo "Incorrect PIN." > "$tty_device"
exit 1
fi
}
main_function() {
# Determine where we are
local identified_tty
identified_tty=$(identify_tty) # e.g. /dev/tty3 or /dev/pts/1
# Logic:
# 1. If we are on a REAL console (/dev/tty1...tty6), FORCE TTY mode.
# Why? Because forcing GUI (DISPLAY=:0) on TTY3 will spawn a window
# on TTY1/2 (where X is), which is invisible to the user on TTY3, causing a hang.
if [[ "$identified_tty" =~ /dev/tty[0-9]+ ]]; then
z_auth_no_gui "$identified_tty"
# If it returns, it passed validation
return
fi
# 2. If we are NOT on a real console (e.g. /dev/pts/* or no TTY), try GUI first.
# This covers GNOME Terminal, SSH X11 forwarding, etc.
# If GUI fails (e.g. headless SSH), fallback to TTY.
z_auth_gui
# 3. GUI failed or returned 1? Fallback to TTY if we have one.
if [ -n "$identified_tty" ]; then
z_auth_no_gui "$identified_tty"
else
# No GUI, no TTY -> Fail
exit 1
fi
}
main_function