pash/pash

176 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# pash - simple password manager.
pw_add() {
yn "Generate a password?"
case $REPLY in
[yY])
[[ $OSTYPE == linux* ]] &&
check_entropy
pw_gen
;;
*) read -rsp "Enter password: " password ;;
esac
[[ $password ]] ||
die "Failed to generate a password."
[[ $1 == */* ]] &&
{ mkdir -p "${1%/*}" || die "Couldn't create category '${1%/*}'.";}
"${gpg[0]}" -co "$1.gpg" <<< "$password"
}
pw_del() {
yn "Delete pass file '$1'?"
[[ $REPLY == [yY] ]] &&
rm "$1.gpg"
}
pw_show() {
read -r password < <("${gpg[0]}" -dq "$1.gpg")
}
pw_list() {
shopt -s globstar nullglob
for pwrd in **; do
[[ -d $pwrd ]] &&
dir=/
nest=${pwrd//[^\/]}
pwrd=${pwrd//[^[:print:]]/^[}
pwrd=${pwrd//.gpg}
if [[ -z $tree ]]; then
printf '%s\n' "$pwrd"
else
printf '%s\n' "${nest//\//│ }├─ ${pwrd##*/}${dir}"
dir=
fi
done
}
pw_gen() {
[[ -r /dev/urandom ]] ||
die "/dev/urandom is not readable."
mapfile -tn "${length:=50}" rand </dev/urandom
[[ $plain ]] &&
glob='[:alnum:]'
password=${rand[*]//[^${glob:-[:graph:]}]}
password=${password:0:$length}
}
check_entropy() {
kernel_path=/proc/sys/kernel/random
entropy_cur=$(< "${kernel_path}/entropy_avail")
entropy_min=$(< "${kernel_path}/read_wakeup_threshold")
[[ ${entropy_cur:=0} -lt ${entropy_min:=1} ]] && {
printf '%s\n' "warn: Not enough entropy to generate a secure password."
yn "warn: Continue anyway?"
[[ $REPLY != [yY] ]] &&
exit 1
}
}
yn() {
read -rn 1 -p "$1 [y/n]: "
printf '\n'
}
die() {
printf 'error: %s\n' "$1" >&2
exit 1
}
copy() {
[[ $TMUX ]] && {
tmux load-buffer "$password"
return
}
hash xclip 2>/dev/null &&
xclip -selection clipboard &>/dev/null <<< "$password"
}
usage() { printf '%s' "\
pash - simple password manager.
usage: pash [add|del|show|list] [name] [-n,-q,-c] [-l length]
-c Copy password to clipboard.
-l length Length of generated passwords.
-n Limit passwords to alphanumeric characters.
-q Don't print password to stdout.
-t Print list output as a tree.
-h Print this message.
-v Show version.
"
exit 1
}
get_args() {
[[ $1 != l* ]] &&
shift 1
shift 1
while getopts ":ncvqtl:" opt; do case $opt in
c) clipboard=1 ;;
l) length=${OPTARG//[^0-9]} ;;
n) plain=1 ;;
q) quiet=1 ;;
t) tree=1 ;;
v) printf '%s\n' "pash 0.01"; exit ;;
?) usage ;;
esac; done
}
main() {
get_args "$@"
mapfile -t gpg < <(type -p gpg gpg2) && [[ ! -x ${gpg[0]} ]] &&
die "GPG not found."
mkdir -p "${pass_dir:=${XDG_DATA_HOME:=$HOME/.local/share}/pash}" ||
die "Couldn't create password directory."
cd "$pass_dir" ||
die "Can't access password directory."
[[ $1 == [ads]* && -z $2 ]] &&
die "Missing [name] argument."
[[ $1 == [ds]* && ! -f $2.gpg ]] &&
die "Pass file '$2' doesn't exist."
[[ $1 == [a]* && -f $2.gpg ]] &&
die "Pass file '$2' already exists."
case $1 in
a*) pw_add "$2" ;;
d*) pw_del "$2" ;;
s*) pw_show "$2" ;;
l*) pw_list ;;
*) usage ;;
esac
[[ $clipboard && $password ]] &&
copy
[[ -z $quiet && $password ]] &&
printf '%s\n' "$password"
}
main "$@"