160 lines
5.1 KiB
Python
Executable File
160 lines
5.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import sys
|
|
import os
|
|
import ctypes
|
|
import ctypes.util
|
|
import getpass
|
|
import subprocess
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# 1. PAM C API Authentication Engine (with persistent callback & libc allocations)
|
|
# ------------------------------------------------------------------------------
|
|
libpam = None
|
|
libc = None
|
|
|
|
try:
|
|
libpam_path = ctypes.util.find_library('pam') or 'libpam.so.0'
|
|
libpam = ctypes.CDLL(libpam_path)
|
|
libc = ctypes.CDLL(None)
|
|
libc.calloc.restype = ctypes.c_void_p
|
|
libc.calloc.argtypes = [ctypes.c_size_t, ctypes.c_size_t]
|
|
libc.strdup.restype = ctypes.c_void_p
|
|
libc.strdup.argtypes = [ctypes.c_char_p]
|
|
except Exception:
|
|
libpam = None
|
|
|
|
class PamMessage(ctypes.Structure):
|
|
_fields_ = [('msg_style', ctypes.c_int), ('msg', ctypes.c_char_p)]
|
|
|
|
class PamResponse(ctypes.Structure):
|
|
_fields_ = [('resp', ctypes.c_void_p), ('resp_retcode', ctypes.c_int)]
|
|
|
|
PamConvFunc = ctypes.CFUNCTYPE(
|
|
ctypes.c_int,
|
|
ctypes.c_int,
|
|
ctypes.POINTER(ctypes.POINTER(PamMessage)),
|
|
ctypes.POINTER(ctypes.POINTER(PamResponse)),
|
|
ctypes.c_void_p
|
|
)
|
|
|
|
class PamConv(ctypes.Structure):
|
|
_fields_ = [('conv', PamConvFunc), ('appdata_ptr', ctypes.c_void_p)]
|
|
|
|
def _py_pam_conv(n_messages, messages, p_response, appdata_ptr):
|
|
if not appdata_ptr or not libc:
|
|
return 19 # PAM_CONV_ERR
|
|
arr = libc.calloc(n_messages, ctypes.sizeof(PamResponse))
|
|
responses = ctypes.cast(arr, ctypes.POINTER(PamResponse))
|
|
|
|
for i in range(n_messages):
|
|
msg = messages[i].contents
|
|
if msg.msg_style in (1, 2): # PAM_PROMPT_ECHO_OFF or PAM_PROMPT_ECHO_ON
|
|
p_bytes = ctypes.cast(appdata_ptr, ctypes.c_char_p).value
|
|
if p_bytes:
|
|
responses[i].resp = libc.strdup(p_bytes)
|
|
responses[i].resp_retcode = 0
|
|
|
|
p_response[0] = responses
|
|
return 0
|
|
|
|
# Store persistent C-callback function reference to prevent GC cleanup
|
|
GLOBAL_PAM_CONV_FUNC = PamConvFunc(_py_pam_conv)
|
|
|
|
def verify_via_libpam(username, password):
|
|
if not libpam or not libc:
|
|
return False
|
|
|
|
pwd_bytes = password.encode('utf-8')
|
|
pwd_buf = ctypes.c_char_p(pwd_bytes)
|
|
conv = PamConv(GLOBAL_PAM_CONV_FUNC, ctypes.cast(pwd_buf, ctypes.c_void_p))
|
|
|
|
for service in ['system-auth', 'system-local-login', 'system-login', 'login', 'passwd', 'kde', 'sddm', 'gdm', 'su', 'other']:
|
|
try:
|
|
pamh = ctypes.c_void_p()
|
|
res = libpam.pam_start(service.encode('utf-8'), username.encode('utf-8'), ctypes.byref(conv), ctypes.byref(pamh))
|
|
if res == 0:
|
|
auth_res = libpam.pam_authenticate(pamh, 0)
|
|
libpam.pam_end(pamh, auth_res)
|
|
if auth_res == 0:
|
|
return True
|
|
except Exception:
|
|
pass
|
|
|
|
return False
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# 2. Sudo Authentication Engine (Fallback for user password validation)
|
|
# ------------------------------------------------------------------------------
|
|
def verify_via_sudo(username, password):
|
|
try:
|
|
# Reset sudo timestamp cache first so sudo always asks for password
|
|
subprocess.run(['sudo', '-k'], capture_output=True, timeout=2)
|
|
|
|
# Validate password via sudo -S -v
|
|
proc = subprocess.Popen(
|
|
['sudo', '-S', '-v'],
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE
|
|
)
|
|
pwd_bytes = (password + '\n').encode('utf-8')
|
|
_, _ = proc.communicate(input=pwd_bytes, timeout=3)
|
|
return (proc.returncode == 0)
|
|
except Exception:
|
|
return False
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# 3. Su Authentication Engine (Fallback for user password validation)
|
|
# ------------------------------------------------------------------------------
|
|
def verify_via_su(username, password):
|
|
try:
|
|
proc = subprocess.Popen(
|
|
['su', '-c', 'true', username],
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE
|
|
)
|
|
pwd_bytes = (password + '\n').encode('utf-8')
|
|
_, _ = proc.communicate(input=pwd_bytes, timeout=3)
|
|
return (proc.returncode == 0)
|
|
except Exception:
|
|
return False
|
|
|
|
def verify_password(username, password):
|
|
if not password:
|
|
return False
|
|
|
|
# 1. Primary method: C libpam API
|
|
if verify_via_libpam(username, password):
|
|
return True
|
|
|
|
# 2. Fallback method: Sudo password validation
|
|
if verify_via_sudo(username, password):
|
|
return True
|
|
|
|
# 3. Fallback method: Su password validation
|
|
if verify_via_su(username, password):
|
|
return True
|
|
|
|
return False
|
|
|
|
def main():
|
|
password = ""
|
|
if len(sys.argv) >= 2:
|
|
password = sys.argv[1]
|
|
else:
|
|
password = sys.stdin.read().strip()
|
|
|
|
username = getpass.getuser()
|
|
if len(sys.argv) >= 3:
|
|
username = sys.argv[2].strip()
|
|
|
|
success = verify_password(username, password)
|
|
if success:
|
|
sys.exit(0)
|
|
else:
|
|
sys.exit(1)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|