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