Re: git: 93eaa5412bfc - main - security/howdy: Face recognition based authentication provider.

From: Daniel Engberg <diizzy_at_FreeBSD.org>
Date: Mon, 03 Apr 2023 15:07:10 UTC
Hi,

This seems to be incorrect versioning looking at 
https://github.com/boltgolt/howdy/tags ?
Please follow Porters Handbook in such cases
https://docs.freebsd.org/en/books/porters-handbook/book/#makefile-master_sites-github

Best regards,
Daniel

On 2023-04-03 16:15, Gleb Popov wrote:
> The branch main has been updated by arrowd:
> 
> URL:
> https://cgit.FreeBSD.org/ports/commit/?id=93eaa5412bfca8daf12b7c81110c81b83211f54e
> 
> commit 93eaa5412bfca8daf12b7c81110c81b83211f54e
> Author:     Alexey Yushkin <636808@mail.ru>
> AuthorDate: 2023-04-03 14:06:36 +0000
> Commit:     Gleb Popov <arrowd@FreeBSD.org>
> CommitDate: 2023-04-03 14:15:32 +0000
> 
>     security/howdy: Face recognition based authentication provider.
> 
>     Co-authored-by: Alexey Donskov <voxnod@gmail.com>
>     Co-authored-by: Gleb Popov <arrowd@FreeBSD.org>
> 
>     Sponsored by:   Serenity Cybersecurity, LLC
> ---
>  security/Makefile                |   1 +
>  security/howdy/Makefile          |  52 +++++
>  security/howdy/distinfo          |   3 +
>  security/howdy/files/patch-paths | 429 
> +++++++++++++++++++++++++++++++++++++++
>  security/howdy/pkg-descr         |   3 +
>  security/howdy/pkg-plist         |  27 +++
>  6 files changed, 515 insertions(+)
> 
> diff --git a/security/Makefile b/security/Makefile
> index 034e957031c0..2f61c05f25c7 100644
> --- a/security/Makefile
> +++ b/security/Makefile
> @@ -218,6 +218,7 @@
>      SUBDIR += honeytrap
>      SUBDIR += honggfuzz
>      SUBDIR += horcrux
> +    SUBDIR += howdy
>      SUBDIR += hpenc
>      SUBDIR += hs-cryptol
>      SUBDIR += hydra
> diff --git a/security/howdy/Makefile b/security/howdy/Makefile
> new file mode 100644
> index 000000000000..675fca7c5aca
> --- /dev/null
> +++ b/security/howdy/Makefile
> @@ -0,0 +1,52 @@
> +PORTNAME=	howdy
> +PORTVERSION=	3.0.0.b
> +CATEGORIES=	security
> +
> +MAINTAINER=	arrowd@FreeBSD.org
> +COMMENT=	Windows Hello(TM) style authentication provider
> +WWW=		https://github.com/boltgolt/howdy
> +
> +LICENSE=	MIT
> +LICENSE_FILE=	${WRKSRC}/LICENSE
> +
> +RUN_DEPENDS=	opencv>0:graphics/opencv \
> +		${PYTHON_PKGNAMEPREFIX}python-pam>0:security/py-python-pam@${PY_FLAVOR} 
> \
> +		${PYTHON_PKGNAMEPREFIX}dlib>0:science/py-dlib@${PY_FLAVOR} \
> +		${PYTHON_PKGNAMEPREFIX}numpy>0:math/py-numpy@${PY_FLAVOR}
> +
> +USES=		python:run shebangfix
> +
> +USE_GITHUB=	yes
> +GH_ACCOUNT=	boltgolt
> +GH_PROJECT=	howdy
> +GH_TAGNAME=	30728a6d3634479c24ffd4e094c34a30bbb43058
> +
> +SHEBANG_GLOB=	*.py
> +
> +NO_BUILD=	yes
> +NO_ARCH=	yes
> +STRIP=
> +
> +post-patch:
> +	${ECHO_CMD} 'config_dir = "${ETCDIR}"' >> 
> ${WRKSRC}/howdy/src/paths.py
> +	${ECHO_CMD} 'dlib_data_dir = "${LOCALBASE}/share/dlib-data/"' >>
> ${WRKSRC}/howdy/src/paths.py
> +	${ECHO_CMD} 'user_models_dir = "/var/db/howdy/models/"' >>
> ${WRKSRC}/howdy/src/paths.py
> +
> +do-install:
> +	${MKDIR} ${STAGEDIR}${PREFIX}/libexec/howdy
> +	cd ${WRKSRC}/howdy/src/ && \
> +		${COPYTREE_SHARE} . ${STAGEDIR}${PREFIX}/libexec/howdy
> +	${MKDIR} ${STAGEDIR}${ETCDIR}
> +	${MV} ${STAGEDIR}${PREFIX}/libexec/howdy/config.ini
> ${STAGEDIR}${ETCDIR}/config.ini.sample
> +	${LN} -s ../libexec/howdy/cli.py ${STAGEDIR}${PREFIX}/bin/howdy
> +	${INSTALL_PROGRAM} ${WRKSRC}/howdy/src/cli.py
> ${STAGEDIR}${PREFIX}/libexec/howdy
> +
> +	${MKDIR} ${STAGEDIR}${PREFIX}/share/bash-completion/completions
> +	${INSTALL_DATA}  ${WRKSRC}/howdy/src/autocomplete/howdy
> ${STAGEDIR}${PREFIX}/share/bash-completion/completions/
> +
> +	${RM} -r ${STAGEDIR}${PREFIX}/libexec/howdy/autocomplete
> +	${RM} -r ${STAGEDIR}${PREFIX}/libexec/howdy/dlib-data
> +	${RM} -r ${STAGEDIR}${PREFIX}/libexec/howdy/pam
> +	${RM} -r ${STAGEDIR}${PREFIX}/libexec/howdy/pam-config
> +
> +.include <bsd.port.mk>
> diff --git a/security/howdy/distinfo b/security/howdy/distinfo
> new file mode 100644
> index 000000000000..8b91fb02664b
> --- /dev/null
> +++ b/security/howdy/distinfo
> @@ -0,0 +1,3 @@
> +TIMESTAMP = 1680193772
> +SHA256
> (boltgolt-howdy-3.0.0.b-30728a6d3634479c24ffd4e094c34a30bbb43058_GH0.tar.gz)
> = 76078fbfb48a186678476cf9844987c97793a71135f1e612d761350c6b27fa42
> +SIZE
> (boltgolt-howdy-3.0.0.b-30728a6d3634479c24ffd4e094c34a30bbb43058_GH0.tar.gz)
> = 130651
> diff --git a/security/howdy/files/patch-paths 
> b/security/howdy/files/patch-paths
> new file mode 100644
> index 000000000000..57e375f2dea5
> --- /dev/null
> +++ b/security/howdy/files/patch-paths
> @@ -0,0 +1,429 @@
> +From 30034c66d72e8e15e2ad5db68d1a5940df2568b6 Mon Sep 17 00:00:00 2001
> +From: Gleb Popov <6yearold@gmail.com>
> +Date: Thu, 30 Mar 2023 21:55:11 +0300
> +Subject: [PATCH] Put all path variables into a separate module.
> +
> +This makes it easier for downstream packagers to customize where howdy 
> installs
> +its files.
> +---
> + howdy/src/cli/add.py     | 22 ++++++++++------------
> + howdy/src/cli/clear.py   |  9 ++++-----
> + howdy/src/cli/config.py  |  3 ++-
> + howdy/src/cli/disable.py |  3 ++-
> + howdy/src/cli/list.py    |  7 +++----
> + howdy/src/cli/remove.py  |  9 ++++-----
> + howdy/src/cli/set.py     |  3 ++-
> + howdy/src/cli/snap.py    |  5 ++---
> + howdy/src/cli/test.py    | 11 ++++++-----
> + howdy/src/compare.py     | 18 ++++++++----------
> + howdy/src/paths.py       | 12 ++++++++++++
> + howdy/src/snapshot.py    | 15 ++++++---------
> + 12 files changed, 61 insertions(+), 56 deletions(-)
> + create mode 100644 howdy/src/paths.py
> +
> +diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py
> +index 7a6d9eca..5a63bdfe 100644
> +--- a/howdy/src/cli/add.py
> ++++ ./howdy/src/cli/add.py
> +@@ -8,6 +8,7 @@
> + import configparser
> + import builtins
> + import numpy as np
> ++import paths
> +
> + from recorders.video_capture import VideoCapture
> + from i18n import _
> +@@ -26,39 +27,36 @@
> + # OpenCV needs to be imported after dlib
> + import cv2
> +
> +-# Define the absolute path to the config directory
> +-config_path = "/etc/howdy"
> +-
> + # Test if at lest 1 of the data files is there and abort if it's not
> +-if not os.path.isfile(config_path +
> "/dlib-data/shape_predictor_5_face_landmarks.dat"):
> ++if not os.path.isfile(paths.dlib_data_dir +
> "shape_predictor_5_face_landmarks.dat"):
> + 	print(_("Data files have not been downloaded, please run the
> following commands:"))
> +-	print("\n\tcd " + config_path + "/dlib-data")
> ++	print("\n\tcd " + paths.dlib_data_dir)
> + 	print("\tsudo ./install.sh\n")
> + 	sys.exit(1)
> +
> + # Read config from disk
> + config = configparser.ConfigParser()
> +-config.read(config_path + "/config.ini")
> ++config.read(paths.config_dir + "/config.ini")
> +
> + use_cnn = config.getboolean("core", "use_cnn", fallback=False)
> + if use_cnn:
> +-	face_detector = dlib.cnn_face_detection_model_v1(config_path +
> "/dlib-data/mmod_human_face_detector.dat")
> ++	face_detector =
> dlib.cnn_face_detection_model_v1(paths.dlib_data_dir +
> "mmod_human_face_detector.dat")
> + else:
> + 	face_detector = dlib.get_frontal_face_detector()
> +
> +-pose_predictor = dlib.shape_predictor(config_path +
> "/dlib-data/shape_predictor_5_face_landmarks.dat")
> +-face_encoder = dlib.face_recognition_model_v1(config_path +
> "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
> ++pose_predictor = dlib.shape_predictor(paths.dlib_data_dir +
> "shape_predictor_5_face_landmarks.dat")
> ++face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir +
> "dlib_face_recognition_resnet_model_v1.dat")
> +
> + user = builtins.howdy_user
> + # The permanent file to store the encoded model in
> +-enc_file = config_path + "/models/" + user + ".dat"
> ++enc_file = paths.user_models_dir + user + ".dat"
> + # Known encodings
> + encodings = []
> +
> + # Make the ./models folder if it doesn't already exist
> +-if not os.path.exists(config_path + "/models"):
> ++if not os.path.exists(paths.user_models_dir):
> + 	print(_("No face model folder found, creating one"))
> +-	os.makedirs(config_path + "/models")
> ++	os.makedirs(paths.user_models_dir)
> +
> + # To try read a premade encodings file if it exists
> + try:
> +diff --git a/howdy/src/cli/clear.py b/howdy/src/cli/clear.py
> +index 6fa5f3ef..aa43e152 100644
> +--- a/howdy/src/cli/clear.py
> ++++ ./howdy/src/cli/clear.py
> +@@ -4,21 +4,20 @@
> + import os
> + import sys
> + import builtins
> ++import paths
> +
> + from i18n import _
> +
> +-# Get the full path to this file
> +-path = "/etc/howdy/models"
> + # Get the passed user
> + user = builtins.howdy_user
> +
> + # Check if the models folder is there
> +-if not os.path.exists(path):
> ++if not os.path.exists(paths.user_models_dir):
> + 	print(_("No models created yet, can't clear them if they don't 
> exist"))
> + 	sys.exit(1)
> +
> + # Check if the user has a models file to delete
> +-if not os.path.isfile(path + "/" + user + ".dat"):
> ++if not os.path.isfile(paths.user_models_dir + user + ".dat"):
> + 	print(_("{} has no models or they have been cleared 
> already").format(user))
> + 	sys.exit(1)
> +
> +@@ -34,5 +33,5 @@
> + 		sys.exit(1)
> +
> + # Delete otherwise
> +-os.remove(path + "/" + user + ".dat")
> ++os.remove(paths.user_models_dir + user + ".dat")
> + print(_("\nModels cleared"))
> +diff --git a/howdy/src/cli/config.py b/howdy/src/cli/config.py
> +index 71064839..04c51798 100644
> +--- a/howdy/src/cli/config.py
> ++++ ./howdy/src/cli/config.py
> +@@ -3,6 +3,7 @@
> + # Import required modules
> + import os
> + import subprocess
> ++import paths
> +
> + from i18n import _
> +
> +@@ -19,4 +20,4 @@
> + 	editor = "/etc/alternatives/editor"
> +
> + # Open the editor as a subprocess and fork it
> +-subprocess.call([editor, "/etc/howdy/config.ini"])
> ++subprocess.call([editor, paths.config_dir + "config.ini"])
> +diff --git a/howdy/src/cli/disable.py b/howdy/src/cli/disable.py
> +index be78c97f..1f655412 100644
> +--- a/howdy/src/cli/disable.py
> ++++ ./howdy/src/cli/disable.py
> +@@ -6,11 +6,12 @@
> + import builtins
> + import fileinput
> + import configparser
> ++import paths
> +
> + from i18n import _
> +
> + # Get the absolute filepath
> +-config_path = os.path.dirname("/etc/howdy") + "/config.ini"
> ++config_path = os.path.dirname(paths.config_dir) + "/config.ini"
> +
> + # Read config from disk
> + config = configparser.ConfigParser()
> +diff --git a/howdy/src/cli/list.py b/howdy/src/cli/list.py
> +index 3532e9f8..7539837d 100644
> +--- a/howdy/src/cli/list.py
> ++++ ./howdy/src/cli/list.py
> +@@ -6,21 +6,20 @@
> + import json
> + import time
> + import builtins
> ++import paths
> +
> + from i18n import _
> +
> +-# Get the absolute path and the username
> +-path = "/etc/howdy"
> + user = builtins.howdy_user
> +
> + # Check if the models file has been created yet
> +-if not os.path.exists(path + "/models"):
> ++if not os.path.exists(paths.user_models_dir):
> + 	print(_("Face models have not been initialized yet, please run:"))
> + 	print("\n\tsudo howdy -U " + user + " add\n")
> + 	sys.exit(1)
> +
> + # Path to the models file
> +-enc_file = path + "/models/" + user + ".dat"
> ++enc_file = paths.user_models_dir + user + ".dat"
> +
> + # Try to load the models file and abort if the user does not have it 
> yet
> + try:
> +diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py
> +index 6321e0b5..37894422 100644
> +--- a/howdy/src/cli/remove.py
> ++++ ./howdy/src/cli/remove.py
> +@@ -5,11 +5,10 @@
> + import os
> + import json
> + import builtins
> ++import paths
> +
> + from i18n import _
> +
> +-# Get the absolute path and the username
> +-path = "/etc/howdy"
> + user = builtins.howdy_user
> +
> + # Check if enough arguments have been passed
> +@@ -22,13 +21,13 @@
> + 	sys.exit(1)
> +
> + # Check if the models file has been created yet
> +-if not os.path.exists(path + "/models"):
> ++if not os.path.exists(paths.user_models_dir):
> + 	print(_("Face models have not been initialized yet, please run:"))
> + 	print("\n\thowdy add\n")
> + 	sys.exit(1)
> +
> + # Path to the models file
> +-enc_file = path + "/models/" + user + ".dat"
> ++enc_file = paths.user_models_dir + user + ".dat"
> +
> + # Try to load the models file and abort if the user does not have it 
> yet
> + try:
> +@@ -72,7 +71,7 @@
> +
> + # Remove the entire file if this encoding is the only one
> + if len(encodings) == 1:
> +-	os.remove(path + "/models/" + user + ".dat")
> ++	os.remove(paths.user_models_dir + user + ".dat")
> + 	print(_("Removed last model, howdy disabled for user"))
> + else:
> + 	# A place holder to contain the encodings that will remain
> +diff --git a/howdy/src/cli/set.py b/howdy/src/cli/set.py
> +index 14d15c20..efbbee5b 100644
> +--- a/howdy/src/cli/set.py
> ++++ ./howdy/src/cli/set.py
> +@@ -5,11 +5,12 @@
> + import os
> + import builtins
> + import fileinput
> ++import paths
> +
> + from i18n import _
> +
> + # Get the absolute filepath
> +-config_path = os.path.dirname("/etc/howdy") + "/config.ini"
> ++config_path = os.path.dirname(paths.config_dir) + "/config.ini"
> +
> + # Check if enough arguments have been passed
> + if len(builtins.howdy_args.arguments) < 2:
> +diff --git a/howdy/src/cli/snap.py b/howdy/src/cli/snap.py
> +index cbcae501..2c625d3b 100644
> +--- a/howdy/src/cli/snap.py
> ++++ ./howdy/src/cli/snap.py
> +@@ -5,15 +5,14 @@
> + import configparser
> + import datetime
> + import snapshot
> ++import paths
> + from recorders.video_capture import VideoCapture
> +
> + from i18n import _
> +
> +-path = "/etc/howdy"
> +-
> + # Read the config
> + config = configparser.ConfigParser()
> +-config.read(path + "/config.ini")
> ++config.read(paths.config_dir + "config.ini")
> +
> + # Start video capture
> + video_capture = VideoCapture(config)
> +diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py
> +index 3a6e4d19..563be19b 100644
> +--- a/howdy/src/cli/test.py
> ++++ ./howdy/src/cli/test.py
> +@@ -10,6 +10,7 @@
> + import dlib
> + import cv2
> + import numpy as np
> ++import paths
> +
> + from i18n import _
> + from recorders.video_capture import VideoCapture
> +@@ -19,7 +20,7 @@
> +
> + # Read config from disk
> + config = configparser.ConfigParser()
> +-config.read(path + "/config.ini")
> ++config.read(paths.config_dir + "config.ini")
> +
> + if config.get("video", "recording_plugin", fallback="opencv") != 
> "opencv":
> + 	print(_("Howdy has been configured to use a recorder which doesn't
> support the test command yet, aborting"))
> +@@ -59,20 +60,20 @@ def print_text(line_number, text):
> +
> + if use_cnn:
> + 	face_detector = dlib.cnn_face_detection_model_v1(
> +-		path + "/dlib-data/mmod_human_face_detector.dat"
> ++		paths.dlib_data_dir + "mmod_human_face_detector.dat"
> + 	)
> + else:
> + 	face_detector = dlib.get_frontal_face_detector()
> +
> +-pose_predictor = dlib.shape_predictor(path +
> "/dlib-data/shape_predictor_5_face_landmarks.dat")
> +-face_encoder = dlib.face_recognition_model_v1(path +
> "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
> ++pose_predictor = dlib.shape_predictor(paths.dlib_data_dir +
> "shape_predictor_5_face_landmarks.dat")
> ++face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir +
> "dlib_face_recognition_resnet_model_v1.dat")
> +
> + encodings = []
> + models = None
> +
> + try:
> + 	user = builtins.howdy_user
> +-	models = json.load(open(path + "/models/" + user + ".dat"))
> ++	models = json.load(open(paths.user_models_dir + user + ".dat"))
> +
> + 	for model in models:
> + 		encodings += model["data"]
> +diff --git a/howdy/src/compare.py b/howdy/src/compare.py
> +index 99f5285b..f81fe386 100644
> +--- a/howdy/src/compare.py
> ++++ ./howdy/src/compare.py
> +@@ -23,6 +23,7 @@
> + import snapshot
> + import numpy as np
> + import _thread as thread
> ++import paths
> +
> + # Allow imports from the local howdy folder
> + sys.path.append('/lib/security/howdy')
> +@@ -48,22 +49,22 @@ def init_detector(lock):
> + 	global face_detector, pose_predictor, face_encoder
> +
> + 	# Test if at lest 1 of the data files is there and abort if it's not
> +-	if not os.path.isfile(PATH +
> "/dlib-data/shape_predictor_5_face_landmarks.dat"):
> ++	if not os.path.isfile(paths.dlib_data_dir +
> "shape_predictor_5_face_landmarks.dat"):
> + 		print(_("Data files have not been downloaded, please run the
> following commands:"))
> +-		print("\n\tcd " + PATH + "/dlib-data")
> ++		print("\n\tcd " + paths.dlib_data_dir)
> + 		print("\tsudo ./install.sh\n")
> + 		lock.release()
> + 		exit(1)
> +
> + 	# Use the CNN detector if enabled
> + 	if use_cnn:
> +-		face_detector = dlib.cnn_face_detection_model_v1(PATH +
> "/dlib-data/mmod_human_face_detector.dat")
> ++		face_detector =
> dlib.cnn_face_detection_model_v1(paths.dlib_data_dir +
> "mmod_human_face_detector.dat")
> + 	else:
> + 		face_detector = dlib.get_frontal_face_detector()
> +
> + 	# Start the others regardless
> +-	pose_predictor = dlib.shape_predictor(PATH +
> "/dlib-data/shape_predictor_5_face_landmarks.dat")
> +-	face_encoder = dlib.face_recognition_model_v1(PATH +
> "/dlib-data/dlib_face_recognition_resnet_model_v1.dat")
> ++	pose_predictor = dlib.shape_predictor(paths.dlib_data_dir +
> "shape_predictor_5_face_landmarks.dat")
> ++	face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir +
> "dlib_face_recognition_resnet_model_v1.dat")
> +
> + 	# Note the time it took to initialize detectors
> + 	timings["ll"] = time.time() - timings["ll"]
> +@@ -103,9 +104,6 @@ def send_to_ui(type, message):
> + if len(sys.argv) < 2:
> + 	exit(12)
> +
> +-# Get the absolute path to the config directory
> +-PATH = "/etc/howdy"
> +-
> + # The username of the user being authenticated
> + user = sys.argv[1]
> + # The model file contents
> +@@ -129,7 +127,7 @@ def send_to_ui(type, message):
> +
> + # Try to load the face model from the models folder
> + try:
> +-	models = json.load(open(PATH + "/models/" + user + ".dat"))
> ++	models = json.load(open(paths.user_models_dir + user + ".dat"))
> +
> + 	for model in models:
> + 		encodings += model["data"]
> +@@ -142,7 +140,7 @@ def send_to_ui(type, message):
> +
> + # Read config from disk
> + config = configparser.ConfigParser()
> +-config.read(PATH + "/config.ini")
> ++config.read(paths.config_dir + "config.ini")
> +
> + # Get all config values needed
> + use_cnn = config.getboolean("core", "use_cnn", fallback=False)
> +diff --git a/howdy/src/paths.py b/howdy/src/paths.py
> +new file mode 100644
> +index 00000000..22825405
> +--- /dev/null
> ++++ ./howdy/src/paths.py
> +@@ -0,0 +1,12 @@
> ++
> ++# Define the absolute path to the config directory
> ++config_dir = "/etc/howdy/"
> ++
> ++# Define the absolute path to the DLib models data directory
> ++dlib_data_dir = config_dir + "/dlib-data/"
> ++
> ++# Define the absolute path to the Howdy user models directory
> ++user_models_dir = config_dir + "/models/"
> ++
> ++# Define path to any howdy logs
> ++log_path = "/var/log/howdy"
> +diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py
> +index 324b5789..9f2f563c 100644
> +--- a/howdy/src/snapshot.py
> ++++ ./howdy/src/snapshot.py
> +@@ -49,19 +49,16 @@ def generate(frames, text_lines):
> +
> + 		line_number += 1
> +
> +-	# Define path to any howdy logs
> +-	log_path = "/var/log/howdy"
> +-
> + 	# Made sure a snapshot folder exist
> +-	if not os.path.exists(log_path):
> +-		os.makedirs(log_path)
> +-	if not os.path.exists(log_path + "/snapshots"):
> +-		os.makedirs(log_path + "/snapshots")
> ++	if not os.path.exists(paths.log_path):
> ++		os.makedirs(paths.log_path)
> ++	if not os.path.exists(paths.log_path + "/snapshots"):
> ++		os.makedirs(paths.log_path + "/snapshots")
> +
> + 	# Generate a filename based on the current time
> + 	filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg")
> + 	# Write the image to that file
> +-	cv2.imwrite(log_path + "/snapshots/" + filename, snap)
> ++	cv2.imwrite(paths.log_path + "/snapshots/" + filename, snap)
> +
> + 	# Return the saved file location
> +-	return log_path + "/snapshots/" + filename
> ++	return paths.log_path + "/snapshots/" + filename
> diff --git a/security/howdy/pkg-descr b/security/howdy/pkg-descr
> new file mode 100644
> index 000000000000..1fd314d7c55e
> --- /dev/null
> +++ b/security/howdy/pkg-descr
> @@ -0,0 +1,3 @@
> +Howdy is an authentication tool that allows you to unlock your desktop
> +session using your webcam. It uses facial recognition to authenticate 
> and
> +unlock the session.
> diff --git a/security/howdy/pkg-plist b/security/howdy/pkg-plist
> new file mode 100644
> index 000000000000..5353cffca512
> --- /dev/null
> +++ b/security/howdy/pkg-plist
> @@ -0,0 +1,27 @@
> +bin/howdy
> +@sample %%ETCDIR%%/config.ini.sample
> +libexec/howdy/cli.py
> +libexec/howdy/cli/__init__.py
> +libexec/howdy/cli/add.py
> +libexec/howdy/cli/clear.py
> +libexec/howdy/cli/config.py
> +libexec/howdy/cli/disable.py
> +libexec/howdy/cli/list.py
> +libexec/howdy/cli/remove.py
> +libexec/howdy/cli/set.py
> +libexec/howdy/cli/snap.py
> +libexec/howdy/cli/test.py
> +libexec/howdy/compare.py
> +libexec/howdy/i18n.py
> +libexec/howdy/logo.png
> +libexec/howdy/paths.py
> +libexec/howdy/recorders/__init__.py
> +libexec/howdy/recorders/ffmpeg_reader.py
> +libexec/howdy/recorders/pyv4l2_reader.py
> +libexec/howdy/recorders/v4l2.py
> +libexec/howdy/recorders/video_capture.py
> +libexec/howdy/rubberstamps/__init__.py
> +libexec/howdy/rubberstamps/hotkey.py
> +libexec/howdy/rubberstamps/nod.py
> +libexec/howdy/snapshot.py
> +share/bash-completion/completions/howdy