Blob


1 #!/bin/sh
2 # Copyright (C) 2013-2016 Sören Tempel
3 # Copyright (C) 2017 Matthias Schmidt
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 set -eu
20 umask 077
22 ##
23 # Variables r/w
24 ##
26 GPG_OPTS="--quiet --yes"
27 GPGB="gpg2"
28 TMPDIR="/tmp"
29 NUKE="rm"
30 PASSWORD_STORE_KEY=${PASSWORD_STORE_KEY:-""}
31 EDITOR=${EDITOR:-""}
32 TOPTS="-C"
34 ##
35 # Variables r/o
36 ##
38 readonly STORE_DIR="${HOME}/.password-store"
39 readonly PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
41 ##
42 # Helper
43 ##
45 if [ -r "${STORE_DIR}/.gpg-id" ] && [ -z "${PASSWORD_STORE_KEY}" ]; then
46 read -r PASSWORD_STORE_KEY < "${STORE_DIR}/.gpg-id"
47 fi
49 usage() {
50 echo "Usage: $(basename $0) [option] [argument]"
51 echo
52 echo "Option can be one of the following"
53 echo
54 echo " show <entry> Shows password <entry>"
55 echo " insert <entry> Inserts new <entry>"
56 echo " find <entry> Searches for <entry>"
57 echo " rm <entry> Removes <entry>"
58 echo
59 echo "If you do not specify an option, all entries will be shown"
60 exit 0
61 }
63 abort() {
64 echo "$(basename ${0}): ${1}" 1>&2
65 exit 1
66 }
68 mygpg() {
70 if [ -z "$(which gpg2 2> /dev/null)" ]; then
71 if [ -z "$(which gpg 2> /dev/null)" ]; then
72 abort "gpg or gpg2 not found. Please install."
73 else
74 GPGB="gpg"
75 fi
76 fi
78 if [ -n "${PASSWORD_STORE_KEY}" ]; then
79 ${GPGB} ${GPG_OPTS} --recipient "${PASSWORD_STORE_KEY}" "$@"
80 else
81 ${GPGB} ${GPG_OPTS} --default-recipient-self "$@"
82 fi
84 return $?
85 }
87 readpw() {
88 if [ -t 0 ]; then
89 printf "%s" "${1}"
90 stty -echo
91 fi
93 IFS= read -r "${2}"
94 [ -t 0 ] && stty echo
95 }
97 entry_exists() {
98 local _entry_name="${1}"
99 local _entry_path="${STORE_DIR}/${_entry_name}.gpg"
101 if [ ! -f "${_entry_path}" ]; then
102 abort "The requested entry doesn't exist."
103 fi
106 choose_tmp() {
107 # Create the temp file on a shared memory fs if available
108 if [ -d "/dev/shm" -a -k "/dev/shm" ]; then
109 TMPDIR="/dev/shm"
110 fi
113 nuke_file() {
114 OSV=$(uname -s)
116 if [ -n ${OSV} ]; then
117 if [ "${OSV}" = "Linux" ]; then
118 if [[ -z "$(which shred 2> /dev/null)" ]]; then
119 echo "Cannot find shred. Using rm"
120 return
121 fi
122 NUKE="shred -u"
123 elif [ "${OSV}" = "OpenBSD" -o "${OSV}" = "FreeBSD" -o "${OSV}" = "DragonFly" -o "${OSV}" = "NetBSD" ]; then
124 NUKE="rm -P"
125 fi
126 fi
129 ##
130 # Commands
131 ##
133 show() {
134 local _entry_name="${1}"
135 local _entry_path="${STORE_DIR}/${_entry_name}.gpg"
137 if [ -z "${_entry_name}" ]; then
138 abort "show needs an argument"
139 fi
141 entry_exists ${_entry_name}
143 mygpg --decrypt "${_entry_path}" || abort "Cannot open entry"
146 insert() {
147 local _entry_name="${1}"
148 local _entry_path="${STORE_DIR}/${_entry_name}.gpg"
150 if [ -z "${_entry_name}" ]; then
151 abort "insert needs an argument"
152 fi
154 if [ -f "${_entry_path}" ]; then
155 abort "This entry already exists"
156 fi
158 password=""
159 readpw "Password for '${_entry_name}': " password
160 if [ -t 0 ]; then
161 printf "\n"
162 fi
164 if [ -z "${password}" ]; then
165 abort "You didn't specify a password."
166 fi
168 command mkdir -p "${_entry_path%/*}" || abort "Cannot create password entry"
169 printf '%s\n' "${password}" | mygpg --encrypt \
170 --output "${_entry_path}"
173 show_all() {
174 ${TREE} -l -x --noreport $TOPTS "${STORE_DIR}" -P "*.gpg" --prune | sed 's/\.gpg//'
177 remove_entry() {
178 local _entry_name="${1}" _answer
180 [ -z "${_entry_name}" ] && abort "rm needs an argument"
182 entry_exists ${_entry_name}
184 nuke_file
186 echo -n "$0: Really remove ${_entry_name} [y/N]? "
187 read -r _answer
188 case "${_answer}" in
189 [yY]) command ${NUKE} -f "${STORE_DIR}/${_entry_name}.gpg" ;;
190 *) ;;
191 esac
194 find_entry() {
195 local _entry_name="${1}"
197 [ -z "${_entry_name}" ] && abort "find needs an argument"
198 ${TREE} -l -x --noreport $TOPTS "${STORE_DIR}/" -P "*${_entry_name}*" --prune \
199 --matchdirs --ignore-case | sed 's/\.gpg//'
200 echo
203 edit_entry() {
204 local _entry_name="${1}" _tmpfile
206 [ -z "${_entry_name}" ] && abort "edit needs an argument"
208 entry_exists ${_entry_name}
210 nuke_file
212 choose_tmp
213 _tmpfile=$(mktemp ${TMPDIR}/tpm.XXXXXXXXXX) || abort "Cannot create temporary file"
214 trap "${NUKE} -f ${_tmpfile}; exit 0" 0 1 2 3 15
216 mygpg --output ${_tmpfile} --decrypt "${STORE_DIR}/${_entry_name}.gpg" || \
217 abort "Cannot edit file"
219 if [ -n "${EDITOR}" ]; then
220 ${EDITOR} ${_tmpfile} || abort "Cannot open file using \$EDITOR"
221 else
222 vi ${_tmpfile} || abort "Neither vi or an editor in \$EDITOR was found"
223 fi
225 mygpg --output "${STORE_DIR}/${_entry_name}.gpg" --encrypt ${_tmpfile} || \
226 abort "Cannot re-encrypt temporary file"
228 # If the file is there, remove it
229 [ -e "${_tmpfile}" ] && ${NUKE} -f "${_tmpfile}"
232 ##
233 # Parse input
234 ##
236 set +u
237 [ "${1}" != "insert" -a ! -d "${STORE_DIR}" ] && abort "Password directory does not exist."
239 # Disable colors for colortree if NO_COLOR is set
240 [[ -n $NO_COLOR ]] && TOPTS="-n"
241 set -u
243 if [ ! -z "$(which colortree 2> /dev/null)" ]; then
244 TREE=colortree
245 elif [ ! -z "$(which tree 2> /dev/null)" ]; then
246 TREE=tree
247 else
248 abort "tpm needs tree or colortree to run. Please install"
249 fi
251 if [ $# -eq 0 ]; then
252 show_all
253 exit 0
254 elif [ $# -gt 2 ]; then
255 abort "tpm doesn't accept more than two arguments."
256 fi
258 case "${1}" in
259 "show") show "${2}" ;;
260 "insert") insert "${2}" ;;
261 "find") find_entry "${2}" ;;
262 "rm") remove_entry "${2}" ;;
263 "edit") edit_entry "${2}" ;;
264 "help") usage ;;
265 *) show "${1}" ;;
266 esac
268 exit 0