diff --git a/howdy/src/bin/howdy.in b/howdy/src/bin/howdy.in new file mode 100644 index 0000000..e9f4953 --- /dev/null +++ b/howdy/src/bin/howdy.in @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +/usr/bin/env python3 "@script_path@" "$@" \ No newline at end of file diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py index 5a63bdf..19e8b8c 100644 --- a/howdy/src/cli/add.py +++ b/howdy/src/cli/add.py @@ -28,7 +28,7 @@ except ImportError as err: import cv2 # Test if at lest 1 of the data files is there and abort if it's not -if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat"): +if not os.path.isfile(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat"): print(_("Data files have not been downloaded, please run the following commands:")) print("\n\tcd " + paths.dlib_data_dir) print("\tsudo ./install.sh\n") @@ -36,20 +36,20 @@ if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.da # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir + "/config.ini") +config.read(paths.config_dir / "config.ini") use_cnn = config.getboolean("core", "use_cnn", fallback=False) if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir + "mmod_human_face_detector.dat") + face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir / "mmod_human_face_detector.dat") else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") -face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") +pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") +face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") user = builtins.howdy_user # The permanent file to store the encoded model in -enc_file = paths.user_models_dir + user + ".dat" +enc_file = paths.user_models_dir / f"{user}.dat" # Known encodings encodings = [] diff --git a/howdy/src/cli/clear.py b/howdy/src/cli/clear.py index aa43e15..c5a48b6 100644 --- a/howdy/src/cli/clear.py +++ b/howdy/src/cli/clear.py @@ -17,7 +17,7 @@ if not os.path.exists(paths.user_models_dir): sys.exit(1) # Check if the user has a models file to delete -if not os.path.isfile(paths.user_models_dir + user + ".dat"): +if not os.path.isfile(paths.user_models_dir / f"{user}.dat"): print(_("{} has no models or they have been cleared already").format(user)) sys.exit(1) @@ -33,5 +33,5 @@ if not builtins.howdy_args.y: sys.exit(1) # Delete otherwise -os.remove(paths.user_models_dir + user + ".dat") +os.remove(paths.user_models_dir / f"{user}.dat") print(_("\nModels cleared")) diff --git a/howdy/src/cli/config.py b/howdy/src/cli/config.py index 04c5179..c298099 100644 --- a/howdy/src/cli/config.py +++ b/howdy/src/cli/config.py @@ -20,4 +20,4 @@ elif os.path.isfile("/etc/alternatives/editor"): editor = "/etc/alternatives/editor" # Open the editor as a subprocess and fork it -subprocess.call([editor, paths.config_dir + "config.ini"]) +subprocess.call([editor, paths.config_dir / "config.ini"]) diff --git a/howdy/src/cli/disable.py b/howdy/src/cli/disable.py index 1f65541..ac98ea0 100644 --- a/howdy/src/cli/disable.py +++ b/howdy/src/cli/disable.py @@ -11,7 +11,7 @@ import paths from i18n import _ # Get the absolute filepath -config_path = os.path.dirname(paths.config_dir) + "/config.ini" +config_path = paths.config_dir.parent / "config.ini" # Read config from disk config = configparser.ConfigParser() diff --git a/howdy/src/cli/list.py b/howdy/src/cli/list.py index 7539837..2e5073a 100644 --- a/howdy/src/cli/list.py +++ b/howdy/src/cli/list.py @@ -19,7 +19,7 @@ if not os.path.exists(paths.user_models_dir): sys.exit(1) # Path to the models file -enc_file = paths.user_models_dir + user + ".dat" +enc_file = paths.user_models_dir / f"{user}.dat" # Try to load the models file and abort if the user does not have it yet try: diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py index 3789442..d4df673 100644 --- a/howdy/src/cli/remove.py +++ b/howdy/src/cli/remove.py @@ -27,7 +27,7 @@ if not os.path.exists(paths.user_models_dir): sys.exit(1) # Path to the models file -enc_file = paths.user_models_dir + user + ".dat" +enc_file = paths.user_models_dir / f"{user}.dat" # Try to load the models file and abort if the user does not have it yet try: @@ -71,7 +71,7 @@ if not found: # Remove the entire file if this encoding is the only one if len(encodings) == 1: - os.remove(paths.user_models_dir + user + ".dat") + os.remove(paths.user_models_dir / f"{user}.dat") print(_("Removed last model, howdy disabled for user")) else: # A place holder to contain the encodings that will remain diff --git a/howdy/src/cli/set.py b/howdy/src/cli/set.py index efbbee5..a4184b1 100644 --- a/howdy/src/cli/set.py +++ b/howdy/src/cli/set.py @@ -10,7 +10,7 @@ import paths from i18n import _ # Get the absolute filepath -config_path = os.path.dirname(paths.config_dir) + "/config.ini" +config_path = paths.config_dir / "/config.ini" # Check if enough arguments have been passed if len(builtins.howdy_args.arguments) < 2: diff --git a/howdy/src/cli/snap.py b/howdy/src/cli/snap.py index 2c625d3..e16187e 100644 --- a/howdy/src/cli/snap.py +++ b/howdy/src/cli/snap.py @@ -12,7 +12,7 @@ from i18n import _ # Read the config config = configparser.ConfigParser() -config.read(paths.config_dir + "config.ini") +config.read(paths.config_dir / "config.ini") # Start video capture video_capture = VideoCapture(config) diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py index 563be19..68477ed 100644 --- a/howdy/src/cli/test.py +++ b/howdy/src/cli/test.py @@ -20,7 +20,7 @@ path = "/etc/howdy" # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir + "config.ini") +config.read(paths.config_dir / "config.ini") if config.get("video", "recording_plugin", fallback="opencv") != "opencv": print(_("Howdy has been configured to use a recorder which doesn't support the test command yet, aborting")) @@ -60,20 +60,20 @@ use_cnn = config.getboolean('core', 'use_cnn', fallback=False) if use_cnn: face_detector = dlib.cnn_face_detection_model_v1( - paths.dlib_data_dir + "mmod_human_face_detector.dat" + paths.dlib_data_dir / "mmod_human_face_detector.dat" ) else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") -face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") +pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") +face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") encodings = [] models = None try: user = builtins.howdy_user - models = json.load(open(paths.user_models_dir + user + ".dat")) + models = json.load(open(paths.user_models_dir / f"{user}.dat")) for model in models: encodings += model["data"] diff --git a/howdy/src/compare.py b/howdy/src/compare.py index f81fe38..48d3e9f 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -49,7 +49,7 @@ def init_detector(lock): global face_detector, pose_predictor, face_encoder # Test if at lest 1 of the data files is there and abort if it's not - if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat"): + if not os.path.isfile(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat"): print(_("Data files have not been downloaded, please run the following commands:")) print("\n\tcd " + paths.dlib_data_dir) print("\tsudo ./install.sh\n") @@ -58,13 +58,13 @@ def init_detector(lock): # Use the CNN detector if enabled if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir + "mmod_human_face_detector.dat") + face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir / "mmod_human_face_detector.dat") else: face_detector = dlib.get_frontal_face_detector() # Start the others regardless - pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") - face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") + pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") + face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") # Note the time it took to initialize detectors timings["ll"] = time.time() - timings["ll"] @@ -127,7 +127,7 @@ face_encoder = None # Try to load the face model from the models folder try: - models = json.load(open(paths.user_models_dir + user + ".dat")) + models = json.load(open(paths.user_models_dir / f"{user}.dat")) for model in models: encodings += model["data"] @@ -140,7 +140,7 @@ if len(models) < 1: # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir + "config.ini") +config.read(paths.config_dir / "config.ini") # Get all config values needed use_cnn = config.getboolean("core", "use_cnn", fallback=False) diff --git a/howdy/src/meson.build b/howdy/src/meson.build new file mode 100644 index 0000000..bb127a5 --- /dev/null +++ b/howdy/src/meson.build @@ -0,0 +1,129 @@ +project('howdy', 'cpp', license: 'MIT', license_files: '../LICENSE', version: 'beta', meson_version: '>= 1.1.0') + +py = import('python').find_installation() +py.dependency() + +confdir = join_paths(get_option('sysconfdir'), 'howdy') +dlibdatadir = join_paths(confdir, 'dlib-data') +usermodelsdir = join_paths(confdir, 'models') +logpath = '/var/log/howdy' + +py_conf = configuration_data({ + 'config_dir': confdir, + 'dlib_data_dir': dlibdatadir, + 'user_models_dir': usermodelsdir, + 'log_path': logpath, +}) + +py_paths = configure_file( + input: 'paths.py.in', + output: 'paths.py', + configuration: py_conf, +) + +py_sources = [ + 'cli/__init__.py', + 'cli/add.py', + 'cli/clear.py', + 'cli/config.py', + 'cli/disable.py', + 'cli/list.py', + 'cli/remove.py', + 'cli/set.py', + 'cli/snap.py', + 'cli/test.py', + 'cli.py', + 'compare.py', + 'i18n.py', + 'recorders/__init__.py', + 'recorders/ffmpeg_reader.py', + 'recorders/pyv4l2_reader.py', + 'recorders/v4l2.py', + 'recorders/video_capture.py', + 'rubberstamps/__init__.py', + 'rubberstamps/hotkey.py', + 'rubberstamps/nod.py', + 'snapshot.py', + py_paths, +] + +# Include PAM module +compare_script_path = join_paths(py.get_install_dir(), 'howdy', 'compare.py') +subdir('pam') + +py.install_sources( + py_sources, + subdir: 'howdy', + preserve_path: true, +) + +install_data('logo.png', install_tag: 'meta') + +install_data('config.ini', install_dir: confdir, install_mode: 'rwxr--r--', install_tag: 'config') + +install_data('dlib-data/install.sh', install_dir: dlibdatadir, install_mode: 'rwxr--r--') + +install_data('dlib-data/Readme.md', install_dir: dlibdatadir, install_mode: 'r--r--r--', install_tag: 'docs') +install_man('../howdy.1') + +# if get_option('fetch_dlib_data') +# downloader = find_program('wget') +# bunzip2 = find_program('bunzip2') + +# links = [ +# 'https://github.com/davisking/dlib-models/raw/master/dlib_face_recognition_resnet_model_v1.dat.bz2', +# 'https://github.com/davisking/dlib-models/raw/master/mmod_human_face_detector.dat.bz2', +# 'https://github.com/davisking/dlib-models/raw/master/shape_predictor_5_face_landmarks.dat.bz2' +# ] + +# archived_model_files = [ +# 'dlib_face_recognition_resnet_model_v1.dat.bz2', +# 'shape_predictor_5_face_landmarks.dat.bz2', +# 'mmod_human_face_detector.dat.bz2' +# ] + +# download = run_command( +# 'download', +# links, +# output: archived_model_files, +# command: [downloader, '-O', '@OUTPUT@', '@INPUT@'] +# ) + +# model_files = [ +# 'dlib_face_recognition_resnet_model_v1.dat', +# 'shape_predictor_5_face_landmarks.dat', +# 'mmod_human_face_detector.dat' +# ] + +# models = custom_target( +# 'models', +# input: archived_model_files, +# output: model_files, +# command: [bunzip2, '-k', '@INPUT@'], +# ) + +# install_data( +# model_files, +# install_dir: join_paths(get_option('prefix'), get_option('libdir'), 'dlib_models'), +# ) + +# endif + +py_path = py.get_install_dir() +cli_path = join_paths(py_path, 'howdy', 'cli.py') + +conf_data = configuration_data({ 'script_path': cli_path }) + +bin_name = 'howdy' + +bin = configure_file( + input: 'bin/howdy.in', + output: bin_name, + configuration: conf_data +) + +install_data( + bin, + install_mode: 'rwxr-xr-x', + install_dir: get_option('bindir'), +) \ No newline at end of file diff --git a/howdy/src/meson.options b/howdy/src/meson.options new file mode 100644 index 0000000..0586054 --- /dev/null +++ b/howdy/src/meson.options @@ -0,0 +1,2 @@ +option('pam_dir', type: 'string', value: '/lib/security', description: 'Set the pam_howdy destination directory') +#option('fetch_dlib_data', type: 'boolean', value: false, description: 'Download dlib data files') \ No newline at end of file diff --git a/howdy/src/pam/main.cc b/howdy/src/pam/main.cc index 290a549..2f7a996 100644 --- a/howdy/src/pam/main.cc +++ b/howdy/src/pam/main.cc @@ -41,12 +41,12 @@ #include "enter_device.hh" #include "main.hh" #include "optional_task.hh" +#include "paths.hh" const auto DEFAULT_TIMEOUT = std::chrono::duration(100); const auto MAX_RETRIES = 5; const auto PYTHON_EXECUTABLE = "python3"; -const auto COMPARE_PROCESS_PATH = "/lib/security/howdy/compare.py"; #define S(msg) gettext(msg) @@ -80,7 +80,8 @@ auto howdy_error(int status, syslog(LOG_ERR, "Failure, image too dark"); break; case CompareError::INVALID_DEVICE: - syslog(LOG_ERR, "Failure, not possible to open camera at configured path"); + syslog(LOG_ERR, + "Failure, not possible to open camera at configured path"); break; default: conv_function(PAM_ERROR_MSG, @@ -321,7 +322,8 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv, // Wait for the end either of the child or the password input { std::unique_lock lock(mutx); - convar.wait(lock, [&] { return confirmation_type != ConfirmationType::Unset; }); + convar.wait(lock, + [&] { return confirmation_type != ConfirmationType::Unset; }); } // The password has been entered or an error has occurred diff --git a/howdy/src/pam/meson.build b/howdy/src/pam/meson.build index be93919..3cb0161 100644 --- a/howdy/src/pam/meson.build +++ b/howdy/src/pam/meson.build @@ -1,5 +1,3 @@ -project('pam_howdy', 'cpp', version: '0.1.0', default_options: ['cpp_std=c++14']) - inih_cpp = dependency('INIReader', fallback: ['inih', 'INIReader_dep']) libevdev = dependency('libevdev') libpam = meson.get_compiler('cpp').find_library('pam') @@ -8,6 +6,16 @@ threads = dependency('threads') # Translations subdir('po') +# Paths +paths = { 'compare_script_path': compare_script_path } + +paths_h = configure_file( + input: 'paths.hh.in', + output: 'paths.hh', + configuration: paths, + install_dir: get_option('pam_dir') +) + shared_library( 'pam_howdy', 'main.cc', @@ -18,7 +26,10 @@ shared_library( threads, libevdev, ], + link_depends: [ + paths_h, + ], install: true, - install_dir: '/lib/security', + install_dir: get_option('pam_dir'), name_prefix: '' ) diff --git a/howdy/src/pam/paths.hh.in b/howdy/src/pam/paths.hh.in new file mode 100644 index 0000000..df21928 --- /dev/null +++ b/howdy/src/pam/paths.hh.in @@ -0,0 +1 @@ +const auto COMPARE_PROCESS_PATH = "@compare_script_path@"; \ No newline at end of file diff --git a/howdy/src/paths.py b/howdy/src/paths.py.in similarity index 52% rename from howdy/src/paths.py rename to howdy/src/paths.py.in index 2282540..e51d738 100644 --- a/howdy/src/paths.py +++ b/howdy/src/paths.py.in @@ -1,12 +1,13 @@ +from pathlib import PurePath # Define the absolute path to the config directory -config_dir = "/etc/howdy/" +config_dir = PurePath("@config_dir@") # Define the absolute path to the DLib models data directory -dlib_data_dir = config_dir + "/dlib-data/" +dlib_data_dir = PurePath("@dlib_data_dir@") # Define the absolute path to the Howdy user models directory -user_models_dir = config_dir + "/models/" +user_models_dir = PurePath("@user_models_dir@") # Define path to any howdy logs -log_path = "/var/log/howdy" +log_path = PurePath("@log_path@") diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py index 9f2f563..34e3428 100644 --- a/howdy/src/snapshot.py +++ b/howdy/src/snapshot.py @@ -52,13 +52,13 @@ def generate(frames, text_lines): # Made sure a snapshot folder exist if not os.path.exists(paths.log_path): os.makedirs(paths.log_path) - if not os.path.exists(paths.log_path + "/snapshots"): - os.makedirs(paths.log_path + "/snapshots") + if not os.path.exists(paths.log_path / "snapshots"): + os.makedirs(paths.log_path / "snapshots") # Generate a filename based on the current time filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg") # Write the image to that file - cv2.imwrite(paths.log_path + "/snapshots/" + filename, snap) + cv2.imwrite(paths.log_path / "snapshots" / filename, snap) # Return the saved file location - return paths.log_path + "/snapshots/" + filename + return paths.log_path / "/snapshots/" / filename