spm

Personal fork of spm (simple password manager)

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
#!/bin/sh
# Copyright (C) 2013-2016 Sören Tempel
# Copyright (C) 2016, 2017 Klemens Nanni <kl3@posteo.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

set -eu
umask u=rwx,go=

## Variables
GPG_OPTS='--quiet --yes --batch'
STORE_DIR="${PASSWORD_STORE_DIR:-${HOME}/.spm}"

## Helper
usage() {
	cat 1>&2 <<-EOF
	${1:+Error: ${1}}
	USAGE: ${0##*/} add|del|list [-g]|search|show|help [[group/]entry|expression]
	See spm(1) for more information.
	EOF

	exit ${1:+1}
}

check() {
	[ -n "${entry}" ] || usage 'no such entry'

	[ $(printf '%s' "${entry}" | wc -l) -eq 0 ] ||
		usage 'ambigious expression'
}

gpg() {
	if [ -z "${PASSWORD_STORE_KEY}" ]; then
		gpg2 ${GPG_OPTS} --default-recipient-self "${@}"
	else
		gpg2 ${GPG_OPTS} --recipient "${PASSWORD_STORE_KEY}" "${@}"
	fi
}

readpw() {
	[ -t 0 ] && stty -echo && printf '%s' "${1}"
	IFS= read -r "${2}"
	[ -t 0 ] && stty echo
	[ -z "${2}" ] && usage 'empty password'
}

find() {
	command find "${STORE_DIR}" -type f -o -type l | grep -ie "${1}"
}

munge() {
	abspath="$(readlink -f "${STORE_DIR}"/"${1}")"
	case "${abspath}" in
	"${STORE_DIR}"*)
		eval ${2}=\"${abspath#${STORE_DIR}}\"
		;;
	*)
		usage 'bad traversal'
	esac
}

view() {
	less -EiKRX
}

## Commands
add() {
	[ -e "${STORE_DIR}"/"${1}" ] && usage 'entry already exists'

	password=
	readpw "Password for '${1}': " password
	[ -t 0 ] && printf '\n'

	group="${1%/*}"
	[ "${group}" = "${1}" ] && group=

	mkdir -p "${STORE_DIR}"/"${group}" &&
		printf '%s\n' "${password}" |
			gpg --encrypt --output "${STORE_DIR}"/"${1}"
}

list() {
	[ -d "${STORE_DIR}"/"${1:-}" ] || usage 'no such group'

	tree ${gflag:+-d} -Fx -- "${STORE_DIR}"/"${1:-}" | view
}

del() {
	entry=$(find "${1}" | head -n2)
	check; command rm -i "${entry}" && printf '\n'
}

search() {
	find "${1}" | view
}

show() {
	entry=$(find "${1}" | head -n2)
	check; gpg --decrypt "${entry}"
}

## Parse input
[ ${#} -eq 0 ] || [ ${#} -gt 3 ] ||
[ ${#} -eq 3 ] && [ "${1:-}" != list ] &&
	usage 'wrong number of arguments'

case "${1}" in
add|del|search|show)
	[ -z "${2:-}" ] && usage 'empty name'
	${1} "${2}"
	;;
list)
	[ "${2:-}" = -g ] && gflag=1 && shift 1
	[ ${#} -gt 2 ] && usage 'too many arguments'
	[ -n "${2:-}" ] && munge "${2}" relpath
	list "${relpath:-}"
	;;
help)
	usage
	;;
*)
	usage 'invalid command'
	;;
esac