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 | ||