feat: add workarounds

This commit is contained in:
MusiKid 2021-06-30 22:21:10 +02:00 committed by musikid
parent 7ee0f361fb
commit d078f81baa
No known key found for this signature in database
GPG key ID: 7567D43648C6E2F4
4 changed files with 174 additions and 46 deletions

View file

@ -2,6 +2,7 @@
#include <csignal> #include <csignal>
#include <cstdlib> #include <cstdlib>
#include <glob.h> #include <glob.h>
#include <ostream>
#include <poll.h> #include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <spawn.h> #include <spawn.h>
@ -13,6 +14,7 @@
#include <syslog.h> #include <syslog.h>
#include <unistd.h> #include <unistd.h>
#include <atomic>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <cstring> #include <cstring>
@ -30,16 +32,19 @@
#include <vector> #include <vector>
#include <INIReader.h> #include <INIReader.h>
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <security/pam_appl.h> #include <security/pam_appl.h>
#include <security/pam_ext.h> #include <security/pam_ext.h>
#include <security/pam_modules.h> #include <security/pam_modules.h>
#include "main.hh"
#include "optional_task.hh"
using namespace std; using namespace std;
using namespace boost::locale; using namespace boost::locale;
using namespace std::chrono_literals;
enum class Type { Howdy, Pam };
/** /**
* Inspect the status code returned by the compare process * Inspect the status code returned by the compare process
@ -56,7 +61,8 @@ int on_howdy_auth(int code, function<int(int, const char *)> conv_function) {
switch (code) { switch (code) {
// Status 10 means we couldn't find any face models // Status 10 means we couldn't find any face models
case 10: 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"); syslog(LOG_NOTICE, "Failure, no face model known");
break; break;
// Status 11 means we exceded the maximum retry count // Status 11 means we exceded the maximum retry count
@ -69,13 +75,15 @@ int on_howdy_auth(int code, function<int(int, const char *)> conv_function) {
break; break;
// Status 13 means the image was too dark // Status 13 means the image was too dark
case 13: 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"); syslog(LOG_INFO, "Failure, image too dark");
break; break;
// Otherwise, we can't describe what happened but it wasn't successful // Otherwise, we can't describe what happened but it wasn't successful
default: default:
conv_function(PAM_ERROR_MSG, conv_function(
string(dgettext("pam", "Unknown error:") + to_string(code)).c_str()); PAM_ERROR_MSG,
string(dgettext("pam", "Unknown error:") + to_string(code)).c_str());
syslog(LOG_INFO, "Failure, unknown error %d", code); syslog(LOG_INFO, "Failure, unknown error %d", code);
} }
} }
@ -109,6 +117,17 @@ int send_message(function<int(int, const struct pam_message **,
return conv(1, &msgp, &resp_, nullptr); return conv(1, &msgp, &resp_, nullptr);
} }
bool operator==(const string &l, const Workaround &r) {
switch (r) {
case Workaround::Off:
return (l == "off");
case Workaround::Input:
return (l == "input");
case Workaround::Native:
return (l == "native");
}
}
/** /**
* The main function, runs the identification and authentication * The main function, runs the identification and authentication
* @param pamh The handle to interface directly with PAM * @param pamh The handle to interface directly with PAM
@ -125,6 +144,11 @@ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
// Open the system log so we can write to it // Open the system log so we can write to it
openlog("pam_howdy", 0, LOG_AUTHPRIV); openlog("pam_howdy", 0, LOG_AUTHPRIV);
string workaround = reader.GetString("core", "workaround", "input");
// In this case, we are not asking for any password
if (workaround == Workaround::Off)
auth_tok = false;
// Will contain PAM conversation function // Will contain PAM conversation function
struct pam_conv *conv = nullptr; struct pam_conv *conv = nullptr;
// Will contain the responses from PAM functions // Will contain the responses from PAM functions
@ -162,21 +186,20 @@ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
} }
} }
// Try to detect the laptop lid state and stop if it's closed // Try to detect the laptop lid state and stop if it's closed
if (reader.GetBoolean("core", "ignore_closed_lid", true)) { if (reader.GetBoolean("core", "ignore_closed_lid", true)) {
glob_t glob_result{}; glob_t glob_result{};
// Get any files containing lid state // Get any files containing lid state
int return_value = glob("/proc/acpi/button/lid/*/state", 0, nullptr, &glob_result); int return_value =
glob("/proc/acpi/button/lid/*/state", 0, nullptr, &glob_result);
// TODO: We ignore the result // TODO: We ignore the result
if (return_value != 0) { if (return_value != 0) {
globfree(&glob_result); globfree(&glob_result);
} }
// For each lid status file found for (size_t i = 0; i < glob_result.gl_pathc; i++) {
for (size_t i = 0; i < glob_result.gl_pathc; ++i) {
// Read the file contents // Read the file contents
ifstream file(string(glob_result.gl_pathv[i])); ifstream file(string(glob_result.gl_pathv[i]));
string lid_state; string lid_state;
@ -196,7 +219,10 @@ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
// If enabled, send a notice to the user that facial login is being attempted // If enabled, send a notice to the user that facial login is being attempted
if (reader.GetBoolean("core", "detection_notice", false)) { if (reader.GetBoolean("core", "detection_notice", false)) {
if ((pam_res = conv_function(PAM_TEXT_INFO, dgettext("pam", "Attempting facial authentication"))) != PAM_SUCCESS) { if ((pam_res = conv_function(
PAM_TEXT_INFO,
dgettext("pam", "Attempting facial authentication"))) !=
PAM_SUCCESS) {
syslog(LOG_ERR, "Failed to send detection notice"); syslog(LOG_ERR, "Failed to send detection notice");
} }
} }
@ -215,9 +241,8 @@ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO); posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO);
posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO); posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO);
// Prepare the python command that will run the facial authentication const char *const args[] = {
const char *const args[] = {"/usr/bin/python3", "/lib/security/howdy/compare.py", "/usr/bin/python3", "/lib/security/howdy/compare.py", user_ptr, nullptr};
user_ptr, nullptr};
pid_t child_pid; pid_t child_pid;
// Start the python subprocess // Start the python subprocess
@ -227,10 +252,12 @@ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
return PAM_SYSTEM_ERR; return PAM_SYSTEM_ERR;
} }
std::mutex m; mutex m;
std::condition_variable cv; condition_variable cv;
Type t; Type t;
packaged_task<int()> child_task([&] { // This task wait for the status of the python subprocess (we don't want a
// zombie process).
optional_task<int> child_task(packaged_task<int()>([&] {
int status; int status;
wait(&status); wait(&status);
{ {
@ -239,48 +266,50 @@ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
} }
cv.notify_all(); cv.notify_all();
return status; return status;
}); }));
auto child_future = child_task.get_future();
thread child_thread(move(child_task));
packaged_task<int()> pass_task([&] { optional_task<tuple<int, char *>> pass_task(
char *auth_tok_ptr = nullptr; packaged_task<tuple<int, char *>()>([&] {
int pam_res = pam_get_authtok(pamh, PAM_AUTHTOK, char *auth_tok_ptr = nullptr;
(const char **)&auth_tok_ptr, nullptr); int pam_res = pam_get_authtok(pamh, PAM_AUTHTOK,
{ (const char **)&auth_tok_ptr, nullptr);
unique_lock<mutex> lk(m); {
t = Type::Pam; unique_lock<mutex> lk(m);
} t = Type::Pam;
cv.notify_all(); }
return pam_res; cv.notify_all();
}); return tuple<int, char *>(pam_res, auth_tok_ptr);
auto pass_future = pass_task.get_future(); }));
thread pass_thread;
if (auth_tok) { if (auth_tok) {
pass_thread = thread(move(pass_task)); pass_task.activate();
} }
{ // Wait for the end
if (workaround == Workaround::Native) {
unique_lock<mutex> lk(m); unique_lock<mutex> lk(m);
cv.wait(lk); cv.wait(lk);
} else if (auth_tok) {
pass_task.stop(false);
} }
if (t == Type::Howdy) { if (t == Type::Howdy) {
if (auth_tok) { if (auth_tok) {
// We cancel the thread using pthread, pam_get_authtok seems to be a // We cancel the thread using pthread, pam_get_authtok seems to be a
// cancellation point // cancellation point
auto native_hd = pass_thread.native_handle(); if (child_task.wait(1s) == future_status::timeout) {
pthread_cancel(native_hd); kill(child_pid, SIGTERM);
pass_thread.join(); }
child_task.stop(false);
pass_task.stop(false);
} }
child_thread.join(); int howdy_status = child_task.get();
int howdy_status = child_future.get();
if (howdy_status == 0) { if (howdy_status == 0) {
if (!reader.GetBoolean("core", "no_confirmation", true)) { if (!reader.GetBoolean("core", "no_confirmation", true)) {
// Construct confirmation text from i18n string // Construct confirmation text from i18n string
string confirm_text = dgettext("pam", "Identified face as {}"); 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 // Send confirmation message to user
conv_function(PAM_TEXT_INFO, identify_msg.c_str()); 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); return on_howdy_auth(howdy_status, conv_function);
} }
} else { } else {
kill(child_pid, SIGTERM); if (!(workaround == Workaround::Native)) {
pass_thread.join(); if (child_task.wait(1s) == future_status::timeout) {
auto pam_res = pass_future.get(); 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) if (pam_res != PAM_SUCCESS)
return pam_res; 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; return PAM_IGNORE;
} }
} }

8
src/pam/main.hh Normal file
View file

@ -0,0 +1,8 @@
#ifndef MAIN_H_
#define MAIN_H_
enum class Type { Howdy, Pam };
enum class Workaround { Off, Input, Native };
#endif // MAIN_H_

View file

@ -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 = subproject('inih')
inih_cpp = inih.get_variable('INIReader_dep') inih_cpp = inih.get_variable('INIReader_dep')
libpam = meson.get_compiler('c').find_library('pam') libpam = meson.get_compiler('c').find_library('pam')
boost = dependency('boost', modules: ['filesystem']) boost = dependency('boost')
threads = dependency('threads') threads = dependency('threads')
shared_library('pam_howdy', 'main.cc', dependencies: [boost, libpam, inih_cpp, threads], install: true, install_dir: '/lib/security/') shared_library('pam_howdy', 'main.cc', dependencies: [boost, libpam, inih_cpp, threads], install: true, install_dir: '/lib/security/')

64
src/pam/optional_task.hh Normal file
View file

@ -0,0 +1,64 @@
#ifndef OPTIONAL_TASK_H_
#define OPTIONAL_TASK_H_
#include <cassert>
#include <chrono>
#include <future>
#include <thread>
template <typename T> class optional_task {
std::thread _thread;
std::packaged_task<T()> _task;
std::future<T> _future;
std::atomic<bool> _spawned;
std::atomic<bool> _is_active;
public:
optional_task(std::packaged_task<T()>);
void activate();
std::future_status wait(std::chrono::duration<int>);
T get();
void stop(bool);
~optional_task();
};
template <typename T>
optional_task<T>::optional_task(std::packaged_task<T()> t)
: _task(std::move(t)), _future(_task.get_future()) {}
template <typename T> void optional_task<T>::activate() {
_thread = std::thread(std::move(_task));
_spawned = true;
_is_active = true;
}
template <typename T>
std::future_status optional_task<T>::wait(std::chrono::duration<int> dur) {
return _future.wait_for(dur);
}
template <typename T> T optional_task<T>::get() {
assert(!_is_active && _spawned);
return _future.get();
}
template <typename T> void optional_task<T>::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 <typename T> optional_task<T>::~optional_task<T>() {
if (_is_active && _spawned)
stop(false);
}
#endif // OPTIONAL_TASK_H_