From 1dc56b170592e7ce403b9710164298ea02ea8097 Mon Sep 17 00:00:00 2001 From: boltgolt Date: Sat, 18 Feb 2023 20:07:32 +0100 Subject: [PATCH] Finally move fully to the compiled .so --- howdy/debian/control | 4 +- howdy/debian/install | 9 +-- howdy/debian/rules | 2 +- howdy/src/compare.py | 22 +++++-- howdy/src/pam-config/howdy | 2 +- howdy/src/pam.py | 130 ------------------------------------- howdy/src/pam/main.cc | 25 ++++++- howdy/src/pam/main.hh | 3 +- 8 files changed, 48 insertions(+), 149 deletions(-) delete mode 100644 howdy/src/pam.py diff --git a/howdy/debian/control b/howdy/debian/control index f414ce9..d164854 100644 --- a/howdy/debian/control +++ b/howdy/debian/control @@ -2,14 +2,14 @@ Source: howdy Section: misc Priority: optional Standards-Version: 3.9.7 -Build-Depends: python, devscripts, dh-make, debhelper, fakeroot, python3, python3-pip, python3-setuptools, python3-wheel, ninja-build, meson, libpam0g-dev, libboost-all-dev +Build-Depends: devscripts, git, dh-make, debhelper, fakeroot, python3, python3-pip, python3-setuptools, python3-wheel, ninja-build, meson, libpam0g-dev, libboost-all-dev, pkg-config, libevdev-dev, libinih-dev Maintainer: boltgolt Vcs-Git: https://github.com/boltgolt/howdy Package: howdy Homepage: https://github.com/boltgolt/howdy Architecture: amd64 -Depends: ${misc:Depends}, libc6, libgcc-s1, libpam0g, libstdc++6, curl | wget, python3, python3-pip, python3-dev, python3-setuptools, python3-numpy, python-opencv, libopencv-dev, cmake +Depends: ${misc:Depends}, libc6, libgcc-s1, libpam0g, libstdc++6, curl | wget, python3, python3-pip, python3-dev, python3-setuptools, python3-numpy, python-opencv | python3-opencv, libopencv-dev, cmake, libinih-dev Recommends: libatlas-base-dev | libopenblas-dev | liblapack-dev, howdy-gtk Suggests: nvidia-cuda-dev (>= 7.5) Description: Howdy: Windows Hello style authentication for Linux. diff --git a/howdy/debian/install b/howdy/debian/install index 3fadce0..cc3c9ea 100644 --- a/howdy/debian/install +++ b/howdy/debian/install @@ -1,16 +1,17 @@ src/cli/. lib/security/howdy/cli -src/dlib-data/. lib/security/howdy/dlib-data src/locales/. lib/security/howdy/locales src/recorders/. lib/security/howdy/recorders src/rubberstamps/. lib/security/howdy/rubberstamps src/cli.py lib/security/howdy src/compare.py lib/security/howdy -src/config.ini lib/security/howdy src/i18n.py lib/security/howdy src/logo.png lib/security/howdy -src/pam.py lib/security/howdy src/snapshot.py lib/security/howdy +build/pam_howdy.so lib/security/howdy + +src/dlib-data/. etc/howdy/dlib-data +src/config.ini etc/howdy + src/autocomplete/. usr/share/bash-completion/completions src/pam-config/. /usr/share/pam-configs -build/libpam_howdy.so lib/security/howdy diff --git a/howdy/debian/rules b/howdy/debian/rules index 41a89d7..91e16ac 100755 --- a/howdy/debian/rules +++ b/howdy/debian/rules @@ -11,7 +11,7 @@ build: # Create build dir meson setup -Dinih:with_INIReader=true build src/pam # Compile shared object - meson compile -C build + ninja -C build clean: # Delete mason build directory diff --git a/howdy/src/compare.py b/howdy/src/compare.py index 9d07dd7..4bcce8b 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -1,4 +1,5 @@ -# Compare incomming video with known faces +#!/usr/bin/env python3 +# Compare incoming video with known faces # Running in a local python instance to get around PATH issues # Import time so we can start timing asap @@ -23,11 +24,14 @@ import snapshot import numpy as np import _thread as thread -from i18n import _ +# Allow imports from the local howdy folder +sys.path.append('/lib/security/howdy') + from recorders.video_capture import VideoCapture +from i18n import _ def exit(code=None): - """Exit while closeing howdy-gtk properly""" + """Exit while closing howdy-gtk properly""" global gtk_proc # Exit the auth ui process if there is one @@ -99,8 +103,8 @@ def send_to_ui(type, message): if len(sys.argv) < 2: exit(12) -# Get the absolute path to the current directory -PATH = os.path.abspath(__file__ + "/..") +# Get the absolute path to the config directory +PATH = "/etc/howdy" # The username of the user being authenticated user = sys.argv[1] @@ -110,7 +114,7 @@ models = [] encodings = [] # Amount of ignored 100% black frames black_tries = 0 -# Amount of ingnored dark frames +# Amount of ignored dark frames dark_tries = 0 # Total amount of frames captured frames = 0 @@ -229,7 +233,7 @@ while True: # Show it in the ui as subtext send_to_ui("S", ui_subtext) - # Stop if we've exceded the time limit + # Stop if we've exceeded the time limit if time.time() - timings["fr"] > timeout: # Create a timeout snapshot if enabled if capture_failed: @@ -268,6 +272,7 @@ while True: dark_running_total += darkness valid_frames += 1 + # If the image exceeds darkness threshold due to subject distance, # skip to the next frame if (darkness > dark_threshold): @@ -279,6 +284,7 @@ while True: # Apply that factor to the frame frame = cv2.resize(frame, None, fx=scaling_factor, fy=scaling_factor, interpolation=cv2.INTER_AREA) gsframe = cv2.resize(gsframe, None, fx=scaling_factor, fy=scaling_factor, interpolation=cv2.INTER_AREA) + # If camera is configured to rotate = 1, check portrait in addition to landscape if rotate == 1: if frames % 3 == 1: @@ -287,6 +293,7 @@ while True: if frames % 3 == 2: frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE) gsframe = cv2.rotate(gsframe, cv2.ROTATE_90_CLOCKWISE) + # If camera is configured to rotate = 2, check portrait orientation elif rotate == 2: if frames % 2 == 0: @@ -295,6 +302,7 @@ while True: else: frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE) gsframe = cv2.rotate(gsframe, cv2.ROTATE_90_CLOCKWISE) + # Get all faces from that frame as encodings # Upsamples 1 time face_locations = face_detector(gsframe, 1) diff --git a/howdy/src/pam-config/howdy b/howdy/src/pam-config/howdy index 28fb6b1..ed19039 100644 --- a/howdy/src/pam-config/howdy +++ b/howdy/src/pam-config/howdy @@ -3,4 +3,4 @@ Default: yes Priority: 512 Auth-Type: Primary Auth: - [success=end default=ignore] pam_python.so /lib/security/howdy/pam.py + [success=end default=ignore] /lib/security/howdy/pam_howdy.so diff --git a/howdy/src/pam.py b/howdy/src/pam.py deleted file mode 100644 index 262b24c..0000000 --- a/howdy/src/pam.py +++ /dev/null @@ -1,130 +0,0 @@ -# PAM interface in python, launches compare.py - -# Import required modules -import subprocess -import os -import glob -import syslog - -# pam-python is running python 2, so we use the old module here -import ConfigParser - -# Read config from disk -config = ConfigParser.ConfigParser() -config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini") - - -def doAuth(pamh): - """Starts authentication in a separate process""" - - # Abort if Howdy is disabled - if config.getboolean("core", "disabled"): - return pamh.PAM_AUTHINFO_UNAVAIL - - # Abort if we're in a remote SSH env - if config.getboolean("core", "ignore_ssh"): - if "SSH_CONNECTION" in os.environ or "SSH_CLIENT" in os.environ or "SSHD_OPTS" in os.environ: - return pamh.PAM_AUTHINFO_UNAVAIL - - # Abort if lid is closed - if config.getboolean("core", "ignore_closed_lid"): - if any("closed" in open(f).read() for f in glob.glob("/proc/acpi/button/lid/*/state")): - return pamh.PAM_AUTHINFO_UNAVAIL - - # Abort if the video device does not exist - if not os.path.exists(config.get("video", "device_path")): - if config.getboolean("video", "warn_no_device"): - print("Camera path is not configured correctly, please edit the 'device_path' config value.") - return pamh.PAM_AUTHINFO_UNAVAIL - - # Set up syslog - syslog.openlog("[HOWDY]", 0, syslog.LOG_AUTH) - - # Alert the user that we are doing face detection - if config.getboolean("core", "detection_notice"): - pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Attempting face detection")) - - syslog.syslog(syslog.LOG_INFO, "Attempting facial authentication for user " + pamh.get_user()) - - # Run compare as python3 subprocess to circumvent python version and import issues - status = subprocess.call(["/usr/bin/python3", os.path.dirname(os.path.abspath(__file__)) + "/compare.py", pamh.get_user()]) - - # Status 10 means we couldn't find any face models - if status == 10: - if not config.getboolean("core", "suppress_unknown"): - pamh.conversation(pamh.Message(pamh.PAM_ERROR_MSG, "No face model known")) - - syslog.syslog(syslog.LOG_NOTICE, "Failure, no face model known") - syslog.closelog() - return pamh.PAM_USER_UNKNOWN - - # Status 11 means we exceded the maximum retry count - elif status == 11: - if config.getboolean("core", "timeout_notice"): - pamh.conversation(pamh.Message(pamh.PAM_ERROR_MSG, "Face detection timeout reached")) - syslog.syslog(syslog.LOG_INFO, "Failure, timeout reached") - syslog.closelog() - return pamh.PAM_AUTH_ERR - - # Status 12 means we aborted - elif status == 12: - syslog.syslog(syslog.LOG_INFO, "Failure, general abort") - syslog.closelog() - return pamh.PAM_AUTH_ERR - - # Status 13 means the image was too dark - elif status == 13: - pamh.conversation(pamh.Message(pamh.PAM_ERROR_MSG, "Face detection image too dark")) - syslog.syslog(syslog.LOG_INFO, "Failure, image too dark") - syslog.closelog() - return pamh.PAM_AUTH_ERR - - # Status 14 means a rubberstamp could not be given - elif status == 14: - pamh.conversation(pamh.Message(pamh.PAM_ERROR_MSG, "Rubberstamp denied")) - syslog.syslog(syslog.LOG_INFO, "Failure, rubberstamp did not succeed") - syslog.closelog() - return pamh.PAM_AUTH_ERR - - # Status 1 is probably a python crash - elif status == 1: - pamh.conversation(pamh.Message(pamh.PAM_ERROR_MSG, "Howdy encountered error, check stderr")) - syslog.syslog(syslog.LOG_INFO, "Failure, process crashed while authenticating") - syslog.closelog() - return pamh.PAM_SYSTEM_ERR - - # Status 0 is a successful exit - elif status == 0: - # Show the success message if it isn't suppressed - if not config.getboolean("core", "no_confirmation"): - pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Identified face as " + pamh.get_user())) - - syslog.syslog(syslog.LOG_INFO, "Login approved") - syslog.closelog() - return pamh.PAM_SUCCESS - - # Otherwise, we can't describe what happened but it wasn't successful - pamh.conversation(pamh.Message(pamh.PAM_ERROR_MSG, "Unknown error: " + str(status))) - syslog.syslog(syslog.LOG_INFO, "Failure, unknown error" + str(status)) - syslog.closelog() - return pamh.PAM_SYSTEM_ERR - - -def pam_sm_authenticate(pamh, flags, args): - """Called by PAM when the user wants to authenticate, in sudo for example""" - return doAuth(pamh) - - -def pam_sm_open_session(pamh, flags, args): - """Called when starting a session, such as su""" - return doAuth(pamh) - - -def pam_sm_close_session(pamh, flags, argv): - """We don't need to clean anyting up at the end of a session, so returns true""" - return pamh.PAM_SUCCESS - - -def pam_sm_setcred(pamh, flags, argv): - """We don't need set any credentials, so returns true""" - return pamh.PAM_SUCCESS diff --git a/howdy/src/pam/main.cc b/howdy/src/pam/main.cc index 530d5c8..e3e79e2 100644 --- a/howdy/src/pam/main.cc +++ b/howdy/src/pam/main.cc @@ -193,7 +193,7 @@ auto check_enabled(const INIReader &config) -> int { */ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv, bool auth_tok) -> int { - INIReader config("/lib/security/howdy/config.ini"); + INIReader config("/etc/howdy/config.ini"); openlog("pam_howdy", 0, LOG_AUTHPRIV); // Error out if we could not read the config file @@ -343,6 +343,27 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv, // The compare process has finished its execution child_task.stop(false); + // Get python process status code + int status = child_task.get(); + + // If python process ran into a timeout + // Do not send enter presses or terminate the PAM function, as the user might still be typing their password + if (status == CompareError::TIMEOUT_ACTIVE) { + // Wait for the password to be typed + pass_task.stop(false); + + char *password = nullptr; + std::tie(pam_res, password) = pass_task.get(); + + if (pam_res != PAM_SUCCESS) { + return pam_res; + } + + // The password has been entered, we are passing it to PAM stack + return PAM_IGNORE; + } + + // We want to stop the password prompt, either by canceling the thread when // workaround is set to "native", or by emulating "Enter" input with // "input" @@ -391,8 +412,6 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv, pass_task.stop(false); } - int status = child_task.get(); - return howdy_status(username, status, config, conv_function); } diff --git a/howdy/src/pam/main.hh b/howdy/src/pam/main.hh index 3d250fe..5b5dcf8 100644 --- a/howdy/src/pam/main.hh +++ b/howdy/src/pam/main.hh @@ -12,7 +12,8 @@ enum CompareError : int { NO_FACE_MODEL = 10, TIMEOUT_REACHED = 11, ABORT = 12, - TOO_DARK = 13 + TOO_DARK = 13, + TIMEOUT_ACTIVE = 2816, }; inline auto get_workaround(const std::string &workaround) -> Workaround {