Skip to content

Commit 1d4308c

Browse files
authored
Merge pull request #3 from Boltgolt/dev
Adding new installer and CLI
2 parents 4b37c5e + f084411 commit 1d4308c

16 files changed

Lines changed: 722 additions & 142 deletions

README.md

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,41 @@
11
# Howdy for Ubuntu
22

3-
Windows Hello™ style authentication for Ubuntu. Use your build in IR emitters and camera in combination with face recognition to prove who you are.
3+
Windows Hello™ style authentication for Ubuntu. Use your built-in IR emitters and camera in combination with face recognition to prove who you are.
4+
5+
Using the central authentication system in Linux (PAM), this works everywhere you would otherwise need your password: Login, lock screen, sudo, su, etc.
46

57
### Installation
68

7-
First we need to install pam-python, fswebcam and OpenCV from the Ubuntu repositories:
9+
Run the installer by pasting (`ctrl+shift+V`) the following command into the terminal:
810

911
```
10-
sudo apt install libpam-python fswebcam libopencv-dev python-opencv
12+
wget -O /tmp/howdy_install.py https://raw.githubusercontent.com/Boltgolt/howdy/master/installer.py && sudo python3 /tmp/howdy_install.py
1113
```
1214

13-
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).
14-
15-
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.
15+
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.
1616

17-
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:
17+
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.
1818

19-
```
20-
python3 learn.py
21-
```
19+
**Note:** The build of dlib can hang on 100% for over a minute, give it time.
2220

23-
The script should guide you through the process.
21+
### Command line
2422

25-
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:
23+
The installer adds a `howdy` command to manage face models for the current user. Use `howdy help` to list the available options.
2624

27-
```
28-
auth sufficient pam_python.so /path/to/pam.py
29-
```
25+
### Troubleshooting
3026

31-
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.
27+
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.
3228

33-
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.
34-
35-
### Troubleshooting
29+
If you encounter an error that hasn't been reported yet, don't be afraid to open a new issue.
3630

37-
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.
31+
### Uninstalling
3832

33+
There is an uninstaller available, run `sudo python3 /lib/security/howdy/uninstall.py` to remove Howdy from your system.
3934

4035
### A note on security
4136

4237
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.
4338

44-
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.
39+
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.
4540

46-
DO NOT USE THIS SCRIPT AS THE SOLE AUTHENTICATION METHOD FOR YOUR SYSTEM.
41+
DO NOT USE HOWDY AS THE SOLE AUTHENTICATION METHOD FOR YOUR SYSTEM.

autocomplete.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Autocomplete file run in bash
2+
# Will sugest arguments on tab
3+
4+
_howdy() {
5+
local cur prev opts
6+
COMPREPLY=()
7+
cur="${COMP_WORDS[COMP_CWORD]}"
8+
prev="${COMP_WORDS[COMP_CWORD-1]}"
9+
opts="help list add remove clear"
10+
11+
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
12+
return 0
13+
}
14+
15+
complete -F _howdy howdy

cli.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env python3
2+
3+
# CLI directly called by running the howdy command
4+
5+
# Import required modules
6+
import sys
7+
import os
8+
9+
# Check if the minimum of 3 arugemnts has been met and print help otherwise
10+
if (len(sys.argv) < 3):
11+
print("Howdy IR face recognition help")
12+
import cli.help
13+
sys.exit()
14+
15+
# The command given
16+
cmd = sys.argv[2]
17+
18+
# Requre sudo for comamnds that need root rights to read the model files
19+
if cmd in ["list", "add", "remove", "clear"] and os.getenv("SUDO_USER") is None:
20+
print("Please run this command with sudo")
21+
sys.exit()
22+
23+
# Call the right files for the given command
24+
if cmd == "list":
25+
import cli.list
26+
elif cmd == "help":
27+
print("Howdy IR face recognition")
28+
import cli.help
29+
elif cmd == "add":
30+
import cli.add
31+
elif cmd == "remove":
32+
import cli.remove
33+
elif cmd == "clear":
34+
import cli.clear
35+
else:
36+
# If the comand is invalid, check if the user hasn't swapped the username and command
37+
if sys.argv[1] in ["list", "add", "remove", "clear", "help"]:
38+
print("Usage: howdy <user> <command>")
39+
else:
40+
print('Unknown command "' + cmd + '"')
41+
import cli.help

cli/__init__.py

Whitespace-only changes.

learn.py renamed to cli/add.py

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,35 @@
11
# Save the face of the user in encoded form
22

33
# Import required modules
4-
import face_recognition
54
import subprocess
65
import time
76
import os
87
import sys
98
import json
10-
11-
# Import config and extra functions
129
import configparser
13-
import utils
10+
11+
12+
# Try to import face_recognition and give a nice error if we can't
13+
# Add should be the first point where import issues show up
14+
try:
15+
import face_recognition
16+
except ImportError as err:
17+
print(err)
18+
19+
print("\nCan't import the face_recognition module, check the output of")
20+
print("pip3 show face_recognition")
21+
sys.exit()
22+
23+
# Get the absolute path to the current file
24+
path = os.path.dirname(os.path.abspath(__file__))
1425

1526
# Read config from disk
1627
config = configparser.ConfigParser()
17-
config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
28+
config.read(path + "/../config.ini")
1829

1930
def captureFrame(delay):
2031
"""Capture and encode 1 frame of video"""
21-
global encodings
32+
global insert_model
2233

2334
# Call fswebcam to save a frame to /tmp with a set delay
2435
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):
5465
for point in enc[0]:
5566
clean_enc.append(point)
5667

57-
encodings.append(clean_enc)
68+
insert_model["data"].append(clean_enc)
5869

5970
# The current user
60-
user = os.environ.get("USER")
71+
user = sys.argv[1]
6172
# The name of the tmp frame file to user
6273
tmp_file = "/tmp/howdy_" + user + ".jpg"
6374
# The permanent file to store the encoded model in
64-
enc_file = "./models/" + user + ".dat"
75+
enc_file = path + "/../models/" + user + ".dat"
6576
# Known encodings
6677
encodings = []
6778

6879
# Make the ./models folder if it doesn't already exist
69-
if not os.path.exists("models"):
80+
if not os.path.exists(path + "/../models"):
7081
print("No face model folder found, creating one")
71-
os.makedirs("models")
82+
os.makedirs(path + "/../models")
7283

7384
# To try read a premade encodings file if it exists
7485
try:
7586
encodings = json.load(open(enc_file))
7687
except FileNotFoundError:
77-
encodings = False
78-
79-
# If a file does exist, ask the user what needs to be done
80-
if encodings != False:
81-
encodings = utils.print_menu(encodings)
82-
else:
8388
encodings = []
8489

85-
print("\nLearning face for the user account " + user)
86-
print("Please look straight into the camera for 5 seconds")
90+
print("Adding face model for the user account " + user)
91+
92+
# Set the default label
93+
label = "Initial model"
94+
95+
# If models already exist, set that default label
96+
if len(encodings) > 0:
97+
label = "Model #" + str(len(encodings) + 1)
98+
99+
# Ask the user for a custom label
100+
label_in = input("Enter a label for this new model [" + label + "]: ")
101+
102+
# Set the custom label (if any) and limit it to 24 characters
103+
if label_in != "":
104+
label = label_in[:24]
105+
106+
# Prepare the metadata for insertion
107+
insert_model = {
108+
"time": int(time.time()),
109+
"label": label,
110+
"id": len(encodings),
111+
"data": []
112+
}
113+
114+
print("\nPlease look straight into the camera for 5 seconds")
87115

88116
# Give the user time to read
89117
time.sleep(2)
@@ -93,6 +121,9 @@ def captureFrame(delay):
93121
time.sleep(.3)
94122
captureFrame(delay)
95123

124+
# Insert full object into the list
125+
encodings.append(insert_model)
126+
96127
# Save the new encodings to disk
97128
with open(enc_file, "w") as datafile:
98129
json.dump(encodings, datafile)

cli/clear.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Clear all models by deleting the whole file
2+
3+
# Import required modules
4+
import os
5+
import sys
6+
7+
# Get the full path to this file
8+
path = os.path.dirname(os.path.abspath(__file__))
9+
# Get the passed user
10+
user = sys.argv[1]
11+
12+
# Check if the models folder is there
13+
if not os.path.exists(path + "/../models"):
14+
print("No models created yet, can't clear them if they don't exist")
15+
sys.exit()
16+
17+
# Check if the user has a models file to delete
18+
if not os.path.isfile(path + "/../models/" + user + ".dat"):
19+
print(user + " has no models or they have been cleared already")
20+
sys.exit()
21+
22+
# Double check with the user
23+
print("This will clear all models for " + user)
24+
ans = input("Do you want to continue [y/N]: ")
25+
26+
# Abort if they don't answer y or Y
27+
if (ans.lower() != "y"):
28+
print('\nInerpeting as a "NO"')
29+
sys.exit()
30+
31+
# Delete otherwise
32+
os.remove(path + "/../models/" + user + ".dat")
33+
print("\nModels cleared")

cli/help.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Prints a simple help page for the CLI
2+
3+
print("""
4+
Usage:
5+
howdy <user> <command> [argument]
6+
7+
Commands:
8+
help Show this help page
9+
list List all saved face models for the current user
10+
add Add a new face model for the current user
11+
remove [id] Remove a specific model
12+
clear Remove all face models for the current user
13+
14+
For support please visit
15+
https://github.com/Boltgolt/howdy\
16+
""")

cli/list.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# List all models for a user
2+
3+
# Import required modules
4+
import sys
5+
import os
6+
import json
7+
import time
8+
9+
# Get the absolute path and the username
10+
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
11+
user = sys.argv[1]
12+
13+
# Check if the models file has been created yet
14+
if not os.path.exists(path + "/models"):
15+
print("Face models have not been initialized yet, please run:")
16+
print("\n\thowdy " + user + " add\n")
17+
sys.exit(1)
18+
19+
# Path to the models file
20+
enc_file = path + "/models/" + user + ".dat"
21+
22+
# Try to load the models file and abort if the user does not have it yet
23+
try:
24+
encodings = json.load(open(enc_file))
25+
except FileNotFoundError:
26+
print("No face model known for the user " + user + ", please run:")
27+
print("\n\thowdy " + user + " add\n")
28+
sys.exit(1)
29+
30+
# Print a header
31+
print("Known face models for " + user + ":")
32+
print("\n\t\033[1;29mID Date Label\033[0m")
33+
34+
# Loop through all encodings and print info about them
35+
for enc in encodings:
36+
# Start with a tab and print the id
37+
print("\t" + str(enc["id"]), end="")
38+
# Print padding spaces after the id
39+
print((4 - len(str(enc["id"]))) * " ", end="")
40+
# Format the time as ISO in the local timezone
41+
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(enc["time"])), end="")
42+
# End with the label
43+
print(" " + enc["label"])
44+
45+
# Add a closing enter
46+
print()

0 commit comments

Comments
 (0)