-
-
Save Boldewyn/20f3939b2e1ba47c60e1 to your computer and use it in GitHub Desktop.
pw: a convenient password retriever
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Convenient password retrieval | |
# | |
# Get your immemorable password by its alias name. | |
# | |
# Requirements: | |
# * either xclip or xsel (Linux), pbclip (OSX), Cygwin's putclip (Win) | |
# * standard tools: sed, column, tr | |
# * gpg or gpg2 | |
# * the 'moreutils' package, if you want to edit passwords easily | |
# | |
# the gpg-encrypted password file in the to-be-expected-by-XDG place | |
# (see http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) | |
PASSWORD_FILE="${XDG_DATA_HOME:-$HOME/.local/share}/pw.gpg" | |
# change this, if you use GNU screen and fiddled with the bufferfile option | |
SCREEN_EXCHANGE=/tmp/screen-exchange | |
# The GPG binary to use | |
GPG=gpg | |
if hash gpg2 2>/dev/null; then | |
GPG=gpg2 | |
fi | |
# Allowed characters for passwords, when generated. Default: as much of ASCII | |
# as feasible | |
ALLOWED_PASSWORD_CHARS='!-Z_a-~' | |
# length of generated password | |
DEFAULT_PASSWORD_LENGTH=32 | |
# Load the preference file, if it exists. Search in the standardized place | |
# first | |
if [[ -f ${XDG_CONFIG_HOME:-$HOME/.config}/pw ]]; then | |
. ${XDG_CONFIG_HOME:-$HOME/.config}/pw | |
elif [[ -f ~/.pwrc ]]; then | |
. ~/.pwrc | |
fi | |
# first CLI parameter | |
NAME=$1 | |
#### show HELP and exit | |
if [[ $NAME == "-h" || $NAME == "--help" ]]; then | |
echo "usage: $(basename $0) [NAME]" | |
echo "" | |
echo "Store and retrieve passwords securely" | |
echo "" | |
echo "Options" | |
echo "" | |
echo " -l, --list list available password names" | |
echo " -e, --edit edit the password file" | |
echo " -g, --generate NAME generate a new password with" | |
echo " the associated name NAME" | |
echo "" | |
echo "Description" | |
echo "" | |
echo " Return the password associated with NAME. If NAME" | |
echo " is -l or --list, list available NAMEs. If NAME is" | |
echo " omitted, show a list of all NAMEs to select from." | |
echo "" | |
echo " Specify -e or --edit to edit the password storage" | |
echo " file. (Requires the moreutils package.)" | |
echo "" | |
echo " With -g or --generate generate a password automa-" | |
echo " tically." | |
exit | |
fi | |
#### EDIT the password file and exit | |
if [[ $NAME == "-e" || $NAME == "--edit" ]]; then | |
if [[ ! $PASSWORD_FILE || ! -f $PASSWORD_FILE ]]; then | |
echo "No password file found." >&2 | |
if [[ $PASSWORD_FILE ]]; then | |
echo "To initialize one, run:" >&2 | |
echo "\$ touch /tmp/pw" >&2 | |
echo "\$ $GPG --default-recipient-self --output $PASSWORD_FILE --encrypt /tmp/pw" >&2 | |
fi | |
exit 1 | |
fi | |
if ! hash vipe 2>/dev/null; then | |
echo "vipe not found. Please install the 'moreutils' package." >&2 | |
exit 2 | |
fi | |
$GPG --quiet --decrypt "$PASSWORD_FILE" | \ | |
vipe | \ | |
$GPG --batch --yes --default-recipient-self --output "$PASSWORD_FILE" --encrypt - | |
exit | |
fi | |
#### GENERATE a password and exit | |
if [[ $NAME == "-g" || $NAME == "--generate" ]]; then | |
CONTENT= | |
if [[ $PASSWORD_FILE && -f $PASSWORD_FILE ]]; then | |
CONTENT=$($GPG --quiet --decrypt "$PASSWORD_FILE") | |
fi | |
REALNAME=$2 | |
if [[ ! $REALNAME ]]; then | |
echo "Enter a name as mnemonic for this password:" >&2 | |
read REALNAME | |
fi | |
echo "$CONTENT" | | |
cat - <( printf "%s:%s # generated on %s\n" \ | |
"$REALNAME" \ | |
"$( cat /dev/urandom | tr -d -c "$ALLOWED_PASSWORD_CHARS" | head -c "$DEFAULT_PASSWORD_LENGTH" )" \ | |
"$(date +%c)" ) | | |
$GPG --batch --yes --default-recipient-self --output "$PASSWORD_FILE" --encrypt - | |
exit | |
fi | |
# this is the password storage. Syntax: | |
# NAME:PASSWORD( # COMMENT)? | |
# The space before the comment hash is mandatory. | |
# If you want to have a quick and dirty solution, enter the data below | |
# into the EOL-marked heredoc. | |
# You can make this a bit safer (no plain-text passwords) by writing | |
# the content in a special file $PASSWORD_FILE and encrypting it with gpg2. | |
# Of course this goes at the cost of easily adding new passwords. | |
if [[ $PASSWORD_FILE && -f $PASSWORD_FILE ]]; then | |
LIST="$($GPG --quiet --decrypt "$PASSWORD_FILE" | sed -e '/^$/d' -e '/^#/d')" | |
else | |
read -r -d '' LIST <<EOL | |
name:password # optional comment | |
EOL | |
fi | |
# copy stdin to every imaginable (and available) clipboard | |
function _pw_copy { | |
local _pwd=$(cat -) | |
if [[ $_pwd ]]; then | |
case "$OSTYPE" in | |
darwin*) | |
# set the OSX pasteboard | |
if hash pbclip 2>/dev/null; then | |
echo -n "$_pwd" | pbclip | |
else | |
echo 'pbclip not found.' >&2 | |
fi | |
;; | |
linux*) | |
# set both X11 clipboards | |
if hash xclip 2>/dev/null; then | |
echo -n "$_pwd" | xclip -selection primary | |
echo -n "$_pwd" | xclip -selection clipboard | |
elif hash xsel 2>/dev/null; then | |
echo -n "$_pwd" | xsel | |
echo -n "$_pwd" | xsel --clipboard | |
else | |
echo 'neither xclip nor xsel found.' >&2 | |
fi | |
;; | |
cygwin*) | |
# set the Cygwin/Windows clipboard | |
if hash putclip 2>/dev/null; then | |
echo -n "$_pwd" | putclip | |
else | |
echo 'putclip not found.' >&2 | |
fi | |
;; | |
*) | |
echo "no clipboard handler found for $OSTYPE." >&2 | |
# do not exit, since we still might be able to set | |
# screen clipboard below. | |
;; | |
esac | |
if [[ $SCREEN_EXCHANGE && $STY ]]; then | |
# we are in a screen session: set screen clipboard, too | |
echo -n "$_pwd" > "$SCREEN_EXCHANGE" | |
screen -X readbuf | |
rm -f "$SCREEN_EXCHANGE" | |
fi | |
else | |
echo "No password found for $NAME." >&2 | |
fi | |
} | |
if [[ $NAME == "-l" || $NAME == "--list" ]]; then | |
echo "$LIST" | \ | |
sed "s/:\\([^ ]#\\|[^#]\\)*//" | \ | |
sed 's/#/\t/' | \ | |
column -t -s $'\t' | |
elif [[ ! $NAME ]]; then | |
echo "Choose password to copy:" >&2 | |
echo "$LIST" | \ | |
sed "s/:\\([^ ]#\\|[^#]\\)*//" | \ | |
sed '/^#/d' | \ | |
sed '/^$/d' | \ | |
sed 's/#/\t/' | \ | |
cat -n | \ | |
column -t -s $'\t' >&2 | |
echo "Select a number:" >&2 | |
read q | |
if [[ $q ]]; then | |
i=0 | |
IFS=$'\n' | |
for LINE in $LIST; do | |
if [[ ! $(echo $LINE | tr -d '[[:space:]]') ]]; then | |
continue | |
fi | |
case $LINE in | |
[^#]*) | |
let 'i+=1' | |
;; | |
esac | |
if [[ $i == $q ]]; then | |
NAME="entry $q" | |
echo "$LINE" | \ | |
sed 's/^[^:]\+://' | \ | |
sed 's/ \+#.*$//' | \ | |
tr -d '\n' | \ | |
_pw_copy | |
break | |
fi | |
done | |
if [[ ! $NAME ]]; then | |
echo "Invalid number specified." >&2 | |
exit 2 | |
fi | |
fi | |
else | |
echo "$LIST" | \ | |
sed -n "/^$NAME:/s/^$NAME://p" | \ | |
sed 's/ \+#.*$//' | \ | |
tr -d '\n' | \ | |
_pw_copy | |
fi | |
# vim: ft=sh shiftwidth=4 softtabstop=4 tabstop=4 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment