/pss
1 #!/bin/sh
2
3 # Passwords, secrets and stuff
4 # Copyright 2018, 2019 Vasilii Kolobkov
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19 umask 077
20
21 # Secrets file is made of newline-separated records of following form:
22 # <name>FS<password>FS<resource>FS<notes>
23 # where FS is value of 'field_sepator' variable.
24 # Empty lines, ones made of whitespace and those beginning with '#' are ignored.
25
26 secrets=~/.secrets
27 field_separator="$(printf '\037')"
28
29 usage() {
30 echo 'Usage: pss [-n|-yn|-p|-yp] <key>|-a' 1>&2
31 exit 1
32 }
33
34 prompt() {
35 if [ -t 0 ]; then
36 printf '%s: ' "${1}"
37 fi
38 }
39
40 read_field() {
41 [ -t 0 -a "${1}" -eq 1 ] && stty -echo
42 (
43 IFS= read -r val
44 printf '%s\n' "${val}" | tr -d "${field_separator}"
45 )
46 [ -t 0 -a "${1}" -eq 1 ] && stty echo
47 }
48
49 add_rec() {
50 (
51 prompt 'Name'; name="$(read_field 0)"
52 prompt 'Password'; password="$(read_field 1)"
53 prompt 'Resource'; resource="$(read_field 0)"
54 prompt 'Notes'; notes="$(read_field 0)"
55
56 set -eC
57
58 secrets_new="${secrets}.new"
59 secrets_old="${secrets}.old"
60 {
61 [ -f "${secrets}" ] && gpg --decrypt "${secrets}"
62 {
63 echo "${name}"
64 echo "${password}"
65 echo "${resource}"
66 echo "${notes}"
67 } | pr -4 -t -s"${field_separator}"
68 } | gpg --encrypt --default-recipient-self > "${secrets_new}"
69 [ -f "${secrets}" ] && mv "${secrets}" "${secrets_old}"
70 mv "${secrets_new}" "${secrets}"
71 [ -f "${secrets_old}" ] && unlink "${secrets_old}"
72 )
73 }
74
75 find_rec() {
76 awk -F "${field_separator}" -v "query=${1}" '
77 $0 ~ /^#/ || $0 ~ /^[:space:]*$/ { next }
78 $1 == query || $3 == query {
79 rec = $0
80 nexact++
81 }
82 index($1, query) || index($3, query) {
83 rec = $0
84 nsubstr++
85 }
86 END {
87 if (nexact == 1 || (nexact == 0 && nsubstr == 1)) print rec
88 else if (nexact + nsubstr == 0) exit 1
89 else exit 2
90 }'
91 case "${?}" in
92 1) echo 'No match' 1>&2; return 1;;
93 2) echo 'Ambiguous query' 1>&2; return 2;;
94 esac
95 }
96
97 show_field() {
98 if [ "${1}" = "" ]; then
99 usage
100 fi
101 gpg --decrypt "${secrets}" | find_rec "${1}" |
102 awk -F "${field_separator}" "{print \$${2}}"
103 }
104
105 if [ ${#} -lt 1 ]; then
106 usage
107 fi
108
109 case "${1}" in
110 -n) shift; show_field "${*}" 1;;
111 -yn) shift; show_field "${*}" 1 | xclip -r;;
112 -p) shift; show_field "${*}" 2;;
113 -yp) shift; show_field "${*}" 2 | xclip -r;;
114 -a) add_rec;;
115 *) usage;;
116 esac