mirror of
https://github.com/boltgolt/howdy.git
synced 2024-09-19 09:51:19 +02:00
Split files into separate howdy-gtk and howdy packages
This commit is contained in:
parent
f463434309
commit
692d674619
69 changed files with 531 additions and 365 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -101,10 +101,10 @@ ENV/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
|
||||||
# generated models
|
# generated models
|
||||||
/src/models
|
/howdy/src/models
|
||||||
|
|
||||||
# snapshots
|
# snapshots
|
||||||
/src/snapshots
|
/howdy/src/snapshots
|
||||||
|
|
||||||
# build files
|
# build files
|
||||||
debian/howdy.substvars
|
debian/howdy.substvars
|
||||||
|
|
39
.travis.yml
39
.travis.yml
|
@ -1,39 +0,0 @@
|
||||||
sudo: required
|
|
||||||
dist: xenial
|
|
||||||
language: python
|
|
||||||
python:
|
|
||||||
- "3.4"
|
|
||||||
- "3.6"
|
|
||||||
- "3.7"
|
|
||||||
- "3.8-dev"
|
|
||||||
|
|
||||||
script:
|
|
||||||
# Build the binary (.deb)
|
|
||||||
- debuild -i -us -uc -b
|
|
||||||
# Install the binary, running the debian scripts in the process
|
|
||||||
- sudo apt install ../*.deb -y
|
|
||||||
|
|
||||||
# Go through function tests
|
|
||||||
- ./tests/importing.sh
|
|
||||||
- ./tests/passthrough.sh
|
|
||||||
# Skip PAM integration tests for now because of broken pamtester
|
|
||||||
# - ./tests/pam.sh
|
|
||||||
- ./tests/compare.sh
|
|
||||||
|
|
||||||
# Remove howdy from the installation
|
|
||||||
- sudo apt purge howdy -y
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
on_success: never
|
|
||||||
on_failure: always
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
packages:
|
|
||||||
- dh-make
|
|
||||||
- ack-grep
|
|
||||||
- devscripts
|
|
||||||
- fakeroot
|
|
||||||
- pamtester
|
|
|
@ -1,16 +0,0 @@
|
||||||
pkgbase = pam-python
|
|
||||||
pkgdesc = Python for PAM
|
|
||||||
pkgver = 1.0.8
|
|
||||||
pkgrel = 1
|
|
||||||
url = https://github.com/boltgolt/howdy
|
|
||||||
arch = x86_64
|
|
||||||
license = MIT
|
|
||||||
makedepends = python-sphinx
|
|
||||||
makedepends = cmake
|
|
||||||
depends = pam
|
|
||||||
depends = python2
|
|
||||||
source = https://downloads.sourceforge.net/project/pam-python/pam-python-1.0.8-1/pam-python-1.0.8.tar.gz
|
|
||||||
sha256sums = fc69d7717db0509111500a81053487fa7684e1be3b7d0ae2b51970b6fdc918f6
|
|
||||||
|
|
||||||
pkgname = pam-python
|
|
||||||
|
|
7
archlinux/pam-python/.gitignore
vendored
7
archlinux/pam-python/.gitignore
vendored
|
@ -1,7 +0,0 @@
|
||||||
pkg
|
|
||||||
src
|
|
||||||
*.tar.gz
|
|
||||||
*.zip
|
|
||||||
*.tar.xz
|
|
||||||
*.patch
|
|
||||||
*.dat.bz2
|
|
|
@ -1,41 +0,0 @@
|
||||||
# Maintainer: boltgolt <boltgolt@gmail.com>
|
|
||||||
# Maintainer: Kelley McChesney <kelley@kelleymcchesney.us>
|
|
||||||
pkgname=pam-python
|
|
||||||
pkgver=1.0.8
|
|
||||||
pkgrel=1
|
|
||||||
pkgdesc="Python for PAM"
|
|
||||||
arch=('x86_64')
|
|
||||||
url="https://github.com/boltgolt/howdy"
|
|
||||||
license=('MIT')
|
|
||||||
depends=(
|
|
||||||
'pam'
|
|
||||||
'python2'
|
|
||||||
)
|
|
||||||
makedepends=(
|
|
||||||
'python-sphinx'
|
|
||||||
'cmake'
|
|
||||||
)
|
|
||||||
source=(
|
|
||||||
"https://downloads.sourceforge.net/project/${pkgname}/pam-python-${pkgver}-1/${pkgname}-${pkgver}.tar.gz"
|
|
||||||
)
|
|
||||||
sha256sums=('fc69d7717db0509111500a81053487fa7684e1be3b7d0ae2b51970b6fdc918f6')
|
|
||||||
|
|
||||||
prepare() {
|
|
||||||
# Preparing pam-python to be installed
|
|
||||||
cd "$srcdir/$pkgname-$pkgver"
|
|
||||||
sed -i'' 's|LIBDIR ?= /lib/security|LIBDIR ?= /usr/lib/security|g' src/Makefile
|
|
||||||
sed -n '/^License/,/^--$/p' README.txt | grep -v -e '^License' -e '^-\+' > $srcdir/LICENSE
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
# Building pam-python
|
|
||||||
cd "$srcdir/$pkgname-$pkgver"
|
|
||||||
PREFIX=/usr make
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
# Installing pam-python
|
|
||||||
cd "$srcdir/$pkgname-$pkgver"
|
|
||||||
PREFIX=/usr make DESTDIR="$pkgdir/" install
|
|
||||||
install -Dm644 "$srcdir/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
%global with_snapshot 0
|
|
||||||
%global date 20181109.
|
|
||||||
%global commit b4ecafe61c83a4aaab56a52a713296143c87b576
|
|
||||||
%global shortcommit %(c=%{commit}; echo ${c:0:7})
|
|
||||||
%global debug_package %{nil}
|
|
||||||
|
|
||||||
Name: howdy
|
|
||||||
Version: 2.5.1
|
|
||||||
%if %{with_snapshot}
|
|
||||||
Release: 0.1.git.%{date}%{shortcommit}%{?dist}
|
|
||||||
%else
|
|
||||||
Release: 4%{?dist}
|
|
||||||
%endif
|
|
||||||
Summary: Windows Hello™ style authentication for Linux
|
|
||||||
|
|
||||||
|
|
||||||
License: MIT
|
|
||||||
URL: https://github.com/boltgolt/%{name}
|
|
||||||
%if %{with_snapshot}
|
|
||||||
Source0: https://github.com/boltgolt/%{name}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz
|
|
||||||
%else
|
|
||||||
Source0: https://github.com/boltgolt/%{name}/archive/v%{version}/%{name}-%{version}.tar.gz
|
|
||||||
%endif
|
|
||||||
Source1: com.github.boltgolt.howdy.policy
|
|
||||||
|
|
||||||
BuildRequires: polkit-devel
|
|
||||||
%if 0%{?fedora}
|
|
||||||
# We need python3-devel for pathfix.py
|
|
||||||
BuildRequires: python3-devel
|
|
||||||
Requires: python3dist(dlib) >= 6.0
|
|
||||||
Requires: python3-opencv
|
|
||||||
Requires: pam_python
|
|
||||||
%endif
|
|
||||||
|
|
||||||
|
|
||||||
%description
|
|
||||||
Windows Hello™ style authentication for Linux. Use your built-in IR emitters and camera in combination with face recognition to prove who you are.
|
|
||||||
|
|
||||||
%prep
|
|
||||||
%autosetup
|
|
||||||
pathfix.py -i %{__python3} .
|
|
||||||
|
|
||||||
%build
|
|
||||||
## nothing to build
|
|
||||||
|
|
||||||
%install
|
|
||||||
mkdir -p %{buildroot}%{_libdir}/security/%{name}
|
|
||||||
# Remove backup file
|
|
||||||
rm -fr src/*~
|
|
||||||
cp -pr src/* %{buildroot}%{_libdir}/security/%{name}
|
|
||||||
|
|
||||||
# Install facial recognition, may look at better alternative
|
|
||||||
# for offline user
|
|
||||||
sh %{buildroot}%{_libdir}/security/%{name}/dlib-data/install.sh
|
|
||||||
mv *.dat %{buildroot}%{_libdir}/security/%{name}/dlib-data
|
|
||||||
rm -fr %{buildroot}%{_libdir}/security/%{name}/dlib-data/{Readme.md,install.sh,.gitignore}
|
|
||||||
|
|
||||||
# Add polkit rules
|
|
||||||
mkdir -p %{buildroot}%{_datadir}/polkit-1/actions
|
|
||||||
install -Dm 0644 %{SOURCE1} %{buildroot}%{_datadir}/polkit-1/actions/
|
|
||||||
|
|
||||||
#Add bash completion
|
|
||||||
mkdir -p %{buildroot}%{_datadir}/bash-completion/completions
|
|
||||||
install -Dm 644 autocomplete/%{name} %{buildroot}%{_datadir}/bash-completion/completions
|
|
||||||
|
|
||||||
# Create an executable
|
|
||||||
mkdir -p %{buildroot}%{_bindir}
|
|
||||||
chmod +x %{buildroot}%{_libdir}/security/%{name}/cli.py
|
|
||||||
ln -s %{_libdir}/security/%{name}/cli.py %{buildroot}%{_bindir}/%{name}
|
|
||||||
|
|
||||||
|
|
||||||
%files
|
|
||||||
%license LICENSE
|
|
||||||
%doc README.md
|
|
||||||
%{_bindir}/%{name}
|
|
||||||
%{_datadir}/bash-completion/completions/%{name}
|
|
||||||
%{_datadir}/polkit-1/actions/
|
|
||||||
%{_libdir}/security/%{name}
|
|
||||||
%config(noreplace) %{_libdir}/security/%{name}/config.ini
|
|
||||||
|
|
||||||
%changelog
|
|
||||||
* Sun Apr 07 2019 Luya Tshimbalanga <luya@fedoraproject.org> - 2.5.1-3
|
|
||||||
- Add polkit policy
|
|
||||||
|
|
||||||
* Sun Apr 07 2019 Luya Tshimbalanga <luya@fedoraproject.org> - 2.5.1-2
|
|
||||||
- Install facial recognition data
|
|
||||||
|
|
||||||
* Tue Apr 02 2019 Luya Tshimbalanga <luya@fedoraproject.org> - 2.5.1-1
|
|
||||||
- Update to 2.5.1
|
|
||||||
|
|
||||||
* Sat Mar 16 2019 Luya Tshimbalanga <luya@fedoraproject.org> - 2.5.0-3
|
|
||||||
- Require python-v4l2
|
|
||||||
|
|
||||||
* Wed Jan 23 2019 Luya Tshimbalanga <luya@fedoraproject.org> - 2.5.0-2
|
|
||||||
- Fix pam configuration
|
|
||||||
|
|
||||||
* Sun Jan 06 2019 Luya Tshimbalanga <luya@fedoraproject.org> - 2.5.0-1
|
|
||||||
- Update to 2.5.0
|
|
||||||
|
|
||||||
* Thu Nov 29 2018 Luya Tshimbalanga <luya@fedoraproject.org> - 2.4.0-3
|
|
||||||
- Add conditional statement for RHEL/Centos 7.x based on williamwlk spec
|
|
||||||
|
|
||||||
* Thu Nov 29 2018 Luya Tshimbalanga <luya@fedoraproject.org> - 2.4.0-3
|
|
||||||
- Include bash completion
|
|
||||||
|
|
||||||
* Mon Nov 26 2018 Luya Tshimbalanga <luya@fedoraproject.org> - 2.4.0-2
|
|
||||||
- Switch to new requirement method from Fedora Python guideline
|
|
||||||
|
|
||||||
* Mon Nov 26 2018 Luya Tshimbalanga <luya@fedoraproject.org> - 2.4.0-1
|
|
||||||
- Update to 2.4.0
|
|
||||||
|
|
||||||
* Thu Nov 1 2018 Luya Tshimbalanga <luya@fedoraproject.org> - 2.3.1-1
|
|
||||||
- Initial package
|
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
1
howdy/src/pam/.gitignore
vendored
Normal file
1
howdy/src/pam/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
subprojects/*/
|
20
howdy/src/pam/README.md
Normal file
20
howdy/src/pam/README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Howdy PAM module
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
meson setup build -Dinih:with_INIReader=true
|
||||||
|
meson compile -C build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mv build/libpam_howdy.so /lib/security/pam_howdy.so
|
||||||
|
```
|
||||||
|
|
||||||
|
Change PAM config line to:
|
||||||
|
|
||||||
|
```pam
|
||||||
|
auth sufficient pam_howdy.so
|
||||||
|
```
|
394
howdy/src/pam/main.cc
Normal file
394
howdy/src/pam/main.cc
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
#include <cerrno>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <glob.h>
|
||||||
|
#include <ostream>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <spawn.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <sys/signalfd.h>
|
||||||
|
#include <sys/syslog.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <system_error>
|
||||||
|
#include <thread>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <INIReader.h>
|
||||||
|
|
||||||
|
#include <boost/locale.hpp>
|
||||||
|
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <security/pam_ext.h>
|
||||||
|
#include <security/pam_modules.h>
|
||||||
|
|
||||||
|
#include "main.hh"
|
||||||
|
#include "optional_task.hh"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace boost::locale;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspect the status code returned by the compare process
|
||||||
|
* @param code The status code
|
||||||
|
* @param conv_function The PAM conversation function
|
||||||
|
* @return A PAM return code
|
||||||
|
*/
|
||||||
|
int on_howdy_auth(int code, function<int(int, const char *)> conv_function) {
|
||||||
|
// If the process has exited
|
||||||
|
if (!WIFEXITED(code)) {
|
||||||
|
// Get the status code returned
|
||||||
|
code = WEXITSTATUS(code);
|
||||||
|
|
||||||
|
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"));
|
||||||
|
syslog(LOG_NOTICE, "Failure, no face model known");
|
||||||
|
break;
|
||||||
|
// Status 11 means we exceded the maximum retry count
|
||||||
|
case 11:
|
||||||
|
syslog(LOG_INFO, "Failure, timeout reached");
|
||||||
|
break;
|
||||||
|
// Status 12 means we aborted
|
||||||
|
case 12:
|
||||||
|
syslog(LOG_INFO, "Failure, general abort");
|
||||||
|
break;
|
||||||
|
// Status 13 means the image was too dark
|
||||||
|
case 13:
|
||||||
|
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());
|
||||||
|
syslog(LOG_INFO, "Failure, unknown error %d", code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As this function is only called for error status codes, signal an error to
|
||||||
|
// PAM
|
||||||
|
return PAM_AUTH_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and send a message to PAM
|
||||||
|
* @param conv PAM conversation function
|
||||||
|
* @param type Type of PAM message
|
||||||
|
* @param message String to show the user
|
||||||
|
* @return Returns the conversation function return code
|
||||||
|
*/
|
||||||
|
int send_message(function<int(int, const struct pam_message **,
|
||||||
|
struct pam_response **, void *)>
|
||||||
|
conv,
|
||||||
|
int type, const char *message) {
|
||||||
|
// No need to free this, it's allocated on the stack
|
||||||
|
const struct pam_message msg = {.msg_style = type, .msg = message};
|
||||||
|
const struct pam_message *msgp = &msg;
|
||||||
|
|
||||||
|
struct pam_response res_ = {};
|
||||||
|
struct pam_response *resp_ = &res_;
|
||||||
|
|
||||||
|
// Call the conversation function with the constructed arguments
|
||||||
|
return conv(1, &msgp, &resp_, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main function, runs the identification and authentication
|
||||||
|
* @param pamh The handle to interface directly with PAM
|
||||||
|
* @param flags Flags passed on to us by PAM, XORed
|
||||||
|
* @param argc Amount of rules in the PAM config (disregared)
|
||||||
|
* @param argv Options defined in the PAM config
|
||||||
|
* @param auth_tok True if we should ask for a password too
|
||||||
|
* @return Returns a PAM return code
|
||||||
|
*/
|
||||||
|
int identify(pam_handle_t *pamh, int flags, int argc, const char **argv,
|
||||||
|
bool auth_tok) {
|
||||||
|
INIReader reader("/lib/security/howdy/config.ini");
|
||||||
|
// Open the system log so we can write to it
|
||||||
|
openlog("pam_howdy", 0, LOG_AUTHPRIV);
|
||||||
|
|
||||||
|
string workaround = reader.GetString("core", "workaround", "input");
|
||||||
|
|
||||||
|
// In this case, we are not asking for the password
|
||||||
|
if (workaround == Workaround::Off && auth_tok)
|
||||||
|
auth_tok = false;
|
||||||
|
|
||||||
|
// Will contain PAM conversation function
|
||||||
|
struct pam_conv *conv = nullptr;
|
||||||
|
// Will contain the responses from PAM functions
|
||||||
|
int pam_res = PAM_IGNORE;
|
||||||
|
|
||||||
|
// Try to get the conversation function and error out if we can't
|
||||||
|
if ((pam_res = pam_get_item(pamh, PAM_CONV, (const void **)&conv)) !=
|
||||||
|
PAM_SUCCESS) {
|
||||||
|
syslog(LOG_ERR, "Failed to acquire conversation");
|
||||||
|
return pam_res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the PAM conversation function in our own, easier function
|
||||||
|
auto conv_function =
|
||||||
|
bind(send_message, conv->conv, placeholders::_1, placeholders::_2);
|
||||||
|
|
||||||
|
// Error out if we could not ready the config file
|
||||||
|
if (reader.ParseError() < 0) {
|
||||||
|
syslog(LOG_ERR, "Failed to parse the configuration file");
|
||||||
|
return PAM_SYSTEM_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop executing if Howdy has been disabled in the config
|
||||||
|
if (reader.GetBoolean("core", "disabled", false)) {
|
||||||
|
syslog(LOG_INFO, "Skipped authentication, Howdy is disabled");
|
||||||
|
return PAM_AUTHINFO_UNAVAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop if we're in a remote shell and configured to exit
|
||||||
|
if (reader.GetBoolean("core", "ignore_ssh", true)) {
|
||||||
|
if (getenv("SSH_CONNECTION") != nullptr ||
|
||||||
|
getenv("SSH_CLIENT") != nullptr || getenv("SSHD_OPTS") != nullptr) {
|
||||||
|
syslog(LOG_INFO, "Skipped authentication, SSH session detected");
|
||||||
|
return PAM_AUTHINFO_UNAVAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to detect the laptop lid state and stop if it's closed
|
||||||
|
if (reader.GetBoolean("core", "ignore_closed_lid", true)) {
|
||||||
|
glob_t glob_result{};
|
||||||
|
|
||||||
|
// Get any files containing lid state
|
||||||
|
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);
|
||||||
|
|
||||||
|
syslog(LOG_INFO, "Skipped authentication, closed lid detected");
|
||||||
|
return PAM_AUTHINFO_UNAVAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globfree(&glob_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If enabled, send a notice to the user that facial login is being attempted
|
||||||
|
if (reader.GetBoolean("core", "detection_notice", false)) {
|
||||||
|
if ((pam_res = conv_function(
|
||||||
|
PAM_TEXT_INFO,
|
||||||
|
dgettext("pam", "Attempting facial authentication"))) !=
|
||||||
|
PAM_SUCCESS) {
|
||||||
|
syslog(LOG_ERR, "Failed to send detection notice");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the username from PAM, needed to match correct face model
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
posix_spawn_file_actions_t file_actions;
|
||||||
|
posix_spawn_file_actions_init(&file_actions);
|
||||||
|
// We close standard descriptors for the child
|
||||||
|
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, STDIN_FILENO);
|
||||||
|
|
||||||
|
const char *const args[] = {
|
||||||
|
"/usr/bin/python3", "/lib/security/howdy/compare.py", user_ptr, nullptr};
|
||||||
|
pid_t child_pid;
|
||||||
|
|
||||||
|
// Start the python subprocess
|
||||||
|
if (posix_spawnp(&child_pid, "/usr/bin/python3", &file_actions, nullptr,
|
||||||
|
(char *const *)args, nullptr) < 0) {
|
||||||
|
syslog(LOG_ERR, "Can't spawn the howdy process: %s", strerror(errno));
|
||||||
|
return PAM_SYSTEM_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex m;
|
||||||
|
condition_variable cv;
|
||||||
|
Type confirmation_type;
|
||||||
|
// TODO: Find a clean way to do this
|
||||||
|
Type final_type;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
wait(&status);
|
||||||
|
{
|
||||||
|
unique_lock<mutex> lk(m);
|
||||||
|
confirmation_type = Type::Howdy;
|
||||||
|
}
|
||||||
|
cv.notify_all();
|
||||||
|
return status;
|
||||||
|
}));
|
||||||
|
child_task.activate();
|
||||||
|
|
||||||
|
// This task waits for the password input (if the workaround wants it)
|
||||||
|
optional_task<tuple<int, char *>> pass_task(
|
||||||
|
packaged_task<tuple<int, char *>()>([&] {
|
||||||
|
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);
|
||||||
|
confirmation_type = Type::Pam;
|
||||||
|
}
|
||||||
|
cv.notify_all();
|
||||||
|
return tuple<int, char *>(pam_res, auth_tok_ptr);
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (auth_tok) {
|
||||||
|
pass_task.activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the end either of the child or the password input
|
||||||
|
{
|
||||||
|
unique_lock<mutex> lk(m);
|
||||||
|
cv.wait(lk);
|
||||||
|
final_type = confirmation_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (final_type == Type::Howdy) {
|
||||||
|
// We need to be sure that we're not going to block forever if the
|
||||||
|
// child has a problem
|
||||||
|
if (child_task.wait(3s) == future_status::timeout) {
|
||||||
|
kill(child_pid, SIGTERM);
|
||||||
|
}
|
||||||
|
child_task.stop(false);
|
||||||
|
|
||||||
|
// If the workaround is native
|
||||||
|
if (auth_tok) {
|
||||||
|
// We cancel the thread using pthread, pam_get_authtok seems to be a
|
||||||
|
// cancellation point
|
||||||
|
if (pass_task.is_active()) {
|
||||||
|
pass_task.stop(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int howdy_status = child_task.get();
|
||||||
|
|
||||||
|
// If exited successfully
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The branch with Howdy confirmation type returns early, so we don't need an
|
||||||
|
// else statement
|
||||||
|
|
||||||
|
// Again, we need to be sure that we're not going to block forever if the
|
||||||
|
// child has a problem
|
||||||
|
if (child_task.wait(1.5s) == future_status::timeout) {
|
||||||
|
kill(child_pid, SIGTERM);
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The password has been entered, we are passing it to PAM stack
|
||||||
|
return PAM_IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by PAM when a user needs to be authenticated, for example by running
|
||||||
|
// the sudo command
|
||||||
|
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
|
||||||
|
const char **argv) {
|
||||||
|
return identify(pamh, flags, argc, argv, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by PAM when a session is started, such as by the su command
|
||||||
|
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
|
||||||
|
const char **argv) {
|
||||||
|
return identify(pamh, flags, argc, argv, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The functions below are required by PAM, but not needed in this module
|
||||||
|
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
|
||||||
|
const char **argv) {
|
||||||
|
return PAM_IGNORE;
|
||||||
|
}
|
||||||
|
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
|
||||||
|
const char **argv) {
|
||||||
|
return PAM_IGNORE;
|
||||||
|
}
|
||||||
|
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc,
|
||||||
|
const char **argv) {
|
||||||
|
return PAM_IGNORE;
|
||||||
|
}
|
||||||
|
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
|
||||||
|
const char **argv) {
|
||||||
|
return PAM_IGNORE;
|
||||||
|
}
|
36
howdy/src/pam/main.hh
Normal file
36
howdy/src/pam/main.hh
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef MAIN_H_
|
||||||
|
#define MAIN_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
enum class Type { Howdy, Pam };
|
||||||
|
|
||||||
|
enum class Workaround { Off, Input, Native };
|
||||||
|
|
||||||
|
inline bool operator==(const std::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");
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator==(const Workaround &l, const std::string &r) {
|
||||||
|
return operator==(r, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(const std::string &l, const Workaround &r) {
|
||||||
|
return !operator==(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(const Workaround &l, const std::string &r) {
|
||||||
|
return operator!=(r, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MAIN_H_
|
9
howdy/src/pam/meson.build
Normal file
9
howdy/src/pam/meson.build
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
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: ['locale'])
|
||||||
|
threads = dependency('threads')
|
||||||
|
shared_library('pam_howdy', 'main.cc', dependencies: [boost, libpam, inih_cpp, threads], install: true, install_dir: '/lib/security/')
|
69
howdy/src/pam/optional_task.hh
Normal file
69
howdy/src/pam/optional_task.hh
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#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();
|
||||||
|
template <typename Dur> std::future_status wait(std::chrono::duration<Dur>);
|
||||||
|
T get();
|
||||||
|
bool is_active();
|
||||||
|
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>
|
||||||
|
template <typename Dur>
|
||||||
|
std::future_status optional_task<T>::wait(std::chrono::duration<Dur> dur) {
|
||||||
|
return _future.wait_for(dur);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> T optional_task<T>::get() {
|
||||||
|
assert(!_is_active && _spawned);
|
||||||
|
return _future.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> bool optional_task<T>::is_active() { return _is_active; }
|
||||||
|
|
||||||
|
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_
|
|
@ -1,70 +0,0 @@
|
||||||
#
|
|
||||||
# spec file for package howdy
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 Dmitriy O. Afanasyev, <dmafanasyev@gmail.com>.
|
|
||||||
#
|
|
||||||
# All modifications and additions to the file contributed by third parties
|
|
||||||
# remain the property of their copyright owners, unless otherwise agreed
|
|
||||||
# upon. The license for this file, and modifications and additions to the
|
|
||||||
# file, is the same license as for the pristine package itself (unless the
|
|
||||||
# license for the pristine package is not an Open Source License, in which
|
|
||||||
# case the license is the MIT License). An "Open Source License" is a
|
|
||||||
# license that conforms to the Open Source Definition (Version 1.9)
|
|
||||||
# published by the Open Source Initiative.
|
|
||||||
|
|
||||||
Name: howdy
|
|
||||||
Version: 2.5.1
|
|
||||||
Release: 0
|
|
||||||
Summary: Windows Hello™ style authentication for Linux
|
|
||||||
License: MIT
|
|
||||||
Url: https://github.com/boltgolt/%{name}
|
|
||||||
Group: System/Base
|
|
||||||
Source: https://github.com/boltgolt/%{name}/archive/v%{version}.tar.gz
|
|
||||||
|
|
||||||
BuildRequires: wget
|
|
||||||
Requires: python3-opencv
|
|
||||||
Requires: ffmpeg
|
|
||||||
Requires: libv4l2-0
|
|
||||||
Requires: pam-python
|
|
||||||
Requires: python3-dlib
|
|
||||||
|
|
||||||
#TODO: pre and post install steps, auto conf /etc/pam.d
|
|
||||||
|
|
||||||
%description
|
|
||||||
Windows Hello™ style authentication for Linux. Use your built-in IR emitters and camera in combination with face recognition to prove who you are.
|
|
||||||
|
|
||||||
%prep
|
|
||||||
%setup -q -n howdy-%{version}
|
|
||||||
|
|
||||||
%build
|
|
||||||
## nothing to build
|
|
||||||
|
|
||||||
%install
|
|
||||||
mkdir -p %{buildroot}%{_libdir}/security/%{name}
|
|
||||||
rm -fr src/*~
|
|
||||||
cp -pr src/* %{buildroot}%{_libdir}/security/%{name}
|
|
||||||
|
|
||||||
# Facial recognition model preinstalled manually (before packaging), need to delete some files
|
|
||||||
rm -fr %{buildroot}%{_libdir}/security/%{name}/dlib-data/{Readme.md,install.sh,.gitignore}
|
|
||||||
|
|
||||||
#Add bash completion
|
|
||||||
mkdir -p %{buildroot}%{_datadir}/bash-completion/completions
|
|
||||||
install -Dm 644 autocomplete/%{name} %{buildroot}%{_datadir}/bash-completion/completions
|
|
||||||
|
|
||||||
# Create an executable
|
|
||||||
mkdir -p %{buildroot}%{_bindir}
|
|
||||||
chmod +x %{buildroot}%{_libdir}/security/%{name}/cli.py
|
|
||||||
ln -s %{_libdir}/security/%{name}/cli.py %{buildroot}%{_bindir}/%{name}
|
|
||||||
|
|
||||||
%files
|
|
||||||
%license LICENSE
|
|
||||||
%doc README.md
|
|
||||||
%{_bindir}/%{name}
|
|
||||||
%dir %{_libdir}/security
|
|
||||||
%{_libdir}/security/%{name}
|
|
||||||
%{_datadir}/bash-completion/completions/%{name}
|
|
||||||
%config(noreplace) %{_libdir}/security/%{name}/config.ini
|
|
||||||
|
|
||||||
%changelog
|
|
||||||
* Sun May 12 2019 Dmitriy O. Afanasyev <dmafanasyev@gmail.com> - 2.5.1
|
|
||||||
- Initial packaging
|
|
|
@ -1,29 +0,0 @@
|
||||||
# TEST MODEL-FRAME COMPARE FUNCTIONS
|
|
||||||
set -o xtrace
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Make sure howdy is clean before starting
|
|
||||||
sudo howdy clear -y || true
|
|
||||||
|
|
||||||
# Learn match 1
|
|
||||||
sudo sed -i "s,device_path.*,device_path = $PWD\/tests\/video\/match1.m4v,g" /lib/security/howdy/config.ini
|
|
||||||
sudo howdy add -y
|
|
||||||
|
|
||||||
# Text compare matching with same camera input
|
|
||||||
sudo python3 /lib/security/howdy/compare.py $USER
|
|
||||||
|
|
||||||
# Change to match 2 and compare against the modal of match 1, which should fail
|
|
||||||
sudo sed -i "s,device_path.*,device_path = $PWD\/tests\/video\/match2.m4v,g" /lib/security/howdy/config.ini
|
|
||||||
! sudo python3 /lib/security/howdy/compare.py $USER
|
|
||||||
|
|
||||||
# Add match 2 as a model to compare both 1 and 2 at the same time
|
|
||||||
sudo howdy add -y
|
|
||||||
sudo python3 /lib/security/howdy/compare.py $USER
|
|
||||||
|
|
||||||
# Compare against a camera with no visible face
|
|
||||||
sudo sed -i "s,device_path.*,device_path = $PWD\/tests\/video\/noMatch.m4v,g" /lib/security/howdy/config.ini
|
|
||||||
! sudo python3 /lib/security/howdy/compare.py $USER
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
sudo howdy clear -y
|
|
||||||
sudo sed -i "s,device_path.*,device_path = none,g" /lib/security/howdy/config.ini
|
|
|
@ -1,9 +0,0 @@
|
||||||
# TEST INSTALLATION OF DEPENDENCIES
|
|
||||||
set -o xtrace
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Confirm the cv2 module has been installed correctly
|
|
||||||
sudo /usr/bin/env python3 -c "import cv2; print(cv2.__version__);"
|
|
||||||
|
|
||||||
# Confirm the dlib module has been installed correctly
|
|
||||||
sudo /usr/bin/env python3 -c "import dlib; print(dlib.__version__);"
|
|
32
tests/pam.sh
32
tests/pam.sh
|
@ -1,32 +0,0 @@
|
||||||
# TEST THE PAM INTEGRATION
|
|
||||||
set -o xtrace
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Make sure howdy is clean before starting
|
|
||||||
sudo howdy clear -y || true
|
|
||||||
|
|
||||||
# Change active camera to match video 1
|
|
||||||
sudo sed -i "s,device_path.*,device_path = $PWD/tests\/video\/match1.m4v,g" /lib/security/howdy/config.ini
|
|
||||||
|
|
||||||
# Let howdy add the match face
|
|
||||||
sudo howdy add -y
|
|
||||||
|
|
||||||
# Test the PAM auth
|
|
||||||
timeout 10 pamtester login $USER authenticate
|
|
||||||
|
|
||||||
# Clear the face models and change the camera to video 2
|
|
||||||
sudo howdy clear -y
|
|
||||||
sudo sed -i "s,device_path.*,device_path = $PWD\/tests\/video\/match2.m4v,g" /lib/security/howdy/config.ini
|
|
||||||
|
|
||||||
# Let howdy add the match face
|
|
||||||
sudo howdy add -y
|
|
||||||
|
|
||||||
# Try to open a elevated session through PAM
|
|
||||||
timeout 10 pamtester login $USER open_session
|
|
||||||
|
|
||||||
# Verify we can close sessions, even though howdy does not use this PAM function
|
|
||||||
timeout 10 pamtester login $USER close_session
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
sudo howdy clear -y
|
|
||||||
sudo sed -i "s,device_path.*,device_path = none,g" /lib/security/howdy/config.ini
|
|
|
@ -1,7 +0,0 @@
|
||||||
# TEST USER SUDO PASSTHOUGH (NON-ROOT)
|
|
||||||
set -o xtrace
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Check if the username passthough works correctly with sudo
|
|
||||||
howdy | ack-grep --passthru --color "current active user: travis"
|
|
||||||
sudo howdy | ack-grep --passthru --color "current active user: travis"
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue