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:
commit
2f4023886e
4 changed files with 276 additions and 0 deletions
30
PKGBUILD
Normal file
30
PKGBUILD
Normal 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
27
z-auth-wrapper.c
Normal 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
42
z-auth.install
Normal 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
177
z-auth.sh
Executable 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
|
||||||
Loading…
Reference in a new issue