From 609b8e99df5c76d905861d60d0bc540c89d105bd Mon Sep 17 00:00:00 2001 From: MusiKid Date: Sat, 26 Dec 2020 09:33:19 +0100 Subject: [PATCH] [WIP]: Add native PAM module Signed-off-by: MusiKid --- howdy/src/compare.py | 17 ++- src/pam/.gitignore | 1 + src/pam/README.md | 8 ++ src/pam/main.cc | 202 ++++++++++++++++++++++++++++++++++ src/pam/meson.build | 9 ++ src/pam/subprojects/inih.wrap | 3 + 6 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 src/pam/.gitignore create mode 100644 src/pam/README.md create mode 100644 src/pam/main.cc create mode 100644 src/pam/meson.build create mode 100644 src/pam/subprojects/inih.wrap diff --git a/howdy/src/compare.py b/howdy/src/compare.py index 3fd7f35..6cb346f 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -25,7 +25,7 @@ import _thread as thread from i18n import _ from recorders.video_capture import VideoCapture - +from evdev import UInput, ecodes as e def exit(code=None): """Exit while closeing howdy-gtk properly""" @@ -374,6 +374,21 @@ while True: "clahe": clahe }) + # Press enter key + if config.getboolean("experimental", "confirm"): + pipe_fd = int(os.getenv("PIPE_FD")) + pipe = os.fdopen(pipe_fd, 'w') + pipe.write('\255') + + enter_cap = { + e.EV_KEY: [e.KEY_ENTER] + } + device = UInput(enter_cap) + device.write(e.EV_KEY, e.KEY_ENTER, 1) + device.syn() + device.write(e.EV_KEY, e.KEY_ENTER, 0) + device.syn() + # End peacefully exit(0) diff --git a/src/pam/.gitignore b/src/pam/.gitignore new file mode 100644 index 0000000..feaa494 --- /dev/null +++ b/src/pam/.gitignore @@ -0,0 +1 @@ +subprojects/*/ diff --git a/src/pam/README.md b/src/pam/README.md new file mode 100644 index 0000000..736b68c --- /dev/null +++ b/src/pam/README.md @@ -0,0 +1,8 @@ +# Howdy PAM module + +## Build + +```sh +meson setup --wipe build -Dinih:with_INIReader=true +meson compile build +``` diff --git a/src/pam/main.cc b/src/pam/main.cc new file mode 100644 index 0000000..fa15083 --- /dev/null +++ b/src/pam/main.cc @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace std; + +int on_howdy_auth(int code, function conv_function) { + + switch (code) { + case 10: + conv_function(PAM_ERROR_MSG, "There is no face model known"); + syslog(LOG_NOTICE, "Failure, no face model known"); + break; + case 11: + syslog(LOG_INFO, "Failure, timeout reached"); + break; + case 12: + syslog(LOG_INFO, "Failure, general abort"); + break; + case 13: + syslog(LOG_INFO, "Failure, image too dark"); + break; + default: + conv_function(PAM_ERROR_MSG, + string("Unknown error:" + to_string(code)).c_str()); + syslog(LOG_INFO, "Failure, unknown error %d", code); + } + + return PAM_AUTH_ERR; +} + +int send_message(function + conv, + int type, const char *message) { + // No need to free this, it's allocated on the stack + const struct pam_message msg = {.msg_style = type, .msg = message}; + const struct pam_message *msgp = &msg; + + struct pam_response res_ = {}; + struct pam_response *resp_ = &res_; + + return conv(1, &msgp, &resp_, nullptr); +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv) { + INIReader reader("/lib/security/howdy/config.ini"); + openlog("[PAM_HOWDY]", 0, LOG_AUTHPRIV); + + struct pam_conv *conv = nullptr; + int pam_res = PAM_IGNORE; + + if ((pam_res = pam_get_item(pamh, PAM_CONV, (const void **)&conv)) != + PAM_SUCCESS) { + syslog(LOG_ERR, "Failed to acquire conversation"); + return pam_res; + } + + auto conv_function = + bind(send_message, (*conv->conv), placeholders::_1, placeholders::_2); + + if (reader.ParseError() < 0) { + syslog(LOG_ERR, "Failed to parse the configuration"); + return PAM_SYSTEM_ERR; + } + + if (reader.GetBoolean("core", "disabled", false)) { + return PAM_AUTHINFO_UNAVAIL; + } + + if (reader.GetBoolean("core", "ignore_ssh", true)) { + if (getenv("SSH_CONNECTION") != nullptr || + getenv("SSH_CLIENT") != nullptr || getenv("SSHD_OPTS") != nullptr) { + return PAM_AUTHINFO_UNAVAIL; + } + } + + if (reader.GetBoolean("core", "detection_notice", false)) { + if ((pam_res = conv_function(PAM_TEXT_INFO, + "Attempting facial authentication")) != + PAM_SUCCESS) { + syslog(LOG_ERR, "Failed to send detection notice"); + return pam_res; + } + } + + if (reader.GetBoolean("core", "ignore_closed_lid", true)) { + glob_t glob_result{}; + + int return_value = + glob("/proc/acpi/button/lid/*/state", 0, nullptr, &glob_result); + + // TODO: We ignore the result + if (return_value != 0) { + globfree(&glob_result); + } + + for (size_t i = 0; i < glob_result.gl_pathc; ++i) { + ifstream file(string(glob_result.gl_pathv[i])); + string lid_state; + getline(file, lid_state, (char)file.eof()); + if (lid_state.find("closed") != std::string::npos) { + globfree(&glob_result); + return PAM_AUTHINFO_UNAVAIL; + } + } + + globfree(&glob_result); + } + + char *user_ptr = nullptr; + if ((pam_res = pam_get_user(pamh, (const char **)&user_ptr, nullptr)) != + PAM_SUCCESS) { + syslog(LOG_ERR, "Failed to get username"); + return pam_res; + } + string user(user_ptr); + + posix_spawn_file_actions_t file_actions; + posix_spawn_file_actions_init(&file_actions); + posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO); + posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO); + vector args{"python", "/lib/security/howdy/compare.py", + user.c_str(), nullptr}; + pid_t child_pid; + if (posix_spawnp(&child_pid, "python", &file_actions, nullptr, + (char *const *)args.data(), nullptr) < 0) { + syslog(LOG_ERR, "Can't spawn the howdy process: %s", strerror(errno)); + return PAM_SYSTEM_ERR; + } + + packaged_task child_task([&] { + int status; + wait(&status); + return status; + }); + auto child_future = child_task.get_future(); + thread child_thread(move(child_task)); + + auto pass_future = async(launch::async, [&] { + char *auth_tok_ptr = nullptr; + int pam_res = pam_get_authtok(pamh, PAM_AUTHTOK, + (const char **)&auth_tok_ptr, nullptr); + return make_pair(auth_tok_ptr, pam_res); + }); + + auto pass = pass_future.get(); + + if (child_future.wait_for(1.5s) == future_status::timeout) { + kill(child_pid, SIGTERM); + } + child_thread.join(); + int howdy_status = child_future.get(); + + if (howdy_status == 0) { + if (!reader.GetBoolean("section", "no_confirmation", true)) { + string identify_msg("Identified face as " + user); + conv_function(PAM_TEXT_INFO, identify_msg.c_str()); + } + + syslog(LOG_INFO, "Login approved"); + return PAM_SUCCESS; + } else if ((get(pass) == PAM_SUCCESS && get(pass) != nullptr && + !string(get(pass)).empty()) || + WIFSIGNALED(howdy_status)) { + return PAM_IGNORE; + } else { + return on_howdy_auth(howdy_status, conv_function); + } +} diff --git a/src/pam/meson.build b/src/pam/meson.build new file mode 100644 index 0000000..efe07fa --- /dev/null +++ b/src/pam/meson.build @@ -0,0 +1,9 @@ +project('pam_howdy', 'cpp', version: '0.1.0') + +inih = subproject('inih') +inih_cpp = inih.get_variable('INIReader_dep') + +libpam = meson.get_compiler('c').find_library('pam') +boost = dependency('boost', modules: ['filesystem']) +threads = dependency('threads') +shared_library('pam_howdy', 'main.cc', dependencies: [boost, libpam, inih_cpp, threads], install: true, install_dir: '/lib/security/') diff --git a/src/pam/subprojects/inih.wrap b/src/pam/subprojects/inih.wrap new file mode 100644 index 0000000..8d02b50 --- /dev/null +++ b/src/pam/subprojects/inih.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://github.com/benhoyt/inih.git +revision = r52