diff options
| -rw-r--r-- | spass | 81 |
1 files changed, 81 insertions, 0 deletions
| @@ -0,0 +1,81 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # spass - simple, secure password storage and retrieval | ||
| 3 | |||
| 4 | [ -z "$XDG_DATA_HOME" ] && XDG_DATA_HOME="$HOME/.local/share" | ||
| 5 | [ -z "$PASSDIR" ] && PASSDIR="$XDG_DATA_HOME/pass" | ||
| 6 | |||
| 7 | all() { basename -s ".gpg" -a "$PASSDIR"/*.gpg; } | ||
| 8 | error() { echo "$(basename "$0"): error: $1" 1>&2; stty echo; exit; } | ||
| 9 | format() { sed -n '/[^\S\r\n]/ { 1s/^/password: /; 2s/^/username: /; 3s/^/totp: /; 4s/^/notes: /; p }'; } | ||
| 10 | |||
| 11 | if [ "$1" = "init" ]; then | ||
| 12 | if [ -z "$2" ] | ||
| 13 | then error "gpg key not specified" | ||
| 14 | elif gpg -K "$2" >/dev/null 2>&1 | ||
| 15 | then mkdir -p "$PASSDIR"; echo "$2" > "$PASSDIR/.gpg-id" | ||
| 16 | else error "gpg key doesn't exist" | ||
| 17 | fi; exit | ||
| 18 | elif [ -f "$PASSDIR/.gpg-id" ] | ||
| 19 | then GPG_ID="$(cat "$PASSDIR/.gpg-id")" | ||
| 20 | else error "password store not initialised!" | ||
| 21 | fi; | ||
| 22 | |||
| 23 | show() { | ||
| 24 | [ ! -f "$PASSDIR/$1.gpg" ] && error "no such entry: $1" | ||
| 25 | gpg -dq "$PASSDIR/$1.gpg" | case "$2" in | ||
| 26 | all) format;; | ||
| 27 | notes) sed -n 4p;; | ||
| 28 | totp) sed -n 3p | oathtool --base32 --totp -;; | ||
| 29 | name) sed -n 2p;; | ||
| 30 | *) sed -n 1p;; | ||
| 31 | esac | ||
| 32 | } | ||
| 33 | |||
| 34 | dump() { | ||
| 35 | for f in $(all) | ||
| 36 | do printf "# %s\n%s\n\n" "$f" "$(show "$f" all)" | ||
| 37 | done | head -n -1; | ||
| 38 | } | ||
| 39 | |||
| 40 | add() { | ||
| 41 | [ -z "$1" ] && error "entry name not specified" | ||
| 42 | printf "username: "; read -r u | ||
| 43 | stty -echo | ||
| 44 | printf "password: "; read -r p1; printf "\npassword (again): "; read -r p2 | ||
| 45 | printf "\ntotp: "; read -r t1; printf "\ntotp (again): "; read -r t2 | ||
| 46 | stty echo | ||
| 47 | printf "\nnotes: "; read -r n | ||
| 48 | [ "$p1" != "$p2" ] && error "passwords don't match" | ||
| 49 | [ "$t1" != "$t2" ] && error "totps don't match" | ||
| 50 | printf "%s\n%s\n%s\n%s" "$p1" "$u" "$t1" "$n" | gpg -eqr "$GPG_ID" -o "$PASSDIR/$1.gpg" | ||
| 51 | } | ||
| 52 | |||
| 53 | move() { | ||
| 54 | [ $# -lt 2 ] && error "source and destination not specified" | ||
| 55 | mv "$PASSDIR/$1.gpg" "$PASSDIR/$2.gpg" | ||
| 56 | } | ||
| 57 | |||
| 58 | remove () { | ||
| 59 | [ ! -f "$PASSDIR/$1.gpg" ] && error "entry does not exist: $1" | ||
| 60 | printf "are you sure you want to remove %s? [y/N] " $1; read -r c | ||
| 61 | [ "$c" = "y" ] || [ "$c" = "Y" ] && rm "$PASSDIR/$1.gpg" | ||
| 62 | } | ||
| 63 | |||
| 64 | sel() { | ||
| 65 | [ -z "$1" ] && error "no menu program specified" | ||
| 66 | ENTRY="$(all | $@)"; [ -z "$ENTRY" ] && exit | ||
| 67 | INFO="$(printf "pass\nname\ntotp\nnotes" | "$@")"; [ -z "$INFO" ] && exit | ||
| 68 | for p in $(pgrep "$(basename "$0")" | head -n -2); do kill "$p"; done | ||
| 69 | show "$ENTRY" "$INFO" | tr -d "\n" | xclip -selection clipboard | ||
| 70 | notify-send "$(basename "$0")" "$INFO copied to clipboard\nclearing in 10s" | ||
| 71 | sleep 10; xclip -selection clipboard < /dev/null | ||
| 72 | } | ||
| 73 | |||
| 74 | case "$1" in | ||
| 75 | mv) shift; move "$@";; | ||
| 76 | rm) shift; remove "$@";; | ||
| 77 | sel) shift; sel "$@";; | ||
| 78 | add ) add "$2";; | ||
| 79 | dump) dump;; | ||
| 80 | *) if [ -z "$1" ]; then all; else show "$@"; fi;; | ||
| 81 | esac | ||
