feat: add workarounds
This commit is contained in:
parent
7ee0f361fb
commit
d078f81baa
4 changed files with 174 additions and 46 deletions
144
src/pam/main.cc
144
src/pam/main.cc
|
|
@ -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
8
src/pam/main.hh
Normal 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_
|
||||||
|
|
@ -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
64
src/pam/optional_task.hh
Normal 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_
|
||||||
Loading…
Reference in a new issue