diff --git a/src/pam/main.cc b/src/pam/main.cc index 3c1cdba..2af7270 100644 --- a/src/pam/main.cc +++ b/src/pam/main.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include +#include #include #include #include @@ -30,16 +32,19 @@ #include #include + #include #include #include #include +#include "main.hh" +#include "optional_task.hh" + using namespace std; using namespace boost::locale; - -enum class Type { Howdy, Pam }; +using namespace std::chrono_literals; /** * Inspect the status code returned by the compare process @@ -56,7 +61,8 @@ int on_howdy_auth(int code, function conv_function) { switch (code) { // Status 10 means we couldn't find any face models case 10: - conv_function(PAM_ERROR_MSG, dgettext("pam", "There is no face model known")); + conv_function(PAM_ERROR_MSG, + dgettext("pam", "There is no face model known")); syslog(LOG_NOTICE, "Failure, no face model known"); break; // Status 11 means we exceded the maximum retry count @@ -69,13 +75,15 @@ int on_howdy_auth(int code, function conv_function) { break; // Status 13 means the image was too dark case 13: - conv_function(PAM_ERROR_MSG, dgettext("pam", "Face detection image too dark")); + conv_function(PAM_ERROR_MSG, + dgettext("pam", "Face detection image too dark")); syslog(LOG_INFO, "Failure, image too dark"); break; // Otherwise, we can't describe what happened but it wasn't successful default: - conv_function(PAM_ERROR_MSG, - string(dgettext("pam", "Unknown error:") + to_string(code)).c_str()); + conv_function( + PAM_ERROR_MSG, + string(dgettext("pam", "Unknown error:") + to_string(code)).c_str()); syslog(LOG_INFO, "Failure, unknown error %d", code); } } @@ -109,6 +117,17 @@ int send_message(function child_task([&] { + // This task wait for the status of the python subprocess (we don't want a + // zombie process). + optional_task child_task(packaged_task([&] { int status; wait(&status); { @@ -239,48 +266,50 @@ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv, } cv.notify_all(); return status; - }); - auto child_future = child_task.get_future(); - thread child_thread(move(child_task)); + })); - packaged_task pass_task([&] { - char *auth_tok_ptr = nullptr; - int pam_res = pam_get_authtok(pamh, PAM_AUTHTOK, - (const char **)&auth_tok_ptr, nullptr); - { - unique_lock lk(m); - t = Type::Pam; - } - cv.notify_all(); - return pam_res; - }); - auto pass_future = pass_task.get_future(); - thread pass_thread; + optional_task> pass_task( + packaged_task()>([&] { + char *auth_tok_ptr = nullptr; + int pam_res = pam_get_authtok(pamh, PAM_AUTHTOK, + (const char **)&auth_tok_ptr, nullptr); + { + unique_lock lk(m); + t = Type::Pam; + } + cv.notify_all(); + return tuple(pam_res, auth_tok_ptr); + })); if (auth_tok) { - pass_thread = thread(move(pass_task)); + pass_task.activate(); } - { + // Wait for the end + if (workaround == Workaround::Native) { unique_lock lk(m); cv.wait(lk); + } else if (auth_tok) { + pass_task.stop(false); } if (t == Type::Howdy) { if (auth_tok) { // We cancel the thread using pthread, pam_get_authtok seems to be a // cancellation point - auto native_hd = pass_thread.native_handle(); - pthread_cancel(native_hd); - pass_thread.join(); + if (child_task.wait(1s) == future_status::timeout) { + kill(child_pid, SIGTERM); + } + child_task.stop(false); + pass_task.stop(false); } - child_thread.join(); - int howdy_status = child_future.get(); + int howdy_status = child_task.get(); if (howdy_status == 0) { if (!reader.GetBoolean("core", "no_confirmation", true)) { // Construct confirmation text from i18n string string confirm_text = dgettext("pam", "Identified face as {}"); - string identify_msg(confirm_text.replace(confirm_text.find("{}"), 2, string(user_ptr))); + string identify_msg( + confirm_text.replace(confirm_text.find("{}"), 2, string(user_ptr))); // Send confirmation message to user conv_function(PAM_TEXT_INFO, identify_msg.c_str()); } @@ -290,13 +319,40 @@ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv, return on_howdy_auth(howdy_status, conv_function); } } else { - kill(child_pid, SIGTERM); - pass_thread.join(); - auto pam_res = pass_future.get(); + if (!(workaround == Workaround::Native)) { + if (child_task.wait(1s) == future_status::timeout) { + kill(child_pid, SIGTERM); + } + child_task.stop(false); + } + if (auth_tok) { + pass_task.stop(false); + } + + char *token = nullptr; + tie(pam_res, token) = pass_task.get(); if (pam_res != PAM_SUCCESS) return pam_res; + int howdy_status = child_task.get(); + if (strlen(token) == 0) { + if (howdy_status == 0) { + if (!reader.GetBoolean("core", "no_confirmation", true)) { + // Construct confirmation text from i18n string + string confirm_text = dgettext("pam", "Identified face as {}"); + string identify_msg(confirm_text.replace(confirm_text.find("{}"), 2, + string(user_ptr))); + // Send confirmation message to user + conv_function(PAM_TEXT_INFO, identify_msg.c_str()); + } + syslog(LOG_INFO, "Login approved"); + return PAM_SUCCESS; + } else { + return on_howdy_auth(howdy_status, conv_function); + } + } + return PAM_IGNORE; } } diff --git a/src/pam/main.hh b/src/pam/main.hh new file mode 100644 index 0000000..e3d6b29 --- /dev/null +++ b/src/pam/main.hh @@ -0,0 +1,8 @@ +#ifndef MAIN_H_ +#define MAIN_H_ + +enum class Type { Howdy, Pam }; + +enum class Workaround { Off, Input, Native }; + +#endif // MAIN_H_ diff --git a/src/pam/meson.build b/src/pam/meson.build index bd2adf1..45bd614 100644 --- a/src/pam/meson.build +++ b/src/pam/meson.build @@ -1,9 +1,9 @@ -project('pam_howdy', 'cpp', version: '0.1.0', default_options: ['cpp_std=c++11']) +project('pam_howdy', 'cpp', version: '0.1.0', default_options: ['cpp_std=c++14']) inih = subproject('inih') inih_cpp = inih.get_variable('INIReader_dep') libpam = meson.get_compiler('c').find_library('pam') -boost = dependency('boost', modules: ['filesystem']) +boost = dependency('boost') 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/optional_task.hh b/src/pam/optional_task.hh new file mode 100644 index 0000000..5048395 --- /dev/null +++ b/src/pam/optional_task.hh @@ -0,0 +1,64 @@ +#ifndef OPTIONAL_TASK_H_ +#define OPTIONAL_TASK_H_ + +#include +#include +#include +#include + +template class optional_task { + std::thread _thread; + std::packaged_task _task; + std::future _future; + std::atomic _spawned; + std::atomic _is_active; + +public: + optional_task(std::packaged_task); + void activate(); + std::future_status wait(std::chrono::duration); + T get(); + void stop(bool); + ~optional_task(); +}; + +template +optional_task::optional_task(std::packaged_task t) + : _task(std::move(t)), _future(_task.get_future()) {} + +template void optional_task::activate() { + _thread = std::thread(std::move(_task)); + _spawned = true; + _is_active = true; +} +template +std::future_status optional_task::wait(std::chrono::duration dur) { + return _future.wait_for(dur); +} + +template T optional_task::get() { + assert(!_is_active && _spawned); + return _future.get(); +} + +template void optional_task::stop(bool force) { + if (!(_is_active && _thread.joinable()) && _spawned) { + _is_active = false; + return; + } + + // We use pthread to cancel the thread + if (force) { + auto native_hd = _thread.native_handle(); + pthread_cancel(native_hd); + } + _thread.join(); + _is_active = false; +} + +template optional_task::~optional_task() { + if (_is_active && _spawned) + stop(false); +} + +#endif // OPTIONAL_TASK_H_