Skip to content

Commit

Permalink
[WIP]: Add native PAM module
Browse files Browse the repository at this point in the history
Signed-off-by: MusiKid <[email protected]>
  • Loading branch information
saidsay-so committed Jan 14, 2021
1 parent fc14425 commit 45a152f
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 1 deletion.
17 changes: 16 additions & 1 deletion src/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import numpy as np
import _thread as thread
from recorders.video_capture import VideoCapture

from evdev import UInput, ecodes as e

def init_detector(lock):
"""Start face detector, encoder and predictor in a new thread"""
Expand Down Expand Up @@ -282,6 +282,21 @@ def print_timing(label, k):
if capture_successful:
make_snapshot("SUCCESSFUL")

# 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
sys.exit(0)

Expand Down
1 change: 1 addition & 0 deletions src/pam/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
subprojects/*/
8 changes: 8 additions & 0 deletions src/pam/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Howdy PAM module

## Build

```sh
meson setup --wipe build -Dinih:with_INIReader=true
meson compile build
```
207 changes: 207 additions & 0 deletions src/pam/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#include <INIReader.h>
#include <errno.h>
#include <glob.h>
#include <sys/syslog.h>
#include <syslog.h>
#include <unistd.h>

#include <chrono>
#include <condition_variable>
#include <cstring>
#include <functional>
#include <future>
#include <iostream>
#include <iterator>
#include <memory>
#include <string>
#include <system_error>
#include <thread>
#include <tuple>
#include <vector>

#include <boost/asio/io_context.hpp>
#include <boost/asio/read.hpp>
#include <boost/process/async.hpp>
#include <boost/process/detail/child_decl.hpp>
#include <boost/process/detail/on_exit.hpp>
#include <boost/process/env.hpp>
#include <boost/process/extend.hpp>
#include <boost/process/io.hpp>
#include <boost/process/search_path.hpp>

#include <security/pam_appl.h>
#include <security/pam_ext.h>
#include <security/pam_modules.h>

using namespace std;
namespace bp = boost::process;

int on_howdy_auth(int code, function<int()> conv_function,
struct pam_message *msg) {

switch (code) {
case 10:
msg->msg = "Image is too dark";
msg->msg_style = PAM_ERROR_MSG;
conv_function();
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:
msg->msg = string("Unknown error:" + to_string(code)).c_str();
msg->msg_style = PAM_ERROR_MSG;
conv_function();
syslog(LOG_INFO, "Failure, unknown error %d", code);
}

return PAM_AUTH_ERR;
}

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;

// No need to free this, it's allocated on the stack
struct pam_message msg = {};
struct pam_message *msgp = &msg;

struct pam_response res_ = {};
struct pam_response *resp_ = &res_;

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(
(*conv->conv), 1, (const struct pam_message **)&msgp, &resp_, nullptr);

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)) {
msg.msg_style = PAM_TEXT_INFO;
msg.msg = "Attempting facial authentication";

if ((pam_res = conv_function()) != 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);

boost::asio::io_context ios;
bp::async_pipe pipe(ios);

array<char, 1> _buf;
atomic<bool> is_howdy_enter(false);
boost::asio::async_read(pipe, boost::asio::buffer(_buf, 1),
[&](auto ec, auto size) {
if (size >= 1)
is_howdy_enter = true;
});
pipe.async_close();

int howdy_status;
bp::child howdy(
bp::search_path("python"), "/lib/security/howdy/compare.py", user, ios,
bp::std_err > bp::null, bp::std_out > bp::null,
bp::env["PIPE_FD"] = to_string(pipe.native_source()),
bp::on_exit = [&](int exit, const error_code &_ec) {
howdy_status = exit;
});

auto _ = async(launch::async, [&] { ios.run(); });

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 (is_howdy_enter) {
if (!howdy.wait_for(1s))
howdy.terminate();
} else {
howdy.terminate();
}

if (is_howdy_enter && howdy_status == 0) {

if (!reader.GetBoolean("section", "no_confirmation", true)) {
string identify_msg("Identified face as ");
identify_msg.append(user);
msg.msg_style = PAM_TEXT_INFO;
msg.msg = identify_msg.c_str();
conv_function();
}

syslog(LOG_INFO, "Login approved");
return PAM_SUCCESS;
} else if (get<int>(pass) == PAM_SUCCESS && get<char *>(pass) != nullptr &&
!string(get<char *>(pass)).empty()) {
return PAM_IGNORE;
} else {
return on_howdy_auth(howdy_status, conv_function, msgp);
}
}
9 changes: 9 additions & 0 deletions src/pam/meson.build
Original file line number Diff line number Diff line change
@@ -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', soversion: '', dependencies: [boost, libpam, inih_cpp, threads], install: true, install_dir: '/lib/security/')
3 changes: 3 additions & 0 deletions src/pam/subprojects/inih.wrap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[wrap-git]
url = https://github.com/benhoyt/inih.git
revision = r52

0 comments on commit 45a152f

Please sign in to comment.