spm 2.8 KB

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