diff --git a/.gitignore b/.gitignore index 0b8e60a..63ef476 100644 --- a/.gitignore +++ b/.gitignore @@ -101,10 +101,10 @@ ENV/ .mypy_cache/ # generated models -/src/models +/howdy/src/models # snapshots -/src/snapshots +/howdy/src/snapshots # build files debian/howdy.substvars diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 847bbae..0000000 --- a/.travis.yml +++ /dev/null @@ -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 diff --git a/archlinux/pam-python/.SRCINFO b/archlinux/pam-python/.SRCINFO deleted file mode 100644 index 5c0ebb7..0000000 --- a/archlinux/pam-python/.SRCINFO +++ /dev/null @@ -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 - diff --git a/archlinux/pam-python/.gitignore b/archlinux/pam-python/.gitignore deleted file mode 100644 index 6748987..0000000 --- a/archlinux/pam-python/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -pkg -src -*.tar.gz -*.zip -*.tar.xz -*.patch -*.dat.bz2 \ No newline at end of file diff --git a/archlinux/pam-python/PKGBUILD b/archlinux/pam-python/PKGBUILD deleted file mode 100644 index 1d2dbd8..0000000 --- a/archlinux/pam-python/PKGBUILD +++ /dev/null @@ -1,41 +0,0 @@ -# Maintainer: boltgolt -# Maintainer: Kelley McChesney -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" -} diff --git a/fedora/howdy.spec b/fedora/howdy.spec deleted file mode 100644 index c558f35..0000000 --- a/fedora/howdy.spec +++ /dev/null @@ -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 - 2.5.1-3 -- Add polkit policy - -* Sun Apr 07 2019 Luya Tshimbalanga - 2.5.1-2 -- Install facial recognition data - -* Tue Apr 02 2019 Luya Tshimbalanga - 2.5.1-1 -- Update to 2.5.1 - -* Sat Mar 16 2019 Luya Tshimbalanga - 2.5.0-3 -- Require python-v4l2 - -* Wed Jan 23 2019 Luya Tshimbalanga - 2.5.0-2 -- Fix pam configuration - -* Sun Jan 06 2019 Luya Tshimbalanga - 2.5.0-1 -- Update to 2.5.0 - -* Thu Nov 29 2018 Luya Tshimbalanga - 2.4.0-3 -- Add conditional statement for RHEL/Centos 7.x based on williamwlk spec - -* Thu Nov 29 2018 Luya Tshimbalanga - 2.4.0-3 -- Include bash completion - -* Mon Nov 26 2018 Luya Tshimbalanga - 2.4.0-2 -- Switch to new requirement method from Fedora Python guideline - -* Mon Nov 26 2018 Luya Tshimbalanga - 2.4.0-1 -- Update to 2.4.0 - -* Thu Nov 1 2018 Luya Tshimbalanga - 2.3.1-1 -- Initial package diff --git a/archlinux/.gitignore b/howdy/archlinux/.gitignore similarity index 100% rename from archlinux/.gitignore rename to howdy/archlinux/.gitignore diff --git a/archlinux/howdy/.SRCINFO b/howdy/archlinux/howdy/.SRCINFO similarity index 100% rename from archlinux/howdy/.SRCINFO rename to howdy/archlinux/howdy/.SRCINFO diff --git a/archlinux/howdy/.gitignore b/howdy/archlinux/howdy/.gitignore similarity index 100% rename from archlinux/howdy/.gitignore rename to howdy/archlinux/howdy/.gitignore diff --git a/archlinux/howdy/PKGBUILD b/howdy/archlinux/howdy/PKGBUILD similarity index 100% rename from archlinux/howdy/PKGBUILD rename to howdy/archlinux/howdy/PKGBUILD diff --git a/autocomplete/howdy b/howdy/autocomplete/howdy similarity index 100% rename from autocomplete/howdy rename to howdy/autocomplete/howdy diff --git a/debian/changelog b/howdy/debian/changelog similarity index 100% rename from debian/changelog rename to howdy/debian/changelog diff --git a/debian/compat b/howdy/debian/compat similarity index 100% rename from debian/compat rename to howdy/debian/compat diff --git a/debian/control b/howdy/debian/control similarity index 100% rename from debian/control rename to howdy/debian/control diff --git a/debian/copyright b/howdy/debian/copyright similarity index 100% rename from debian/copyright rename to howdy/debian/copyright diff --git a/debian/howdy.1 b/howdy/debian/howdy.1 similarity index 100% rename from debian/howdy.1 rename to howdy/debian/howdy.1 diff --git a/debian/howdy.lintian-overrides b/howdy/debian/howdy.lintian-overrides similarity index 100% rename from debian/howdy.lintian-overrides rename to howdy/debian/howdy.lintian-overrides diff --git a/debian/howdy.manpages b/howdy/debian/howdy.manpages similarity index 100% rename from debian/howdy.manpages rename to howdy/debian/howdy.manpages diff --git a/debian/install b/howdy/debian/install similarity index 100% rename from debian/install rename to howdy/debian/install diff --git a/debian/postinst b/howdy/debian/postinst similarity index 100% rename from debian/postinst rename to howdy/debian/postinst diff --git a/debian/preinst b/howdy/debian/preinst similarity index 100% rename from debian/preinst rename to howdy/debian/preinst diff --git a/debian/prerm b/howdy/debian/prerm similarity index 100% rename from debian/prerm rename to howdy/debian/prerm diff --git a/debian/rules b/howdy/debian/rules similarity index 100% rename from debian/rules rename to howdy/debian/rules diff --git a/debian/source/format b/howdy/debian/source/format similarity index 100% rename from debian/source/format rename to howdy/debian/source/format diff --git a/debian/source/options b/howdy/debian/source/options similarity index 100% rename from debian/source/options rename to howdy/debian/source/options diff --git a/fedora/com.github.boltgolt.howdy.policy b/howdy/fedora/com.github.boltgolt.howdy.policy similarity index 100% rename from fedora/com.github.boltgolt.howdy.policy rename to howdy/fedora/com.github.boltgolt.howdy.policy diff --git a/src/cli.py b/howdy/src/cli.py similarity index 100% rename from src/cli.py rename to howdy/src/cli.py diff --git a/src/cli/__init__.py b/howdy/src/cli/__init__.py similarity index 100% rename from src/cli/__init__.py rename to howdy/src/cli/__init__.py diff --git a/src/cli/add.py b/howdy/src/cli/add.py similarity index 100% rename from src/cli/add.py rename to howdy/src/cli/add.py diff --git a/src/cli/clear.py b/howdy/src/cli/clear.py similarity index 100% rename from src/cli/clear.py rename to howdy/src/cli/clear.py diff --git a/src/cli/config.py b/howdy/src/cli/config.py similarity index 100% rename from src/cli/config.py rename to howdy/src/cli/config.py diff --git a/src/cli/disable.py b/howdy/src/cli/disable.py similarity index 100% rename from src/cli/disable.py rename to howdy/src/cli/disable.py diff --git a/src/cli/list.py b/howdy/src/cli/list.py similarity index 100% rename from src/cli/list.py rename to howdy/src/cli/list.py diff --git a/src/cli/remove.py b/howdy/src/cli/remove.py similarity index 100% rename from src/cli/remove.py rename to howdy/src/cli/remove.py diff --git a/src/cli/set.py b/howdy/src/cli/set.py similarity index 100% rename from src/cli/set.py rename to howdy/src/cli/set.py diff --git a/src/cli/snap.py b/howdy/src/cli/snap.py similarity index 100% rename from src/cli/snap.py rename to howdy/src/cli/snap.py diff --git a/src/cli/test.py b/howdy/src/cli/test.py similarity index 100% rename from src/cli/test.py rename to howdy/src/cli/test.py diff --git a/src/compare.py b/howdy/src/compare.py similarity index 100% rename from src/compare.py rename to howdy/src/compare.py diff --git a/src/config.ini b/howdy/src/config.ini similarity index 100% rename from src/config.ini rename to howdy/src/config.ini diff --git a/src/dlib-data/.gitignore b/howdy/src/dlib-data/.gitignore similarity index 100% rename from src/dlib-data/.gitignore rename to howdy/src/dlib-data/.gitignore diff --git a/src/dlib-data/Readme.md b/howdy/src/dlib-data/Readme.md similarity index 100% rename from src/dlib-data/Readme.md rename to howdy/src/dlib-data/Readme.md diff --git a/src/dlib-data/install.sh b/howdy/src/dlib-data/install.sh similarity index 100% rename from src/dlib-data/install.sh rename to howdy/src/dlib-data/install.sh diff --git a/src/i18n.py b/howdy/src/i18n.py similarity index 100% rename from src/i18n.py rename to howdy/src/i18n.py diff --git a/src/logo.png b/howdy/src/logo.png similarity index 100% rename from src/logo.png rename to howdy/src/logo.png diff --git a/src/pam-config/howdy b/howdy/src/pam-config/howdy similarity index 100% rename from src/pam-config/howdy rename to howdy/src/pam-config/howdy diff --git a/src/pam.py b/howdy/src/pam.py similarity index 100% rename from src/pam.py rename to howdy/src/pam.py diff --git a/howdy/src/pam/.gitignore b/howdy/src/pam/.gitignore new file mode 100644 index 0000000..feaa494 --- /dev/null +++ b/howdy/src/pam/.gitignore @@ -0,0 +1 @@ +subprojects/*/ diff --git a/howdy/src/pam/README.md b/howdy/src/pam/README.md new file mode 100644 index 0000000..765cba8 --- /dev/null +++ b/howdy/src/pam/README.md @@ -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 +``` diff --git a/howdy/src/pam/main.cc b/howdy/src/pam/main.cc new file mode 100644 index 0000000..2595c61 --- /dev/null +++ b/howdy/src/pam/main.cc @@ -0,0 +1,394 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#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 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 + 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 child_task(packaged_task([&] { + int status; + wait(&status); + { + unique_lock 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> pass_task( + packaged_task()>([&] { + char *auth_tok_ptr = nullptr; + int pam_res = pam_get_authtok(pamh, PAM_AUTHTOK, + (const char **)&auth_tok_ptr, nullptr); + { + unique_lock lk(m); + confirmation_type = Type::Pam; + } + cv.notify_all(); + return tuple(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 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; +} diff --git a/howdy/src/pam/main.hh b/howdy/src/pam/main.hh new file mode 100644 index 0000000..6b75580 --- /dev/null +++ b/howdy/src/pam/main.hh @@ -0,0 +1,36 @@ +#ifndef MAIN_H_ +#define MAIN_H_ + +#include +#include + +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_ diff --git a/howdy/src/pam/meson.build b/howdy/src/pam/meson.build new file mode 100644 index 0000000..add0595 --- /dev/null +++ b/howdy/src/pam/meson.build @@ -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/') diff --git a/howdy/src/pam/optional_task.hh b/howdy/src/pam/optional_task.hh new file mode 100644 index 0000000..827c535 --- /dev/null +++ b/howdy/src/pam/optional_task.hh @@ -0,0 +1,69 @@ +#ifndef OPTIONAL_TASK_H_ +#define OPTIONAL_TASK_H_ + +#include +#include +#include +#include + +template class optional_task { + std::thread _thread; + std::packaged_task _task; + std::future _future; + std::atomic _spawned; + std::atomic _is_active; + +public: + optional_task(std::packaged_task); + void activate(); + template std::future_status wait(std::chrono::duration); + T get(); + bool is_active(); + void stop(bool); + ~optional_task(); +}; + +template +optional_task::optional_task(std::packaged_task t) + : _task(std::move(t)), _future(_task.get_future()) {} + +template void optional_task::activate() { + _thread = std::thread(std::move(_task)); + _spawned = true; + _is_active = true; +} + +template +template +std::future_status optional_task::wait(std::chrono::duration dur) { + return _future.wait_for(dur); +} + +template T optional_task::get() { + assert(!_is_active && _spawned); + return _future.get(); +} + +template bool optional_task::is_active() { return _is_active; } + +template void optional_task::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 optional_task::~optional_task() { + if (_is_active && _spawned) + stop(false); +} + +#endif // OPTIONAL_TASK_H_ diff --git a/src/recorders/__init__.py b/howdy/src/recorders/__init__.py similarity index 100% rename from src/recorders/__init__.py rename to howdy/src/recorders/__init__.py diff --git a/src/recorders/ffmpeg_reader.py b/howdy/src/recorders/ffmpeg_reader.py similarity index 100% rename from src/recorders/ffmpeg_reader.py rename to howdy/src/recorders/ffmpeg_reader.py diff --git a/src/recorders/pyv4l2_reader.py b/howdy/src/recorders/pyv4l2_reader.py similarity index 100% rename from src/recorders/pyv4l2_reader.py rename to howdy/src/recorders/pyv4l2_reader.py diff --git a/src/recorders/v4l2.py b/howdy/src/recorders/v4l2.py similarity index 100% rename from src/recorders/v4l2.py rename to howdy/src/recorders/v4l2.py diff --git a/src/recorders/video_capture.py b/howdy/src/recorders/video_capture.py similarity index 100% rename from src/recorders/video_capture.py rename to howdy/src/recorders/video_capture.py diff --git a/src/rubberstamps/__init__.py b/howdy/src/rubberstamps/__init__.py similarity index 100% rename from src/rubberstamps/__init__.py rename to howdy/src/rubberstamps/__init__.py diff --git a/src/rubberstamps/hotkey.py b/howdy/src/rubberstamps/hotkey.py similarity index 100% rename from src/rubberstamps/hotkey.py rename to howdy/src/rubberstamps/hotkey.py diff --git a/src/rubberstamps/nod.py b/howdy/src/rubberstamps/nod.py similarity index 100% rename from src/rubberstamps/nod.py rename to howdy/src/rubberstamps/nod.py diff --git a/src/snapshot.py b/howdy/src/snapshot.py similarity index 100% rename from src/snapshot.py rename to howdy/src/snapshot.py diff --git a/opensuse/howdy.spec b/opensuse/howdy.spec deleted file mode 100644 index 2b5721d..0000000 --- a/opensuse/howdy.spec +++ /dev/null @@ -1,70 +0,0 @@ -# -# spec file for package howdy -# -# Copyright (c) 2019 Dmitriy O. Afanasyev, . -# -# 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 - 2.5.1 -- Initial packaging diff --git a/tests/compare.sh b/tests/compare.sh deleted file mode 100755 index 969d550..0000000 --- a/tests/compare.sh +++ /dev/null @@ -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 diff --git a/tests/importing.sh b/tests/importing.sh deleted file mode 100755 index 9a75c76..0000000 --- a/tests/importing.sh +++ /dev/null @@ -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__);" diff --git a/tests/pam.sh b/tests/pam.sh deleted file mode 100755 index 7633546..0000000 --- a/tests/pam.sh +++ /dev/null @@ -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 diff --git a/tests/passthrough.sh b/tests/passthrough.sh deleted file mode 100755 index 629d142..0000000 --- a/tests/passthrough.sh +++ /dev/null @@ -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" diff --git a/tests/video/match1.m4v b/tests/video/match1.m4v deleted file mode 100644 index b3c2497..0000000 Binary files a/tests/video/match1.m4v and /dev/null differ diff --git a/tests/video/match2.m4v b/tests/video/match2.m4v deleted file mode 100644 index 7797c28..0000000 Binary files a/tests/video/match2.m4v and /dev/null differ diff --git a/tests/video/noMatch.m4v b/tests/video/noMatch.m4v deleted file mode 100644 index dacd9a1..0000000 Binary files a/tests/video/noMatch.m4v and /dev/null differ