Merge pull request #3 from Boltgolt/dev

Adding new installer and CLI
This commit is contained in:
boltgolt 2018-02-14 00:14:08 +01:00 committed by GitHub
commit 1d4308cc0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 725 additions and 145 deletions

View file

@ -1,46 +1,41 @@
# Howdy for Ubuntu
Windows Hello™ style authentication for Ubuntu. Use your build in IR emitters and camera in combination with face recognition to prove who you are.
Windows Hello™ style authentication for Ubuntu. Use your built-in IR emitters and camera in combination with face recognition to prove who you are.
Using the central authentication system in Linux (PAM), this works everywhere you would otherwise need your password: Login, lock screen, sudo, su, etc.
### Installation
First we need to install pam-python, fswebcam and OpenCV from the Ubuntu repositories:
Run the installer by pasting (`ctrl+shift+V`) the following command into the terminal:
```
sudo apt install libpam-python fswebcam libopencv-dev python-opencv
wget -O /tmp/howdy_install.py https://raw.githubusercontent.com/Boltgolt/howdy/master/installer.py && sudo python3 /tmp/howdy_install.py
```
After that, install the face_recognition python module. There's an excellent step by step guide on how to do this on [its github page](https://github.com/ageitgey/face_recognition#installation).
In the root of your cloned repo is a file called `config.ini`. The `device_id` variable in this file is important, make sure it is the IR camera and not your normal webcam.
Now it's time to let Howdy learn your face. The learn.py script will make 3 models of your face and store them as an encoded set in the `models` folder. To run the script, open a terminal, navigate to this repository and run:
```
python3 learn.py
```
The script should guide you through the process.
Finally we need to tell PAM that there's a new module installed. Open `/etc/pam.d/common-auth` as root (`sudo nano /etc/pam.d/common-auth`) and add the following line to the top of the file:
```
auth sufficient pam_python.so /path/to/pam.py
```
Replace the final argument with the full path to pam.py in this repository. The `sufficient` control tells PAM that Howdy is enough to authenticate the user, but if it fails we can fall back on more traditional methods.
This will guide you through the installation. When that's done run `howdy USER add` and replace `USER` with your username to add a face model.
If nothing went wrong we should be able to run sudo by just showing your face. Open a new terminal and run `sudo -i` to see it in action.
**Note:** The build of dlib can hang on 100% for over a minute, give it time.
### Command line
The installer adds a `howdy` command to manage face models for the current user. Use `howdy help` to list the available options.
### Troubleshooting
Any errors in the script itself get logged directly into the console and should indicate what went wrong. If authentication still fails but no errors are printed you could take a look at the last lines in `/var/log/auth.log` to see if anything has been reported there.
Any python errors get logged directly into the console and should indicate what went wrong. If authentication still fails but no errors are printed you could take a look at the last lines in `/var/log/auth.log` to see if anything has been reported there.
If you encounter an error that hasn't been reported yet, don't be afraid to open a new issue.
### Uninstalling
There is an uninstaller available, run `sudo python3 /lib/security/howdy/uninstall.py` to remove Howdy from your system.
### A note on security
This script is in no way as secure as a password and will never be. Although it's harder to fool than normal face recognition, a person who looks similar to you or well-printed photo of you could be enough to do it.
To minimize the chance of this script being compromised, it's recommend to store this repo in `/etc/pam.d` and to make it read only.
To minimize the chance of this script being compromised, it's recommend to leave this repo in /lib/security and to keep it read only.
DO NOT USE THIS SCRIPT AS THE SOLE AUTHENTICATION METHOD FOR YOUR SYSTEM.
DO NOT USE HOWDY AS THE SOLE AUTHENTICATION METHOD FOR YOUR SYSTEM.

15
autocomplete.sh Normal file
View file

@ -0,0 +1,15 @@
# Autocomplete file run in bash
# Will sugest arguments on tab
_howdy() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="help list add remove clear"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _howdy howdy

41
cli.py Executable file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# CLI directly called by running the howdy command
# Import required modules
import sys
import os
# Check if the minimum of 3 arugemnts has been met and print help otherwise
if (len(sys.argv) < 3):
print("Howdy IR face recognition help")
import cli.help
sys.exit()
# The command given
cmd = sys.argv[2]
# Requre sudo for comamnds that need root rights to read the model files
if cmd in ["list", "add", "remove", "clear"] and os.getenv("SUDO_USER") is None:
print("Please run this command with sudo")
sys.exit()
# Call the right files for the given command
if cmd == "list":
import cli.list
elif cmd == "help":
print("Howdy IR face recognition")
import cli.help
elif cmd == "add":
import cli.add
elif cmd == "remove":
import cli.remove
elif cmd == "clear":
import cli.clear
else:
# If the comand is invalid, check if the user hasn't swapped the username and command
if sys.argv[1] in ["list", "add", "remove", "clear", "help"]:
print("Usage: howdy <user> <command>")
else:
print('Unknown command "' + cmd + '"')
import cli.help

0
cli/__init__.py Normal file
View file

View file

@ -1,24 +1,35 @@
# Save the face of the user in encoded form
# Import required modules
import face_recognition
import subprocess
import time
import os
import sys
import json
# Import config and extra functions
import configparser
import utils
# Try to import face_recognition and give a nice error if we can't
# Add should be the first point where import issues show up
try:
import face_recognition
except ImportError as err:
print(err)
print("\nCan't import the face_recognition module, check the output of")
print("pip3 show face_recognition")
sys.exit()
# Get the absolute path to the current file
path = os.path.dirname(os.path.abspath(__file__))
# Read config from disk
config = configparser.ConfigParser()
config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
config.read(path + "/../config.ini")
def captureFrame(delay):
"""Capture and encode 1 frame of video"""
global encodings
global insert_model
# Call fswebcam to save a frame to /tmp with a set delay
exit_code = subprocess.call(["fswebcam", "-S", str(delay), "--no-banner", "-d", "/dev/video" + str(config.get("video", "device_id")), tmp_file])
@ -54,36 +65,53 @@ def captureFrame(delay):
for point in enc[0]:
clean_enc.append(point)
encodings.append(clean_enc)
insert_model["data"].append(clean_enc)
# The current user
user = os.environ.get("USER")
user = sys.argv[1]
# The name of the tmp frame file to user
tmp_file = "/tmp/howdy_" + user + ".jpg"
# The permanent file to store the encoded model in
enc_file = "./models/" + user + ".dat"
enc_file = path + "/../models/" + user + ".dat"
# Known encodings
encodings = []
# Make the ./models folder if it doesn't already exist
if not os.path.exists("models"):
if not os.path.exists(path + "/../models"):
print("No face model folder found, creating one")
os.makedirs("models")
os.makedirs(path + "/../models")
# To try read a premade encodings file if it exists
try:
encodings = json.load(open(enc_file))
except FileNotFoundError:
encodings = False
# If a file does exist, ask the user what needs to be done
if encodings != False:
encodings = utils.print_menu(encodings)
else:
encodings = []
print("\nLearning face for the user account " + user)
print("Please look straight into the camera for 5 seconds")
print("Adding face model for the user account " + user)
# Set the default label
label = "Initial model"
# If models already exist, set that default label
if len(encodings) > 0:
label = "Model #" + str(len(encodings) + 1)
# Ask the user for a custom label
label_in = input("Enter a label for this new model [" + label + "]: ")
# Set the custom label (if any) and limit it to 24 characters
if label_in != "":
label = label_in[:24]
# Prepare the metadata for insertion
insert_model = {
"time": int(time.time()),
"label": label,
"id": len(encodings),
"data": []
}
print("\nPlease look straight into the camera for 5 seconds")
# Give the user time to read
time.sleep(2)
@ -93,6 +121,9 @@ for delay in [30, 6, 0]:
time.sleep(.3)
captureFrame(delay)
# Insert full object into the list
encodings.append(insert_model)
# Save the new encodings to disk
with open(enc_file, "w") as datafile:
json.dump(encodings, datafile)

33
cli/clear.py Normal file
View file

@ -0,0 +1,33 @@
# Clear all models by deleting the whole file
# Import required modules
import os
import sys
# Get the full path to this file
path = os.path.dirname(os.path.abspath(__file__))
# Get the passed user
user = sys.argv[1]
# Check if the models folder is there
if not os.path.exists(path + "/../models"):
print("No models created yet, can't clear them if they don't exist")
sys.exit()
# Check if the user has a models file to delete
if not os.path.isfile(path + "/../models/" + user + ".dat"):
print(user + " has no models or they have been cleared already")
sys.exit()
# Double check with the user
print("This will clear all models for " + user)
ans = input("Do you want to continue [y/N]: ")
# Abort if they don't answer y or Y
if (ans.lower() != "y"):
print('\nInerpeting as a "NO"')
sys.exit()
# Delete otherwise
os.remove(path + "/../models/" + user + ".dat")
print("\nModels cleared")

16
cli/help.py Normal file
View file

@ -0,0 +1,16 @@
# Prints a simple help page for the CLI
print("""
Usage:
howdy <user> <command> [argument]
Commands:
help Show this help page
list List all saved face models for the current user
add Add a new face model for the current user
remove [id] Remove a specific model
clear Remove all face models for the current user
For support please visit
https://github.com/Boltgolt/howdy\
""")

46
cli/list.py Normal file
View file

@ -0,0 +1,46 @@
# List all models for a user
# Import required modules
import sys
import os
import json
import time
# Get the absolute path and the username
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
user = sys.argv[1]
# Check if the models file has been created yet
if not os.path.exists(path + "/models"):
print("Face models have not been initialized yet, please run:")
print("\n\thowdy " + user + " add\n")
sys.exit(1)
# Path to the models file
enc_file = path + "/models/" + user + ".dat"
# Try to load the models file and abort if the user does not have it yet
try:
encodings = json.load(open(enc_file))
except FileNotFoundError:
print("No face model known for the user " + user + ", please run:")
print("\n\thowdy " + user + " add\n")
sys.exit(1)
# Print a header
print("Known face models for " + user + ":")
print("\n\t\033[1;29mID Date Label\033[0m")
# Loop through all encodings and print info about them
for enc in encodings:
# Start with a tab and print the id
print("\t" + str(enc["id"]), end="")
# Print padding spaces after the id
print((4 - len(str(enc["id"]))) * " ", end="")
# Format the time as ISO in the local timezone
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(enc["time"])), end="")
# End with the label
print(" " + enc["label"])
# Add a closing enter
print()

78
cli/remove.py Normal file
View file

@ -0,0 +1,78 @@
# Remove a encoding from the models file
# Import required modules
import sys
import os
import json
# Get the absolute path and the username
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
user = sys.argv[1]
# Check if enough arguments have been passed
if len(sys.argv) == 3:
print("Please add the ID of the model to remove as an argument")
print("You can find the IDs by running:")
print("\n\thowdy " + user + " list\n")
sys.exit(1)
# Check if the models file has been created yet
if not os.path.exists(path + "/models"):
print("Face models have not been initialized yet, please run:")
print("\n\thowdy " + user + " add\n")
sys.exit(1)
# Path to the models file
enc_file = path + "/models/" + user + ".dat"
# Try to load the models file and abort if the user does not have it yet
try:
encodings = json.load(open(enc_file))
except FileNotFoundError:
print("No face model known for the user " + user + ", please run:")
print("\n\thowdy " + user + " add\n")
sys.exit(1)
# Tracks if a encoding with that id has been found
found = False
# Loop though all encodings and check if they match the argument
for enc in encodings:
if str(enc["id"]) == sys.argv[3]:
# Double check with the user
print('This will remove the model called "' + enc["label"] + '" for ' + user)
ans = input("Do you want to continue [y/N]: ")
# Abort if the answer isn't yes
if (ans.lower() != "y"):
print('\nInerpeting as a "NO"')
sys.exit()
# Mark as found and print an enter
found = True
print()
break
# Abort if no matching id was found
if not found:
print("No model with ID " + sys.argv[3] + " exists for " + user)
sys.exit()
# Remove the entire file if this encoding is the only one
if len(encodings) == 1:
os.remove(path + "/models/" + user + ".dat")
print("Removed last model, howdy disabled for user")
else:
# A place holder to contain the encodings that will remain
new_encodings = []
# Loop though all encodin and only add thos that don't need to be removed
for enc in encodings:
if str(enc["id"]) != sys.argv[3]:
new_encodings.append(enc)
# Save this new set to disk
with open(enc_file, "w") as datafile:
json.dump(new_encodings, datafile)
print("Removed model " + sys.argv[3])

View file

@ -1,69 +0,0 @@
# Compair incomming video with known faces
# Running in a local python instance to get around PATH issues
# Import required modules
import face_recognition
import cv2
import sys
import os
import json
import configparser
# Read config from disk
config = configparser.ConfigParser()
config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
def stop(status):
"""Stop the execution and close video stream"""
video_capture.release()
sys.exit(status)
# Make sure we were given an username to tast against
try:
if not isinstance(sys.argv[1], str):
sys.exit(1)
except IndexError:
sys.exit(1)
# The username of the authenticating user
user = sys.argv[1]
# List of known faces, encoded by face_recognition
encodings = []
# Amount of frames already matched
tries = 0
# Try to load the face model from the models folder
try:
encodings = json.load(open(os.path.dirname(os.path.abspath(__file__)) + "/models/" + user + ".dat"))
except FileNotFoundError:
sys.exit(10)
# Verify that we have a valid model file
if len(encodings) < 3:
sys.exit(1)
# Start video capture on the IR camera
video_capture = cv2.VideoCapture(int(config.get("video", "device_id")))
while True:
# Grab a single frame of video
ret, frame = video_capture.read()
# Get all faces from that frame as encodings
face_encodings = face_recognition.face_encodings(frame)
# Loop through each face
for face_encoding in face_encodings:
# Match this found face against a known face
matches = face_recognition.face_distance(encodings, face_encoding)
# Check if any match is certain enough to be the user we're looking for
for match in matches:
if match < int(config.get("video", "certainty")) and match > 0:
stop(0)
# Stop if we've exceded the maximum retry count
if tries > int(config.get("video", "frame_count")):
stop(11)
tries += 1

140
compare.py Normal file
View file

@ -0,0 +1,140 @@
# Compare incomming video with known faces
# Running in a local python instance to get around PATH issues
# Import required modules
import cv2
import sys
import os
import json
import time
import math
import configparser
# Start timing
timings = [time.time()]
# Read config from disk
config = configparser.ConfigParser()
config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
def stop(status):
"""Stop the execution and close video stream"""
video_capture.release()
sys.exit(status)
# Make sure we were given an username to tast against
try:
if not isinstance(sys.argv[1], str):
sys.exit(1)
except IndexError:
sys.exit(1)
# The username of the authenticating user
user = sys.argv[1]
# The model file contents
models = []
# Encoded face models
encodings = []
# Amount of frames already matched
tries = 0
# Try to load the face model from the models folder
try:
models = json.load(open(os.path.dirname(os.path.abspath(__file__)) + "/models/" + user + ".dat"))
except FileNotFoundError:
sys.exit(10)
# Check if the file contains a model
if len(models) < 1:
sys.exit(10)
# Put all models together into 1 array
for model in models:
encodings += model["data"]
# Import face recognition, takes some time
timings.append(time.time())
import face_recognition
timings.append(time.time())
# Start video capture on the IR camera
video_capture = cv2.VideoCapture(int(config.get("video", "device_id")))
timings.append(time.time())
# Fetch the max frame height
max_height = int(config.get("video", "max_height"))
# Start the read loop
frames = 0
while True:
frames += 1
# Grab a single frame of video
# Don't remove ret, it doesn't work without it
ret, frame = video_capture.read()
# Get the height and with of the image
height, width = frame.shape[:2]
# If the hight is too high
if max_height < height:
# Calculate the amount the image has to shrink
scaling_factor = max_height / float(height)
# Apply that factor to the frame
frame = cv2.resize(frame, None, fx=scaling_factor, fy=scaling_factor, interpolation=cv2.INTER_AREA)
# Save the new size for diagnostics
scale_height, scale_width = frame.shape[:2]
# Convert from BGR to RGB
frame = frame[:, :, ::-1]
# Get all faces from that frame as encodings
face_encodings = face_recognition.face_encodings(frame)
# Loop through each face
for face_encoding in face_encodings:
# Match this found face against a known face
matches = face_recognition.face_distance(encodings, face_encoding)
# Check if any match is certain enough to be the user we're looking for
match_index = 0
for match in matches:
match_index += 1
# Try to find a match that's confident enough
if match * 10 < float(config.get("video", "certainty")) and match > 0:
timings.append(time.time())
# If set to true in the config, print debug text
if config.get("debug", "end_report") == "true":
def print_timing(label, offset):
"""Helper function to print a timing from the list"""
print(" " + label + ": " + str(round((timings[1 + offset] - timings[offset]) * 1000)) + "ms")
print("Time spend")
print_timing("Starting up", 0)
print_timing("Importing face_recognition", 1)
print_timing("Opening the camera", 2)
print_timing("Searching for known face", 3)
print("\nResolution")
print(" Native: " + str(height) + "x" + str(width))
print(" Used: " + str(scale_height) + "x" + str(scale_width))
print("\nFrames searched: " + str(frames) + " (" + str(round(float(frames) / (timings[4] - timings[2]), 2)) + " fps)")
print("Certainty of winning frame: " + str(round(match * 10, 3)))
exposures = ["long", "medium", "short"]
model_id = math.floor(float(match_index) / 3)
print("Winning model: " + str(model_id) + " (\"" + models[model_id]["label"] + "\") using " + exposures[match_index % 3] + " exposure\n")
# End peacegully
stop(0)
# Stop if we've exceded the maximum retry count
if time.time() - timings[3] > int(config.get("video", "timout")):
stop(11)
tries += 1

View file

@ -9,11 +9,19 @@ suppress_unknown = false
[video]
# The certainty of the detected face belonging to the user of the account
# On a scale from 1 to 10, values above 5 are not recommended
certainty = 3
certainty = 3.5
# The number of frames to capture and to process before timing out
frame_count = 30
# The number of seconds to search before timing out
timout = 4
# The /dev/videoX id to capture frames from
# In my case, video0 is the normal camera and video1 is the IR version
# Should be set automatically by the installer
device_id = 1
# Scale down the video feed to this maximum height
# Speeds up face recognition but can make it less precise
max_height = 320
[debug]
# Show a short but detailed diagnostic report in console
end_report = false

235
installer.py Normal file
View file

@ -0,0 +1,235 @@
# Installation script to install howdy
# Runs completely independent of the others
# Import required modules
import subprocess
import time
import sys
import os
import re
import signal
import fileinput
import urllib.parse
def log(text):
"""Print a nicely formatted line to stdout"""
print("\n>>> \033[32m" + text + "\033[0m\n")
def handleStatus(status):
"""Abort if a command fails"""
if (status != 0):
print("\033[31mError while running last command\033[0m")
sys.exit()
# 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", "libpam-python", "fswebcam", "libopencv-dev", "python-opencv"]))
log("Starting camera check")
# Get all devices
devices = os.listdir("/dev")
# The picked video device id
picked = False
# 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")
# 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)
# 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)
# 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]: ")
# 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")
# Abort if no camera was picked
if (picked == False):
print("\033[31mNo suitable IR camera found\033[0m")
sys.exit()
log("Cloning dlib")
# Clone the git to /tmp
handleStatus(subprocess.call(["git", "clone", "https://github.com/davisking/dlib.git", "/tmp/dlib_clone"]))
log("Building dlib")
# Start the build without GPU
handleStatus(subprocess.call(["cd /tmp/dlib_clone/; python3 setup.py install --yes USE_AVX_INSTRUCTIONS --no DLIB_USE_CUDA"], shell=True))
log("Cleaning up dlib")
# Remove the no longer needed git clone
handleStatus(subprocess.call(["rm", "-rf", "/tmp/dlib_clone"]))
log("Installing face_recognition")
# Install face_recognition though pip
handleStatus(subprocess.call(["pip3", "install", "face_recognition"]))
log("Cloning howdy")
# Make sure /lib/security exists
if not os.path.exists("/lib/security"):
os.makedirs("/lib/security")
# Clone howdy into it
handleStatus(subprocess.call(["git", "clone", "https://github.com/Boltgolt/howdy.git", "/lib/security/howdy"]))
# Manually change the camera id to the one picked
for line in fileinput.input(["/lib/security/howdy/config.ini"], inplace = 1):
print(line.replace("device_id = 1", "device_id = " + picked), end="")
# Secure the howdy folder
handleStatus(subprocess.call(["chmod 600 -R /lib/security/howdy/"], shell=True))
# Make the CLI executable as howdy
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)
log("Adding howdy as PAM module")
# Will be filled with the actual output lines
outlines = []
# Will be fillled with lines that contain coloring
printlines = []
# Track if the new lines have been insterted yet
inserted = False
# Open the PAM config file
with open("/etc/pam.d/common-auth") as fp:
# Read the first line
line = fp.readline()
while line:
# Add the line to the output directly, we're not deleting anything
outlines.append(line)
# Print the comments in gray and don't insert into comments
if line[:1] == "#":
printlines.append("\033[37m" + line + "\033[0m")
else:
printlines.append(line)
# If it's not a comment and we haven't inserted yet
if not inserted:
# Set both the comment and the linking line
line_comment = "# Howdy IR face recognition\n"
line_link = "auth sufficient pam_python.so /lib/security/howdy/pam.py\n\n"
# Add them to the output without any markup
outlines.append(line_comment)
outlines.append(line_link)
# Make the print orange to make it clear what's being added
printlines.append("\033[33m" + line_comment + "\033[0m")
printlines.append("\033[33m" + line_link + "\033[0m")
# Mark as inserted
inserted = True
# Go to the next line
line = fp.readline()
# Print a file Header
print("\033[33m" + ">>> START OF /etc/pam.d/common-auth" + "\033[0m")
# Loop though all printing lines and use the enters from the file
for line in printlines:
print(line, end="")
# 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]: ")
# Abort the whole thing if it's not
if (ans.lower() != "y"):
print("Inerpeting as a \"NO\", aborting")
sys.exit()
print("Adding lines to PAM\n")
# Write to PAM
common_auth = open("/etc/pam.d/common-auth", "w")
common_auth.write("".join(outlines))
common_auth.close()
# From here onwards the installation is complete
# We want to gather more information about the types or IR camera's
# used though, and the following lines are data collection
# List all video devices
diag_out = "Video devices [IR=" + picked + "]\n"
diag_out += "```\n"
diag_out += subprocess.check_output(['ls /dev/ | grep video'], shell=True).decode("utf-8")
diag_out += "```\n"
# Get some info from the USB kernel listings
diag_out += "Lsusb output\n"
diag_out += "```\n"
diag_out += subprocess.check_output(['lsusb -vvvv | grep -i "Camera\|iFunction"'], shell=True).decode("utf-8")
diag_out += "```\n"
# Get camera information from video4linux
diag_out += "Udevadm\n"
diag_out += "```\n"
diag_out += subprocess.check_output(['udevadm info -r --query=all -n /dev/video' + picked + ' | grep -i "ID_BUS\|ID_MODEL_ID\|ID_VENDOR_ID\|ID_V4L_PRODUCT\|ID_MODEL"'], shell=True).decode("utf-8")
diag_out += "```"
# Print it all as a clickable link to a new github issue
print("https://github.com/Boltgolt/howdy-reports/issues/new?title=Post-installation%20camera%20information&body=" + urllib.parse.quote_plus(diag_out) + "\n")
# Let the user know what to do with the link
print("Installation complete.")
print("If you want to help the development, please use the link above to post some camera-related information to github")
# Remove the installer if it was downloaded to /tmp
if os.path.exists("/tmp/howdy_install.py"):
os.remove("/tmp/howdy_install.py")

6
pam.py
View file

@ -1,4 +1,4 @@
# PAM interface in python, launches compair.py
# PAM interface in python, launches compare.py
# Import required modules
import subprocess
@ -15,8 +15,8 @@ config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
def doAuth(pamh):
"""Start authentication in a seperate process"""
# Run compair as python3 subprocess to circumvent python version and import issues
status = subprocess.call(["python3", os.path.dirname(os.path.abspath(__file__)) + "/compair.py", pamh.get_user()])
# 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()])
# Status 10 means we couldn't find any face models
if status == 10:

36
uninstall.py Normal file
View file

@ -0,0 +1,36 @@
# Completely remove howdy from the system
# Import required modules
import subprocess
import sys
import os
# Check if we're running as root
user = os.getenv("SUDO_USER")
if user is None:
print("Please run the uninstaller as a sudo user")
sys.exit()
# Double check with the user for the last time
print("This will remove Howdy and all models generated with it")
ans = input("Do you want to continue? [y/N]: ")
# Abort if they don't say yes
if (ans.lower() != "y"):
sys.exit()
# Remove files and symlinks
subprocess.call(["rm -rf /lib/security/howdy/"], shell=True)
subprocess.call(["rm /usr/bin/howdy"], shell=True)
subprocess.call(["rm /etc/bash_completion.d/howdy"], shell=True)
# Remove face_recognition and dlib
subprocess.call(["pip3 uninstall face_recognition dlib -y"], shell=True)
# Print a tearbending message
print("""
Howdy has been uninstalled :'(
There are still lines in /etc/pam.d/common-auth that can't be removed automatically
Run "nano /etc/pam.d/common-auth" to remove them by hand\
""")

View file

@ -1,25 +0,0 @@
# Useful support functions
def print_menu(encodings):
"""Show a menu asking the user what he wants to do"""
if len(encodings) == 3:
print("There is 1 existing face model for this user")
else:
print("There are " + str(int(len(encodings) / 3)) + " existing face models for this user")
print("What do you want to do?\n")
print("1: Add additional face model")
print("2: Overwrite older model(s)")
print("0: Exit")
com = input("Option: ")
if com == "1":
return encodings
elif com == "2":
return []
elif com == "0":
sys.exit()
else:
print("Invalid option '" + com + "'\n")
return print_menu(encodings)