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