diff --git a/bin/howdy b/bin/howdy new file mode 100755 index 0000000..6e9c61b --- /dev/null +++ b/bin/howdy @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +python3 ../cli.py diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..a77d55a --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +howdy (2.0.0) UNRELEASED; urgency=middle + + * Initial release. + + -- boltgolt Wed, 04 Apr 2018 18:13:15 +0200 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..c8facbf --- /dev/null +++ b/debian/control @@ -0,0 +1,11 @@ +Source: howdy +Section: unknown +Priority: optional +Maintainer: boltgolt +Homepage: https://github.com/Boltgolt/howdy +Package: howdy +Version: 2.0.0 +Architecture: all +Depends: git, python3, python3-pip, python3-dev, python3-setuptools, build-essential, libpam-python, fswebcam, libopencv-dev, python-opencv, cmake +Description: Windows Helloâ„¢ style authentication for Ubuntu. + Use your built-in IR emitters and camera in combination with face recognition to prove who you are. diff --git a/installer.py b/debian/postinst old mode 100644 new mode 100755 similarity index 63% rename from installer.py rename to debian/postinst index 5ec2fd1..93449cf --- a/installer.py +++ b/debian/postinst @@ -1,5 +1,6 @@ +#!/usr/bin/env python3 # Installation script to install howdy -# Runs completely independent of the others +# Executed after primary apt install # Import required modules import subprocess @@ -19,33 +20,13 @@ def handleStatus(status): """Abort if a command fails""" if (status != 0): print("\033[31mError while running last command\033[0m") - sys.exit() + sys.exit(1) # Check if we're running as root user = os.getenv("SUDO_USER") if user is None: - print("Please run this script as a sudo user") - sys.exit() - -# Print some nice intro text -print("\n\033[33m HOWDY INSTALLER FOR UBUNTU\033[0m") -print(" Version 1, 2016/02/05\n") - -# Let it sink in -time.sleep(.5) -log("Installing required apt packages") - -# Install packages though apt -handleStatus(subprocess.call(["apt", "install", "-y", "git", - "python3-pip", - "python3-dev", - "python3-setuptools", - "build-essential", - "libpam-python", - "fswebcam", - "libopencv-dev", - "python-opencv", - "cmake"])) + print("Can't continue installation without root rights.") + sys.exit(1) # Update pip handleStatus(subprocess.call(["pip3 install --upgrade pip"], shell=True)) @@ -55,55 +36,59 @@ log("Starting camera check") # Get all devices devices = os.listdir("/dev") # The picked video device id -picked = False +picked = -1 -# Loop though all devices -for dev in devices: - # Only use the video devices - if (dev[:5] == "video"): - time.sleep(.5) +# If prompting has been disabled, skip camera check +if "HOWDY_NO_PROMPT" in os.environ: + picked = "0" +else: + # Loop though all devices + for dev in devices: + # Only use the video devices + if (dev[:5] == "video"): + time.sleep(.5) - # The full path to the device is the default name - device_name = "/dev/" + dev - # Get the udevadm details to try to get a better name - udevadm = subprocess.check_output(["udevadm info -r --query=all -n " + device_name], shell=True).decode("utf-8") + # The full path to the device is the default name + device_name = "/dev/" + dev + # Get the udevadm details to try to get a better name + udevadm = subprocess.check_output(["udevadm info -r --query=all -n " + device_name], shell=True).decode("utf-8") - # Loop though udevadm to search for a better name - for line in udevadm.split("\n"): - # Match it and encase it in quotes - re_name = re.search('product.*=(.*)$', line, re.IGNORECASE) - if re_name: - device_name = '"' + re_name.group(1) + '"' + # Loop though udevadm to search for a better name + for line in udevadm.split("\n"): + # Match it and encase it in quotes + re_name = re.search('product.*=(.*)$', line, re.IGNORECASE) + if re_name: + device_name = '"' + re_name.group(1) + '"' - # Show what device we're using - print("Trying " + device_name) + # Show what device we're using + print("Trying " + device_name) - # Let fswebcam keep the camera open in the background - sub = subprocess.Popen(["fswebcam -S 9999999999 -d /dev/" + dev + " /dev/null 2>/dev/null"], shell=True, preexec_fn=os.setsid) + # Let fswebcam keep the camera open in the background + sub = subprocess.Popen(["fswebcam -S 9999999999 -d /dev/" + dev + " /dev/null 2>/dev/null"], shell=True, preexec_fn=os.setsid) - try: - # Ask the user if this is the right one - print("\033[33mOne of your cameras should now be on.\033[0m") - ans = input("Did your IR emitters turn on? [y/N]: ") - except KeyboardInterrupt: - # Kill fswebcam if the user aborts + try: + # Ask the user if this is the right one + print("\033[33mOne of your cameras should now be on.\033[0m") + ans = input("Did your IR emitters turn on? [y/N]: ") + except KeyboardInterrupt: + # Kill fswebcam if the user aborts + os.killpg(os.getpgid(sub.pid), signal.SIGTERM) + raise + + # The user has answered, kill fswebcam os.killpg(os.getpgid(sub.pid), signal.SIGTERM) - raise - # The user has answered, kill fswebcam - os.killpg(os.getpgid(sub.pid), signal.SIGTERM) - - # Set this camera as picked if the answer was yes, go to the next one if no - if (ans.lower() == "y"): - picked = dev[5:] - break - else: - print("Inerpeting as a \"NO\"\n") + # Set this camera as picked if the answer was yes, go to the next one if no + if (ans.lower() == "y"): + picked = dev[5:] + break + else: + print("Inerpeting as a \"NO\"\n") # Abort if no camera was picked -if (picked == False): +if (picked == -1): print("\033[31mNo suitable IR camera found\033[0m") - sys.exit() + sys.exit(1) log("Cloning dlib") @@ -141,14 +126,9 @@ for line in fileinput.input(["/lib/security/howdy/config.ini"], inplace = 1): # Secure the howdy folder handleStatus(subprocess.call(["chmod 600 -R /lib/security/howdy/"], shell=True)) -# Allow anyone to execute the python CLI -handleStatus(subprocess.call(["chmod 755 /lib/security/howdy"], shell=True)) -handleStatus(subprocess.call(["chmod 744 /lib/security/howdy/cli.py"], shell=True)) -handleStatus(subprocess.call(["chmod 744 -R /lib/security/howdy/cli"], shell=True)) - # Make the CLI executable as howdy -handleStatus(subprocess.call(["ln -s /lib/security/howdy/cli.py /usr/local/bin/howdy"], shell=True)) -handleStatus(subprocess.call(["chmod +x /usr/local/bin/howdy"], shell=True)) +handleStatus(subprocess.call(["ln -s /lib/security/howdy/cli.py /usr/bin/howdy"], shell=True)) +handleStatus(subprocess.call(["chmod +x /usr/bin/howdy"], shell=True)) # Install the command autocomplete, don't error on failure subprocess.call(["sudo cp /lib/security/howdy/autocomplete.sh /etc/bash_completion.d/howdy"], shell=True) @@ -207,14 +187,16 @@ for line in printlines: # Print a footer print("\033[33m" + ">>> END OF /etc/pam.d/common-auth" + "\033[0m" + "\n") -# Ask the user if this change is okay -print("Lines will be insterted in /etc/pam.d/common-auth as shown above") -ans = input("Apply this change? [y/N]: ") +# Do not prompt for a yes if we're in no promt mode +if "HOWDY_NO_PROMPT" in os.environ: + # Ask the user if this change is okay + print("Lines will be insterted in /etc/pam.d/common-auth as shown above") + ans = input("Apply this change? [y/N]: ") -# Abort the whole thing if it's not -if (ans.lower() != "y"): - print("Inerpeting as a \"NO\", aborting") - sys.exit() + # Abort the whole thing if it's not + if (ans.lower() != "y"): + print("Inerpeting as a \"NO\", aborting") + sys.exit(1) print("Adding lines to PAM\n") diff --git a/uninstall.py b/debian/prerm old mode 100644 new mode 100755 similarity index 95% rename from uninstall.py rename to debian/prerm index 8cc56a1..0cd4327 --- a/uninstall.py +++ b/debian/prerm @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 +# Executed on deinstallation # Completely remove howdy from the system # Import required modules diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..1a24852 --- /dev/null +++ b/debian/rules @@ -0,0 +1 @@ +#!/bin/sh diff --git a/pam.py b/pam.py index 27977ab..8c4eabc 100644 --- a/pam.py +++ b/pam.py @@ -4,8 +4,6 @@ import subprocess import sys import os -import threading -import time # pam-python is running python 2, so we use the old module here import ConfigParser @@ -17,11 +15,6 @@ config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini") def doAuth(pamh): """Start authentication in a seperate process""" - # Abort if the session is remote - if pamh.rhost is not None or "SSH_CONNECTION" in os.environ: - pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Skipping face recognition for remote session")) - return pamh.PAM_AUTH_ERR - # Run compare as python3 subprocess to circumvent python version and import issues status = subprocess.call(["python3", os.path.dirname(os.path.abspath(__file__)) + "/compare.py", pamh.get_user()])