spm

Personal fork of spm (simple password manager)
git clone git@getsh.org:spm.git
Log | Files | Refs | README | LICENSE

spm (2896B)


      1 #!/bin/sh
      2 # Copyright (C) 2013-2016 Sören Tempel
      3 # Copyright (C) 2016, 2017 Klemens Nanni <kl3@posteo.org>
      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/>.
     17 
     18 set -eu
     19 umask u=rwx,go=
     20 
     21 ## Variables
     22 GPG_OPTS='--quiet --yes --batch'
     23 STORE_DIR="${PASSWORD_STORE_DIR:-${HOME}/.spm}"
     24 
     25 ## Helper
     26 usage() {
     27 	cat 1>&2 <<-EOF
     28 	${1:+Error: ${1}}
     29 	USAGE: ${0##*/} add|del|list [-g]|search|show|help [[group/]entry|expression]
     30 	See spm(1) for more information.
     31 	EOF
     32 
     33 	exit ${1:+1}
     34 }
     35 
     36 check() {
     37 	[ -n "${entry}" ] || usage 'no such entry'
     38 
     39 	[ $(printf '%s' "${entry}" | wc -l) -eq 0 ] ||
     40 		usage 'ambigious expression'
     41 }
     42 
     43 gpg() {
     44 	if [ -z "${PASSWORD_STORE_KEY}" ]; then
     45 		gpg2 ${GPG_OPTS} --default-recipient-self "${@}"
     46 	else
     47 		gpg2 ${GPG_OPTS} --recipient "${PASSWORD_STORE_KEY}" "${@}"
     48 	fi
     49 }
     50 
     51 readpw() {
     52 	[ -t 0 ] && stty -echo && printf '%s' "${1}"
     53 	IFS= read -r "${2}"
     54 	[ -t 0 ] && stty echo
     55 	[ -z "${2}" ] && usage 'empty password'
     56 }
     57 
     58 find() {
     59 	command find "${STORE_DIR}" -type f -o -type l | grep -ie "${1}"
     60 }
     61 
     62 munge() {
     63 	abspath="$(readlink -f "${STORE_DIR}"/"${1}")"
     64 	case "${abspath}" in
     65 	"${STORE_DIR}"*)
     66 		eval ${2}=\"${abspath#${STORE_DIR}}\"
     67 		;;
     68 	*)
     69 		usage 'bad traversal'
     70 	esac
     71 }
     72 
     73 view() {
     74 	less -EiKRX
     75 }
     76 
     77 ## Commands
     78 add() {
     79 	[ -e "${STORE_DIR}"/"${1}" ] && usage 'entry already exists'
     80 
     81 	password=
     82 	readpw "Password for '${1}': " password
     83 	[ -t 0 ] && printf '\n'
     84 
     85 	group="${1%/*}"
     86 	[ "${group}" = "${1}" ] && group=
     87 
     88 	mkdir -p "${STORE_DIR}"/"${group}" &&
     89 		printf '%s\n' "${password}" |
     90 			gpg --encrypt --output "${STORE_DIR}"/"${1}"
     91 }
     92 
     93 list() {
     94 	[ -d "${STORE_DIR}"/"${1:-}" ] || usage 'no such group'
     95 
     96 	tree ${gflag:+-d} -Fx -- "${STORE_DIR}"/"${1:-}" | view
     97 }
     98 
     99 del() {
    100 	entry=$(find "${1}" | head -n2)
    101 	check; command rm -i "${entry}" && printf '\n'
    102 }
    103 
    104 search() {
    105 	find "${1}" | view
    106 }
    107 
    108 show() {
    109 	entry=$(find "${1}" | head -n2)
    110 	check; gpg --decrypt "${entry}"
    111 }
    112 
    113 ## Parse input
    114 [ ${#} -eq 0 ] || [ ${#} -gt 3 ] ||
    115 [ ${#} -eq 3 ] && [ "${1:-}" != list ] &&
    116 	usage 'wrong number of arguments'
    117 
    118 case "${1}" in
    119 add|del|search|show)
    120 	[ -z "${2:-}" ] && usage 'empty name'
    121 	${1} "${2}"
    122 	;;
    123 list)
    124 	[ "${2:-}" = -g ] && gflag=1 && shift 1
    125 	[ ${#} -gt 2 ] && usage 'too many arguments'
    126 	[ -n "${2:-}" ] && munge "${2}" relpath
    127 	list "${relpath:-}"
    128 	;;
    129 help)
    130 	usage
    131 	;;
    132 *)
    133 	usage 'invalid command'
    134 	;;
    135 esac