Newer
Older
# Copyright (C) 2012 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
# This file is licensed under the GPLv2+. Please see COPYING for more information.
PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
GIT_DIR="${PASSWORD_STORE_GIT:-$PREFIX}/.git"
GPG_OPTS="--quiet --yes --batch"
Jason A. Donenfeld
committed
export GIT_DIR
export GIT_WORK_TREE="${PASSWORD_STORE_GIT:-$PREFIX}"
version() {
cat <<_EOF
|-----------------------|
| Password Store |
| by zx2c4 |
| |
| Jason@zx2c4.com |
| Jason A. Donenfeld |
|-----------------------|
_EOF
}
$program init [--reencrypt,-e] gpg-id
Initialize new password storage and use gpg-id for encryption.
Optionally reencrypt existing passwords using new gpg-id.
$program [show] [--clip,-c] pass-name
Show existing password and optionally put it on the clipboard.
If put on the clipboard, it will be cleared in 45 seconds.
$program insert [--echo,-e | --multiline,-m] [--force,-f] pass-name
Insert new password. Optionally, echo the password back to the console
during entry. Or, optionally, the entry may be multiline. Prompt before
overwriting existing password unless forced.
$program edit pass-name
Insert a new password or edit an existing password using ${EDITOR:-vi}.
Jason A. Donenfeld
committed
$program generate [--no-symbols,-n] [--clip,-c] [--force,-f] pass-name pass-length
Generate a new password of pass-length with optionally no symbols.
Optionally put it on the clipboard and clear board after 45 seconds.
Jason A. Donenfeld
committed
Prompt before overwriting existing password unless forced.
$program rm [--recursive,-r] [--force,-f] pass-name
Remove existing password or directory, optionally forcefully.
$program git git-command-args...
If the password store is a git repository, execute a git command
specified by git-command-args.
$program version
Show version information.
More information may be found in the pass(1) man page.
init|ls|list|show|insert|edit|generate|remove|rm|delete|git|help|--help|version|--version) return 0 ;;
Jason A. Donenfeld
committed
git_add_file() {
[[ -d $GIT_DIR ]] || return
git add "$1" || return
[[ -n $(git status --porcelain "$1") ]] || return
git commit -m "$2"
}
yesno() {
read -p "$1 [y/N] " response
[[ $response == "y" || $response == "Y" ]] || exit 1
}
Jason A. Donenfeld
committed
#
# BEGIN Platform definable
#
# This base64 business is a disgusting hack to deal with newline inconsistancies
# in shell. There must be a better way to deal with this, but because I'm a dolt,
# we're going with this for now.
before="$(xclip -o -selection clipboard | base64)"
echo -n "$1" | xclip -selection clipboard
(
now="$(xclip -o -selection clipboard | base64)"
if [[ $now != $(echo -n "$1" | base64) ]]; then
before="$now"
fi
# It might be nice to programatically check to see if klipper exists,
# as well as checking for other common clipboard managers. But for now,
# this works fine -- if qdbus isn't there or if klipper isn't running,
# this essentially becomes a no-op.
#
# Clipboard managers frequently write their history out in plaintext,
# so we axe it here:
qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory &>/dev/null
echo "$before" | base64 -d | xclip -selection clipboard
) & disown
echo "Copied $2 to clipboard. Will clear in 45 seconds."
}
Jason A. Donenfeld
committed
tmpdir() {
if [[ -d /dev/shm && -w /dev/shm && -x /dev/shm ]]; then
tmp_dir="$(TMPDIR=/dev/shm mktemp -t "$template" -d)"
else
Jason A. Donenfeld
committed
yesno "$(echo "Your system does not have /dev/shm, which means that it may"
Jason A. Donenfeld
committed
echo "be difficult to entirely erase the temporary non-encrypted"
echo "password file after editing. Are you sure you would like to"
Jason A. Donenfeld
committed
echo -n "continue?")"
Jason A. Donenfeld
committed
tmp_dir="$(mktemp -t "$template" -d)"
fi
}
GETOPT="getopt"
# source /path/to/platform-defined-functions
#
# END Platform definable
#
reencrypt=0
opts="$($GETOPT -o e -l reencrypt -n "$program" -- "$@")"
err=$?
eval set -- "$opts"
while true; do case $1 in
-e|--reencrypt) reencrypt=1; shift ;;
--) shift; break ;;
esac done
echo "Usage: $program $command [--reencrypt,-e] gpg-id"
gpg_id="$1"
mkdir -v -p "$PREFIX"
echo "$gpg_id" > "$ID"
echo "Password store initialized for $gpg_id."
if [[ $reencrypt -eq 1 ]]; then
find "$PREFIX/" -iname '*.gpg' | while read passfile; do
gpg2 -d $GPG_OPTS "$passfile" | gpg2 -e -r "$gpg_id" -o "$passfile.new" $GPG_OPTS &&
mv -v "$passfile.new" "$passfile"
done
git_add_file "$PREFIX" "Reencrypted entire store using new GPG id $gpg_id."
fi
version|--version)
version
exit 0
;;
if [[ -n $PASSWORD_STORE_KEY ]]; then
ID="$PASSWORD_STORE_KEY"
echo " $program init your-gpg-id"
echo "before you may use the password store."
echo
usage
exit 1
else
ID="$(head -n 1 "$ID")"
fi
opts="$($GETOPT -o c -l clip -n "$program" -- "$@")"
err=$?
eval set -- "$opts"
while true; do case $1 in
-c|--clip) clip=1; shift ;;
--) shift; break ;;
esac done
if [[ $err -ne 0 ]]; then
echo "Usage: $program $command [--clip,-c] [pass-name]"
exit 1
passfile="$PREFIX/$path.gpg"
if [[ -f $passfile ]]; then
exec gpg2 -d $GPG_OPTS "$passfile"
pass="$(gpg2 -d $GPG_OPTS "$passfile" | head -n 1)"
[[ -n $pass ]] || exit 1
clip "$pass" "$path"
elif [[ -d $PREFIX/$path ]]; then
if [[ -z $path ]]; then
echo "Password Store"
else
echo "${path%\/}"
fi
tree -l --noreport "$PREFIX/$path" | tail -n +2 | sed 's/\.gpg$//'
else
echo "$path is not in the password store."
exit 1
force=0
opts="$($GETOPT -o mef -l multiline,echo,force -n "$program" -- "$@")"
err=$?
eval set -- "$opts"
while true; do case $1 in
-e|--echo) noecho=0; shift ;;
-f|--force) force=1; shift ;;
if [[ $err -ne 0 || ( $multiline -eq 1 && $noecho -eq 0 ) || $# -ne 1 ]]; then
echo "Usage: $program $command [--echo,-e | --multiline,-m] [--force,-f] pass-name"
Jason A. Donenfeld
committed
[[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
mkdir -p -v "$PREFIX/$(dirname "$path")"
echo "Enter contents of $path and press Ctrl+D when finished:"
echo
gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS
read -r -p "Enter password for $path: " -s password
read -r -p "Retype password for $path: " -s password_again
if [[ $password == "$password_again" ]]; then
gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS <<<"$password"
break
else
echo "Error: the entered passwords do not match."
fi
done
else
read -r -p "Enter password for $path: " -e password
gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS <<<"$password"
git_add_file "$passfile" "Added given password for $path to store."
;;
edit)
if [[ $# -ne 1 ]]; then
exit 1
fi
path="$1"
mkdir -p -v "$PREFIX/$(dirname "$path")"
passfile="$PREFIX/$path.gpg"
trap 'rm -rf "$tmp_dir" "$tmp_file"' INT TERM EXIT
Jason A. Donenfeld
committed
tmpdir #Defines $tmp_dir
tmp_file="$(TMPDIR="$tmp_dir" mktemp -t "$template")"
action="Added"
if [[ -f $passfile ]]; then
gpg2 -d -o "$tmp_file" $GPG_OPTS "$passfile" || exit 1
while ! gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS "$tmp_file"; do
echo "GPG encryption failed. Retrying."
sleep 1
done
git_add_file "$passfile" "$action password for $path using ${EDITOR:-vi}."
Jason A. Donenfeld
committed
force=0
Jason A. Donenfeld
committed
opts="$($GETOPT -o ncf -l no-symbols,clip,force -n "$program" -- "$@")"
err=$?
eval set -- "$opts"
while true; do case $1 in
-n|--no-symbols) symbols=""; shift ;;
-c|--clip) clip=1; shift ;;
Jason A. Donenfeld
committed
-f|--force) force=1; shift ;;
--) shift; break ;;
esac done
if [[ $err -ne 0 || $# -ne 2 ]]; then
Jason A. Donenfeld
committed
echo "Usage: $program $command [--no-symbols,-n] [--clip,-c] [--force,-f] pass-name pass-length"
exit 1
fi
path="$1"
length="$2"
if [[ ! $length =~ ^[0-9]+$ ]]; then
echo "pass-length \"$length\" must be a number."
exit 1
fi
mkdir -p -v "$PREFIX/$(dirname "$path")"
passfile="$PREFIX/$path.gpg"
Jason A. Donenfeld
committed
[[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
pass="$(pwgen -s $symbols $length 1)"
gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS <<<"$pass"
git_add_file "$passfile" "Added generated password for $path to store."
echo "The generated password to $path is:"
echo "$pass"
else
clip "$pass" "$path"
fi
;;
opts="$($GETOPT -o rf -l recursive,force -n "$program" -- "$@")"
err=$?
eval set -- "$opts"
while true; do case $1 in
-r|--recursive) recursive="-r"; shift ;;
--) shift; break ;;
esac done
echo "Usage: $program $command [--recursive,-r] [--force,-f] pass-name"
echo "$path is not in the password store."
exit 1
fi
[[ $force -eq 1 ]] || yesno "Are you sure you would like to delete $path?"
rm $recursive -f -v "$passfile"
Jason A. Donenfeld
committed
if [[ -d $GIT_DIR && ! -e $passfile ]]; then
git commit -m "Removed $path from store."
fi
;;
if [[ $1 == "init" ]]; then
git "$@" || exit 1
git_add_file "$PREFIX" "Added current contents of password store."
elif [[ -d $GIT_DIR ]]; then
else
echo "Error: the password store is not a git repository."
exit 1
fi
;;