2019-02-24 14:36:47 -06:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
#
|
|
|
|
# pash - simple password manager.
|
|
|
|
|
|
|
|
pw_add() {
|
2019-02-24 15:04:28 -06:00
|
|
|
yn "Generate a password?"
|
2019-02-24 14:36:47 -06:00
|
|
|
|
2019-02-24 15:04:28 -06:00
|
|
|
case $REPLY in
|
|
|
|
[yY])
|
|
|
|
[[ $OSTYPE == linux* ]] &&
|
|
|
|
check_entropy
|
|
|
|
|
2019-02-24 15:49:19 -06:00
|
|
|
pw_gen
|
2019-02-24 15:04:28 -06:00
|
|
|
;;
|
|
|
|
|
|
|
|
*) read -rsp "Enter password: " password ;;
|
|
|
|
esac
|
2019-02-24 14:36:47 -06:00
|
|
|
|
|
|
|
[[ $password ]] ||
|
|
|
|
die "Failed to generate a password."
|
|
|
|
|
2019-02-25 00:58:15 -06:00
|
|
|
[[ $1 == */* ]] &&
|
|
|
|
{ mkdir -p "${1%/*}" || die "Couldn't create category '${1%/*}'.";}
|
2019-02-24 15:38:55 -06:00
|
|
|
|
2019-02-24 17:08:50 -06:00
|
|
|
"${gpg[0]}" -co "$1.gpg" <<< "$password"
|
2019-02-24 14:36:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
pw_del() {
|
2019-02-24 15:04:28 -06:00
|
|
|
yn "Delete pass file '$1'?"
|
2019-02-24 14:36:47 -06:00
|
|
|
|
|
|
|
[[ $REPLY == [yY] ]] &&
|
|
|
|
rm "$1.gpg"
|
|
|
|
}
|
|
|
|
|
|
|
|
pw_show() {
|
2019-02-24 17:10:15 -06:00
|
|
|
read -r password < <("${gpg[0]}" -dq "$1.gpg")
|
2019-02-24 14:36:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
pw_list() {
|
2019-02-25 13:46:57 -06:00
|
|
|
shopt -s globstar nullglob
|
2019-02-24 14:36:47 -06:00
|
|
|
|
2019-02-25 14:07:50 -06:00
|
|
|
[[ $tree ]] &&
|
|
|
|
printf '%s\n' "pash"
|
|
|
|
|
2019-02-25 13:46:57 -06:00
|
|
|
for pwrd in **; do
|
|
|
|
[[ -d $pwrd ]] &&
|
|
|
|
dir=/
|
|
|
|
|
|
|
|
nest=${pwrd//[^\/]}
|
|
|
|
pwrd=${pwrd//[^[:print:]]/^[}
|
|
|
|
pwrd=${pwrd//.gpg}
|
2019-02-24 14:36:47 -06:00
|
|
|
|
2019-02-25 13:46:57 -06:00
|
|
|
if [[ -z $tree ]]; then
|
|
|
|
printf '%s\n' "$pwrd"
|
|
|
|
|
|
|
|
else
|
|
|
|
printf '%s\n' "${nest//\//│ }├─ ${pwrd##*/}${dir}"
|
|
|
|
dir=
|
|
|
|
fi
|
|
|
|
done
|
2019-02-25 14:07:50 -06:00
|
|
|
|
|
|
|
[[ $tree ]] &&
|
|
|
|
printf '└%s\b┘\n' "${nest//\//──┴}"
|
2019-02-24 14:36:47 -06:00
|
|
|
}
|
|
|
|
|
2019-02-24 15:49:19 -06:00
|
|
|
pw_gen() {
|
2019-02-24 17:15:42 -06:00
|
|
|
[[ -r /dev/urandom ]] ||
|
|
|
|
die "/dev/urandom is not readable."
|
|
|
|
|
2019-02-24 14:36:47 -06:00
|
|
|
mapfile -tn "${length:=50}" rand </dev/urandom
|
|
|
|
|
|
|
|
[[ $plain ]] &&
|
2019-02-24 15:56:33 -06:00
|
|
|
glob='[:alnum:]'
|
2019-02-24 14:36:47 -06:00
|
|
|
|
2019-02-24 15:53:39 -06:00
|
|
|
password=${rand[*]//[^${glob:-[:graph:]}]}
|
2019-02-24 15:14:37 -06:00
|
|
|
password=${password:0:$length}
|
2019-02-24 14:36:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
check_entropy() {
|
2019-02-24 16:08:29 -06:00
|
|
|
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} ]] && {
|
2019-02-24 14:36:47 -06:00
|
|
|
printf '%s\n' "warn: Not enough entropy to generate a secure password."
|
2019-02-24 15:04:28 -06:00
|
|
|
yn "warn: Continue anyway?"
|
2019-02-24 14:36:47 -06:00
|
|
|
|
|
|
|
[[ $REPLY != [yY] ]] &&
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-24 15:04:28 -06:00
|
|
|
yn() {
|
|
|
|
read -rn 1 -p "$1 [y/n]: "
|
|
|
|
printf '\n'
|
|
|
|
}
|
|
|
|
|
2019-02-24 14:36:47 -06:00
|
|
|
die() {
|
|
|
|
printf 'error: %s\n' "$1" >&2
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
2019-02-24 15:25:11 -06:00
|
|
|
copy() {
|
|
|
|
[[ $TMUX ]] && {
|
|
|
|
tmux load-buffer "$password"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
hash xclip 2>/dev/null &&
|
|
|
|
xclip -selection clipboard &>/dev/null <<< "$password"
|
|
|
|
}
|
|
|
|
|
2019-02-24 14:36:47 -06:00
|
|
|
usage() { printf '%s' "\
|
|
|
|
pash - simple password manager.
|
2019-02-24 14:40:56 -06:00
|
|
|
usage: pash [add|del|show|list] [name] [-n,-q,-c] [-l length]
|
2019-02-24 14:36:47 -06:00
|
|
|
|
2019-02-24 14:40:56 -06:00
|
|
|
-c Copy password to clipboard.
|
2019-02-25 13:49:31 -06:00
|
|
|
-l length Length of generated passwords.
|
|
|
|
-n Limit passwords to alphanumeric characters.
|
2019-02-24 14:36:47 -06:00
|
|
|
-q Don't print password to stdout.
|
2019-02-25 13:46:57 -06:00
|
|
|
-t Print list output as a tree.
|
2019-02-24 14:36:47 -06:00
|
|
|
-h Print this message.
|
|
|
|
-v Show version.
|
|
|
|
"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
get_args() {
|
2019-02-25 13:46:57 -06:00
|
|
|
[[ $1 != l* ]] &&
|
|
|
|
shift 1
|
|
|
|
|
|
|
|
shift 1
|
2019-02-24 14:36:47 -06:00
|
|
|
|
2019-02-25 13:46:57 -06:00
|
|
|
while getopts ":ncvqtl:" opt; do case $opt in
|
2019-02-24 14:36:47 -06:00
|
|
|
c) clipboard=1 ;;
|
2019-02-25 13:49:31 -06:00
|
|
|
l) length=${OPTARG//[^0-9]} ;;
|
|
|
|
n) plain=1 ;;
|
2019-02-24 14:36:47 -06:00
|
|
|
q) quiet=1 ;;
|
2019-02-25 13:46:57 -06:00
|
|
|
t) tree=1 ;;
|
2019-02-24 14:36:47 -06:00
|
|
|
v) printf '%s\n' "pash 0.01"; exit ;;
|
|
|
|
?) usage ;;
|
|
|
|
esac; done
|
|
|
|
}
|
|
|
|
|
|
|
|
main() {
|
|
|
|
get_args "$@"
|
|
|
|
|
2019-02-24 17:09:17 -06:00
|
|
|
mapfile -t gpg < <(type -p gpg gpg2) && [[ ! -x ${gpg[0]} ]] &&
|
2019-02-24 14:36:47 -06:00
|
|
|
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
|
|
|
|
|
2019-02-24 15:25:11 -06:00
|
|
|
[[ $clipboard && $password ]] &&
|
|
|
|
copy
|
2019-02-24 15:04:28 -06:00
|
|
|
|
|
|
|
[[ -z $quiet && $password ]] &&
|
|
|
|
printf '%s\n' "$password"
|
2019-02-24 14:36:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
main "$@"
|