feat: send enter input from the module
The Enter input is now handled directly in the module, rather than in the compare process. The module will try to send it in a loop, while the password task is active and until a fixed limit. If it didn't succeed, it will wait for the Enter key to be manually input or an error.
This commit is contained in:
parent
b9d678a08e
commit
6a395067ad
5 changed files with 143 additions and 55 deletions
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
## Prepare
|
||||
|
||||
This module depends on `INIReader`.
|
||||
It can be installed with these packages:
|
||||
This module depends on `INIReader` and `libevdev`.
|
||||
They can be installed with these packages:
|
||||
|
||||
```
|
||||
Arch Linux - libinih
|
||||
Debian - libinih-dev
|
||||
Fedora - inih-devel
|
||||
OpenSUSE - inih
|
||||
Arch Linux - libinih libevdev
|
||||
Debian - libinih-dev libevdev-dev
|
||||
Fedora - inih-devel libevdev-devel
|
||||
OpenSUSE - inih libevdev-devel
|
||||
```
|
||||
|
||||
If your distribution doesn't provide `INIReader`,
|
||||
it will be automatically pulled from the latest git version.
|
||||
it will be automatically pulled from git at the subproject's pinned version.
|
||||
|
||||
## Build
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ meson compile -C build
|
|||
meson install -C build
|
||||
```
|
||||
|
||||
Change PAM config line to:
|
||||
Add the following line to your PAM configuration (/etc/pam.d/your-service):
|
||||
|
||||
``` pam
|
||||
auth sufficient pam_howdy.so
|
||||
|
|
|
|||
49
src/pam/enter_device.cc
Normal file
49
src/pam/enter_device.cc
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#include "enter_device.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
EnterDevice::EnterDevice()
|
||||
: raw_device(libevdev_new(), &libevdev_free),
|
||||
raw_uinput_device(nullptr, &libevdev_uinput_destroy) {
|
||||
auto *dev_ptr = raw_device.get();
|
||||
|
||||
libevdev_set_name(dev_ptr, "enter device");
|
||||
libevdev_enable_event_type(dev_ptr, EV_KEY);
|
||||
libevdev_enable_event_code(dev_ptr, EV_KEY, KEY_ENTER, nullptr);
|
||||
|
||||
int err;
|
||||
struct libevdev_uinput *uinput_dev_ptr;
|
||||
if ((err = libevdev_uinput_create_from_device(
|
||||
dev_ptr, LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput_dev_ptr)) != 0) {
|
||||
throw std::runtime_error(std::string("Failed to create device: ") +
|
||||
strerror(-err));
|
||||
}
|
||||
|
||||
|
||||
raw_uinput_device.reset(uinput_dev_ptr);
|
||||
};
|
||||
|
||||
void EnterDevice::send_enter_press() const {
|
||||
auto *uinput_dev_ptr = raw_uinput_device.get();
|
||||
|
||||
int err;
|
||||
if ((err = libevdev_uinput_write_event(uinput_dev_ptr, EV_KEY, KEY_ENTER,
|
||||
1)) != 0) {
|
||||
throw std::runtime_error(std::string("Failed to write event: ") +
|
||||
strerror(-err));
|
||||
}
|
||||
|
||||
if ((err = libevdev_uinput_write_event(uinput_dev_ptr, EV_KEY, KEY_ENTER,
|
||||
0)) != 0) {
|
||||
throw std::runtime_error(std::string("Failed to write event: ") +
|
||||
strerror(-err));
|
||||
}
|
||||
|
||||
if ((err = libevdev_uinput_write_event(uinput_dev_ptr, EV_SYN, SYN_REPORT,
|
||||
0)) != 0) {
|
||||
throw std::runtime_error(std::string("Failed to write event: ") +
|
||||
strerror(-err));
|
||||
};
|
||||
}
|
||||
19
src/pam/enter_device.hh
Normal file
19
src/pam/enter_device.hh
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef __ENTER_DEVICE_H_
|
||||
#define __ENTER_DEVICE_H_
|
||||
|
||||
#include <libevdev/libevdev-uinput.h>
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <memory>
|
||||
|
||||
class EnterDevice {
|
||||
std::unique_ptr<struct libevdev, decltype(&libevdev_free)> raw_device;
|
||||
std::unique_ptr<struct libevdev_uinput, decltype(&libevdev_uinput_destroy)>
|
||||
raw_uinput_device;
|
||||
|
||||
public:
|
||||
EnterDevice();
|
||||
void send_enter_press() const;
|
||||
~EnterDevice() = default;
|
||||
};
|
||||
|
||||
#endif // __ENTER_DEVICE_H
|
||||
109
src/pam/main.cc
109
src/pam/main.cc
|
|
@ -7,6 +7,7 @@
|
|||
#include <libintl.h>
|
||||
#include <pthread.h>
|
||||
#include <spawn.h>
|
||||
#include <stdexcept>
|
||||
#include <sys/signalfd.h>
|
||||
#include <sys/syslog.h>
|
||||
#include <sys/types.h>
|
||||
|
|
@ -37,6 +38,7 @@
|
|||
#include <security/pam_ext.h>
|
||||
#include <security/pam_modules.h>
|
||||
|
||||
#include "enter_device.hh"
|
||||
#include "main.hh"
|
||||
#include "optional_task.hh"
|
||||
|
||||
|
|
@ -47,7 +49,7 @@
|
|||
#endif
|
||||
|
||||
const auto DEFAULT_TIMEOUT =
|
||||
std::chrono::duration<int, std::chrono::milliseconds::period>(2500);
|
||||
std::chrono::duration<int, std::chrono::milliseconds::period>(100);
|
||||
const auto MAX_RETRIES = 5;
|
||||
const auto PYTHON_EXECUTABLE = "python3";
|
||||
const auto COMPARE_PROCESS_PATH = "/lib/security/howdy/compare.py";
|
||||
|
|
@ -116,7 +118,7 @@ auto howdy_error(int status,
|
|||
* @return Returns the conversation function return code
|
||||
*/
|
||||
auto howdy_status(char *username, int status, const INIReader &reader,
|
||||
const std::function<int(int, const char *)> &conv_function)
|
||||
const std::function<int(int, const char *)> &conv_function)
|
||||
-> int {
|
||||
if (status != EXIT_SUCCESS) {
|
||||
return howdy_error(status, conv_function);
|
||||
|
|
@ -218,19 +220,19 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
|||
syslog(LOG_ERR, "Underlying error: %s (%d)", strerror(errno), errno);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < glob_result.gl_pathc; i++) {
|
||||
std::ifstream file(std::string(glob_result.gl_pathv[i]));
|
||||
std::string lid_state;
|
||||
std::getline(file, lid_state, static_cast<char>(file.eof()));
|
||||
for (size_t i = 0; i < glob_result.gl_pathc; i++) {
|
||||
std::ifstream file(std::string(glob_result.gl_pathv[i]));
|
||||
std::string lid_state;
|
||||
std::getline(file, lid_state, static_cast<char>(file.eof()));
|
||||
|
||||
if (lid_state.find("closed") != std::string::npos) {
|
||||
globfree(&glob_result);
|
||||
if (lid_state.find("closed") != std::string::npos) {
|
||||
globfree(&glob_result);
|
||||
|
||||
syslog(LOG_INFO, "Skipped authentication, closed lid detected");
|
||||
return PAM_AUTHINFO_UNAVAIL;
|
||||
syslog(LOG_INFO, "Skipped authentication, closed lid detected");
|
||||
return PAM_AUTHINFO_UNAVAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
globfree(&glob_result);
|
||||
}
|
||||
|
||||
|
|
@ -321,52 +323,67 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
|||
cv.wait(lk, [&] { return confirmation_type != ConfirmationType::Unset; });
|
||||
}
|
||||
|
||||
if (confirmation_type == ConfirmationType::Howdy) {
|
||||
// The password has been entered or an error has occurred
|
||||
if (confirmation_type == ConfirmationType::Pam) {
|
||||
// We kill the child because we don't need its result
|
||||
kill(child_pid, SIGTERM);
|
||||
child_task.stop(false);
|
||||
|
||||
// If the workaround is native
|
||||
if (auth_tok) {
|
||||
// UNSAFE: We cancel the thread using pthread, pam_get_authtok seems to be
|
||||
// a cancellation point
|
||||
if (pass_task.is_active()) {
|
||||
pass_task.stop(true);
|
||||
}
|
||||
// We just wait for the thread to stop since it's this one which sent us the
|
||||
// confirmation type
|
||||
pass_task.stop(false);
|
||||
|
||||
char *password = nullptr;
|
||||
std::tie(pam_res, password) = pass_task.get();
|
||||
|
||||
if (pam_res != PAM_SUCCESS) {
|
||||
return pam_res;
|
||||
}
|
||||
int howdy_status = child_task.get();
|
||||
|
||||
return howdy_msg(username, howdy_status, reader, conv_function);
|
||||
// The password has been entered, we are passing it to PAM stack
|
||||
return PAM_IGNORE;
|
||||
}
|
||||
|
||||
// The password has been entered
|
||||
|
||||
// We need to be sure that we're not going to block forever if the
|
||||
// child has a problem
|
||||
if (child_task.wait(DEFAULT_TIMEOUT) == std::future_status::timeout) {
|
||||
kill(child_pid, SIGTERM);
|
||||
}
|
||||
// The compare process has finished its execution
|
||||
child_task.stop(false);
|
||||
|
||||
// We just wait for the thread to stop since it's this one which sent us the
|
||||
// confirmation type
|
||||
if (workaround == Workaround::Input && auth_tok) {
|
||||
// We want to stop the password prompt, either by canceling the thread when
|
||||
// workaround is set to "native", or by emulating "Enter" input with
|
||||
// "input"
|
||||
|
||||
// UNSAFE: We cancel the thread using pthread, pam_get_authtok seems to be
|
||||
// a cancellation point
|
||||
if (workaround == Workaround::Native && pass_task.is_active()) {
|
||||
pass_task.stop(true);
|
||||
} else if (workaround == Workaround::Input) {
|
||||
if (geteuid() != 0) {
|
||||
syslog(LOG_WARNING, "Insufficient permission to create the fake device");
|
||||
conv_function(PAM_ERROR_MSG, S("Insufficient permission to send Enter "
|
||||
"input, waiting for Enter input..."));
|
||||
} else {
|
||||
try {
|
||||
EnterDevice enter_device;
|
||||
for (int retries = 0;
|
||||
retries < MAX_RETRIES &&
|
||||
pass_task.wait(DEFAULT_TIMEOUT) == std::future_status::timeout;
|
||||
retries++) {
|
||||
enter_device.send_enter_press();
|
||||
}
|
||||
} catch (std::runtime_error &err) {
|
||||
syslog(LOG_WARNING, "Failed to send enter input: %s", err.what());
|
||||
conv_function(
|
||||
PAM_ERROR_MSG,
|
||||
S("Failed to send enter input, waiting for Enter input..."));
|
||||
}
|
||||
}
|
||||
|
||||
// We stop the thread (will block until the enter key is pressed, if the
|
||||
// input wasn't focused or if the uinput device failed to send keypress)
|
||||
pass_task.stop(false);
|
||||
}
|
||||
int status = child_task.get();
|
||||
|
||||
char *password = nullptr;
|
||||
std::tie(pam_res, password) = pass_task.get();
|
||||
|
||||
if (pam_res != PAM_SUCCESS) {
|
||||
return pam_res;
|
||||
}
|
||||
|
||||
int howdy_status = child_task.get();
|
||||
// If python process (or user) sent Enter key
|
||||
if (strlen(password) == 0) {
|
||||
return howdy_msg(username, howdy_status, reader, conv_function);
|
||||
}
|
||||
|
||||
// The password has been entered, we are passing it to PAM stack
|
||||
return PAM_IGNORE;
|
||||
return howdy_status(username, status, reader, conv_function);
|
||||
}
|
||||
|
||||
// Called by PAM when a user needs to be authenticated, for example by running
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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')
|
||||
threads = dependency('threads')
|
||||
|
||||
|
|
@ -10,10 +11,12 @@ subdir('po')
|
|||
shared_library(
|
||||
'pam_howdy',
|
||||
'main.cc',
|
||||
'enter_device.cc',
|
||||
dependencies: [
|
||||
libpam,
|
||||
inih_cpp,
|
||||
threads
|
||||
threads,
|
||||
libevdev,
|
||||
],
|
||||
install: true,
|
||||
install_dir: '/lib/security',
|
||||
|
|
|
|||
Loading…
Reference in a new issue