PageRenderTime 1595ms CodeModel.GetById 0ms RepoModel.GetById 1ms app.codeStats 0ms

/src/password-store.sh

https://gitlab.com/ralt/password-store
Shell | 597 lines | 570 code | 18 blank | 9 comment | 34 complexity | 640f2411e443e46472c095c97030da0e MD5 | raw file
  1. #!/usr/bin/env bash
  2. # Copyright (C) 2012 - 2014 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
  3. # This file is licensed under the GPLv2+. Please see COPYING for more information.
  4. umask "${PASSWORD_STORE_UMASK:-077}"
  5. set -o pipefail
  6. GPG_OPTS=( $PASSWORD_STORE_GPG_OPTS "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
  7. GPG="gpg"
  8. export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
  9. which gpg2 &>/dev/null && GPG="gpg2"
  10. [[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
  11. PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
  12. X_SELECTION="${PASSWORD_STORE_X_SELECTION:-clipboard}"
  13. CLIP_TIME="${PASSWORD_STORE_CLIP_TIME:-45}"
  14. GENERATED_LENGTH="${PASSWORD_STORE_GENERATED_LENGTH:-25}"
  15. export GIT_DIR="${PASSWORD_STORE_GIT:-$PREFIX}/.git"
  16. export GIT_WORK_TREE="${PASSWORD_STORE_GIT:-$PREFIX}"
  17. #
  18. # BEGIN helper functions
  19. #
  20. git_add_file() {
  21. [[ -d $GIT_DIR ]] || return
  22. git add "$1" || return
  23. [[ -n $(git status --porcelain "$1") ]] || return
  24. git_commit "$2"
  25. }
  26. git_commit() {
  27. local sign=""
  28. [[ -d $GIT_DIR ]] || return
  29. [[ $(git config --bool --get pass.signcommits) == "true" ]] && sign="-S"
  30. git commit $sign -m "$1"
  31. }
  32. yesno() {
  33. [[ -t 0 ]] || return 0
  34. local response
  35. read -r -p "$1 [y/N] " response
  36. [[ $response == [yY] ]] || exit 1
  37. }
  38. die() {
  39. echo "$@" >&2
  40. exit 1
  41. }
  42. set_gpg_recipients() {
  43. GPG_RECIPIENT_ARGS=( )
  44. GPG_RECIPIENTS=( )
  45. if [[ -n $PASSWORD_STORE_KEY ]]; then
  46. for gpg_id in $PASSWORD_STORE_KEY; do
  47. GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" )
  48. GPG_RECIPIENTS+=( "$gpg_id" )
  49. done
  50. return
  51. fi
  52. local current="$PREFIX/$1"
  53. while [[ $current != "$PREFIX" && ! -f $current/.gpg-id ]]; do
  54. current="${current%/*}"
  55. done
  56. current="$current/.gpg-id"
  57. if [[ ! -f $current ]]; then
  58. cat >&2 <<-_EOF
  59. Error: You must run:
  60. $PROGRAM init your-gpg-id
  61. before you may use the password store.
  62. _EOF
  63. cmd_usage
  64. exit 1
  65. fi
  66. local gpg_id
  67. while read -r gpg_id; do
  68. GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" )
  69. GPG_RECIPIENTS+=( "$gpg_id" )
  70. done < "$current"
  71. }
  72. reencrypt_path() {
  73. local prev_gpg_recipients="" gpg_keys="" current_keys="" index passfile
  74. local groups="$($GPG $PASSWORD_STORE_GPG_OPTS --list-config --with-colons | grep "^cfg:group:.*")"
  75. while read -r -d "" passfile; do
  76. local passfile_dir="${passfile%/*}"
  77. passfile_dir="${passfile_dir#$PREFIX}"
  78. passfile_dir="${passfile_dir#/}"
  79. local passfile_display="${passfile#$PREFIX/}"
  80. passfile_display="${passfile_display%.gpg}"
  81. local passfile_temp="${passfile}.tmp.${RANDOM}.${RANDOM}.${RANDOM}.${RANDOM}.--"
  82. set_gpg_recipients "$passfile_dir"
  83. if [[ $prev_gpg_recipients != "${GPG_RECIPIENTS[*]}" ]]; then
  84. for index in "${!GPG_RECIPIENTS[@]}"; do
  85. local group="$(sed -n "s/^cfg:group:$(sed 's/[\/&]/\\&/g' <<<"${GPG_RECIPIENTS[$index]}"):\\(.*\\)\$/\\1/p" <<<"$groups" | head -n 1)"
  86. [[ -z $group ]] && continue
  87. IFS=";" eval 'GPG_RECIPIENTS+=( $group )' # http://unix.stackexchange.com/a/92190
  88. unset GPG_RECIPIENTS[$index]
  89. done
  90. gpg_keys="$($GPG $PASSWORD_STORE_GPG_OPTS --list-keys --with-colons "${GPG_RECIPIENTS[@]}" | sed -n 's/sub:[^:]*:[^:]*:[^:]*:\([^:]*\):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[a-zA-Z]*e[a-zA-Z]*:.*/\1/p' | LC_ALL=C sort -u)"
  91. fi
  92. current_keys="$($GPG $PASSWORD_STORE_GPG_OPTS -v --no-secmem-warning --no-permission-warning --list-only --keyid-format long "$passfile" 2>&1 | cut -d ' ' -f 5 | LC_ALL=C sort -u)"
  93. if [[ $gpg_keys != "$current_keys" ]]; then
  94. echo "$passfile_display: reencrypting to ${gpg_keys//$'\n'/ }"
  95. $GPG -d "${GPG_OPTS[@]}" "$passfile" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile_temp" "${GPG_OPTS[@]}" &&
  96. mv "$passfile_temp" "$passfile" || rm -f "$passfile_temp"
  97. fi
  98. prev_gpg_recipients="${GPG_RECIPIENTS[*]}"
  99. done < <(find "$1" -iname '*.gpg' -print0)
  100. }
  101. check_sneaky_paths() {
  102. local path
  103. for path in "$@"; do
  104. [[ $path =~ /\.\.$ || $path =~ ^\.\./ || $path =~ /\.\./ || $path =~ ^\.\.$ ]] && die "Error: You've attempted to pass a sneaky path to pass. Go home."
  105. done
  106. }
  107. #
  108. # END helper functions
  109. #
  110. #
  111. # BEGIN platform definable
  112. #
  113. clip() {
  114. # This base64 business is because bash cannot store binary data in a shell
  115. # variable. Specifically, it cannot store nulls nor (non-trivally) store
  116. # trailing new lines.
  117. local sleep_argv0="password store sleep on display $DISPLAY"
  118. pkill -f "^$sleep_argv0" 2>/dev/null && sleep 0.5
  119. local before="$(xclip -o -selection "$X_SELECTION" 2>/dev/null | base64)"
  120. echo -n "$1" | xclip -selection "$X_SELECTION" || die "Error: Could not copy data to the clipboard"
  121. (
  122. ( exec -a "$sleep_argv0" sleep "$CLIP_TIME" )
  123. local now="$(xclip -o -selection "$X_SELECTION" | base64)"
  124. [[ $now != $(echo -n "$1" | base64) ]] && before="$now"
  125. # It might be nice to programatically check to see if klipper exists,
  126. # as well as checking for other common clipboard managers. But for now,
  127. # this works fine -- if qdbus isn't there or if klipper isn't running,
  128. # this essentially becomes a no-op.
  129. #
  130. # Clipboard managers frequently write their history out in plaintext,
  131. # so we axe it here:
  132. qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory &>/dev/null
  133. echo "$before" | base64 -d | xclip -selection "$X_SELECTION"
  134. ) 2>/dev/null & disown
  135. echo "Copied $2 to clipboard. Will clear in $CLIP_TIME seconds."
  136. }
  137. tmpdir() {
  138. [[ -n $SECURE_TMPDIR ]] && return
  139. local warn=1
  140. [[ $1 == "nowarn" ]] && warn=0
  141. local template="$PROGRAM.XXXXXXXXXXXXX"
  142. if [[ -d /dev/shm && -w /dev/shm && -x /dev/shm ]]; then
  143. SECURE_TMPDIR="$(mktemp -d "/dev/shm/$template")"
  144. remove_tmpfile() {
  145. rm -rf "$SECURE_TMPDIR"
  146. }
  147. trap remove_tmpfile INT TERM EXIT
  148. else
  149. [[ $warn -eq 1 ]] && yesno "$(cat <<-_EOF
  150. Your system does not have /dev/shm, which means that it may
  151. be difficult to entirely erase the temporary non-encrypted
  152. password file after editing.
  153. Are you sure you would like to continue?
  154. _EOF
  155. )"
  156. SECURE_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/$template")"
  157. shred_tmpfile() {
  158. find "$SECURE_TMPDIR" -type f -exec $SHRED {} +
  159. rm -rf "$SECURE_TMPDIR"
  160. }
  161. trap shred_tmpfile INT TERM EXIT
  162. fi
  163. }
  164. GETOPT="getopt"
  165. SHRED="shred -f -z"
  166. source "$(dirname "$0")/platform/$(uname | cut -d _ -f 1 | tr '[:upper:]' '[:lower:]').sh" 2>/dev/null # PLATFORM_FUNCTION_FILE
  167. #
  168. # END platform definable
  169. #
  170. #
  171. # BEGIN subcommand functions
  172. #
  173. cmd_version() {
  174. cat <<-_EOF
  175. ============================================
  176. = pass: the standard unix password manager =
  177. = =
  178. = v1.6.5 =
  179. = =
  180. = Jason A. Donenfeld =
  181. = Jason@zx2c4.com =
  182. = =
  183. = http://www.passwordstore.org/ =
  184. ============================================
  185. _EOF
  186. }
  187. cmd_usage() {
  188. cmd_version
  189. echo
  190. cat <<-_EOF
  191. Usage:
  192. $PROGRAM init [--path=subfolder,-p subfolder] gpg-id...
  193. Initialize new password storage and use gpg-id for encryption.
  194. Selectively reencrypt existing passwords using new gpg-id.
  195. $PROGRAM [ls] [subfolder]
  196. List passwords.
  197. $PROGRAM find pass-names...
  198. List passwords that match pass-names.
  199. $PROGRAM [show] [--clip[=line-number],-c[line-number]] pass-name
  200. Show existing password and optionally put it on the clipboard.
  201. If put on the clipboard, it will be cleared in $CLIP_TIME seconds.
  202. $PROGRAM grep search-string
  203. Search for password files containing search-string when decrypted.
  204. $PROGRAM insert [--echo,-e | --multiline,-m] [--force,-f] pass-name
  205. Insert new password. Optionally, echo the password back to the console
  206. during entry. Or, optionally, the entry may be multiline. Prompt before
  207. overwriting existing password unless forced.
  208. $PROGRAM edit pass-name
  209. Insert a new password or edit an existing password using ${EDITOR:-vi}.
  210. $PROGRAM generate [--no-symbols,-n] [--clip,-c] [--in-place,-i | --force,-f] pass-name [pass-length]
  211. Generate a new password of pass-length (or $GENERATED_LENGTH if unspecified) with optionally no symbols.
  212. Optionally put it on the clipboard and clear board after $CLIP_TIME seconds.
  213. Prompt before overwriting existing password unless forced.
  214. Optionally replace only the first line of an existing file with a new password.
  215. $PROGRAM rm [--recursive,-r] [--force,-f] pass-name
  216. Remove existing password or directory, optionally forcefully.
  217. $PROGRAM mv [--force,-f] old-path new-path
  218. Renames or moves old-path to new-path, optionally forcefully, selectively reencrypting.
  219. $PROGRAM cp [--force,-f] old-path new-path
  220. Copies old-path to new-path, optionally forcefully, selectively reencrypting.
  221. $PROGRAM git git-command-args...
  222. If the password store is a git repository, execute a git command
  223. specified by git-command-args.
  224. $PROGRAM help
  225. Show this text.
  226. $PROGRAM version
  227. Show version information.
  228. More information may be found in the pass(1) man page.
  229. _EOF
  230. }
  231. cmd_init() {
  232. local opts id_path=""
  233. opts="$($GETOPT -o p: -l path: -n "$PROGRAM" -- "$@")"
  234. local err=$?
  235. eval set -- "$opts"
  236. while true; do case $1 in
  237. -p|--path) id_path="$2"; shift 2 ;;
  238. --) shift; break ;;
  239. esac done
  240. [[ $err -ne 0 || $# -lt 1 ]] && die "Usage: $PROGRAM $COMMAND [--path=subfolder,-p subfolder] gpg-id..."
  241. [[ -n $id_path ]] && check_sneaky_paths "$id_path"
  242. [[ -n $id_path && ! -d $PREFIX/$id_path && -e $PREFIX/$id_path ]] && die "Error: $PREFIX/$id_path exists but is not a directory."
  243. local gpg_id="$PREFIX/$id_path/.gpg-id"
  244. if [[ $# -eq 1 && -z $1 ]]; then
  245. [[ ! -f "$gpg_id" ]] && die "Error: $gpg_id does not exist and so cannot be removed."
  246. rm -v -f "$gpg_id" || exit 1
  247. if [[ -d $GIT_DIR ]]; then
  248. git rm -qr "$gpg_id"
  249. git_commit "Deinitialize ${gpg_id}${id_path:+ ($id_path)}."
  250. fi
  251. rmdir -p "${gpg_id%/*}" 2>/dev/null
  252. else
  253. mkdir -v -p "$PREFIX/$id_path"
  254. printf "%s\n" "$@" > "$gpg_id"
  255. local id_print="$(printf "%s, " "$@")"
  256. echo "Password store initialized for ${id_print%, }${id_path:+ ($id_path)}"
  257. git_add_file "$gpg_id" "Set GPG id to ${id_print%, }${id_path:+ ($id_path)}."
  258. fi
  259. reencrypt_path "$PREFIX/$id_path"
  260. git_add_file "$PREFIX/$id_path" "Reencrypt password store using new GPG id ${id_print%, }${id_path:+ ($id_path)}."
  261. }
  262. cmd_show() {
  263. local opts clip_location clip=0
  264. opts="$($GETOPT -o c:: -l clip:: -n "$PROGRAM" -- "$@")"
  265. local err=$?
  266. eval set -- "$opts"
  267. while true; do case $1 in
  268. -c|--clip) clip=1; clip_location="${2:-1}"; shift 2 ;;
  269. --) shift; break ;;
  270. esac done
  271. [[ $err -ne 0 ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [pass-name]"
  272. local path="$1"
  273. local passfile="$PREFIX/$path.gpg"
  274. check_sneaky_paths "$path"
  275. if [[ -f $passfile ]]; then
  276. if [[ $clip -eq 0 ]]; then
  277. $GPG -d "${GPG_OPTS[@]}" "$passfile" || exit $?
  278. else
  279. [[ $clip_location =~ ^[0-9]+$ ]] || die "Clip location '$clip_location' is not a number."
  280. local pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | tail -n +${clip_location} | head -n 1)"
  281. [[ -n $pass ]] || die "There is no password to put on the clipboard at line ${clip_location}."
  282. clip "$pass" "$path"
  283. fi
  284. elif [[ -d $PREFIX/$path ]]; then
  285. if [[ -z $path ]]; then
  286. echo "Password Store"
  287. else
  288. echo "${path%\/}"
  289. fi
  290. tree -C -l --noreport "$PREFIX/$path" | tail -n +2 | sed -E 's/\.gpg(\x1B\[[0-9]+m)?( ->|$)/\1\2/g' # remove .gpg at end of line, but keep colors
  291. elif [[ -z $path ]]; then
  292. die "Error: password store is empty. Try \"pass init\"."
  293. else
  294. die "Error: $path is not in the password store."
  295. fi
  296. }
  297. cmd_find() {
  298. [[ -z "$@" ]] && die "Usage: $PROGRAM $COMMAND pass-names..."
  299. IFS="," eval 'echo "Search Terms: $*"'
  300. local terms="*$(printf '%s*|*' "$@")"
  301. tree -C -l --noreport -P "${terms%|*}" --prune --matchdirs --ignore-case "$PREFIX" | tail -n +2 | sed -E 's/\.gpg(\x1B\[[0-9]+m)?( ->|$)/\1\2/g'
  302. }
  303. cmd_grep() {
  304. [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND search-string"
  305. local search="$1" passfile grepresults
  306. while read -r -d "" passfile; do
  307. grepresults="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | grep --color=always "$search")"
  308. [ $? -ne 0 ] && continue
  309. passfile="${passfile%.gpg}"
  310. passfile="${passfile#$PREFIX/}"
  311. local passfile_dir="${passfile%/*}/"
  312. [[ $passfile_dir == "${passfile}/" ]] && passfile_dir=""
  313. passfile="${passfile##*/}"
  314. printf "\e[94m%s\e[1m%s\e[0m:\n" "$passfile_dir" "$passfile"
  315. echo "$grepresults"
  316. done < <(find -L "$PREFIX" -iname '*.gpg' -print0)
  317. }
  318. cmd_insert() {
  319. local opts multiline=0 noecho=1 force=0
  320. opts="$($GETOPT -o mef -l multiline,echo,force -n "$PROGRAM" -- "$@")"
  321. local err=$?
  322. eval set -- "$opts"
  323. while true; do case $1 in
  324. -m|--multiline) multiline=1; shift ;;
  325. -e|--echo) noecho=0; shift ;;
  326. -f|--force) force=1; shift ;;
  327. --) shift; break ;;
  328. esac done
  329. [[ $err -ne 0 || ( $multiline -eq 1 && $noecho -eq 0 ) || $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND [--echo,-e | --multiline,-m] [--force,-f] pass-name"
  330. local path="${1%/}"
  331. local passfile="$PREFIX/$path.gpg"
  332. check_sneaky_paths "$path"
  333. [[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
  334. mkdir -p -v "$PREFIX/$(dirname "$path")"
  335. set_gpg_recipients "$(dirname "$path")"
  336. if [[ $multiline -eq 1 ]]; then
  337. echo "Enter contents of $path and press Ctrl+D when finished:"
  338. echo
  339. $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" || die "Password encryption aborted."
  340. elif [[ $noecho -eq 1 ]]; then
  341. local password password_again
  342. while true; do
  343. read -r -p "Enter password for $path: " -s password || exit 1
  344. echo
  345. read -r -p "Retype password for $path: " -s password_again || exit 1
  346. echo
  347. if [[ $password == "$password_again" ]]; then
  348. $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" <<<"$password" || die "Password encryption aborted."
  349. break
  350. else
  351. die "Error: the entered passwords do not match."
  352. fi
  353. done
  354. else
  355. local password
  356. read -r -p "Enter password for $path: " -e password
  357. $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" <<<"$password" || die "Password encryption aborted."
  358. fi
  359. git_add_file "$passfile" "Add given password for $path to store."
  360. }
  361. cmd_edit() {
  362. [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND pass-name"
  363. local path="${1%/}"
  364. check_sneaky_paths "$path"
  365. mkdir -p -v "$PREFIX/$(dirname "$path")"
  366. set_gpg_recipients "$(dirname "$path")"
  367. local passfile="$PREFIX/$path.gpg"
  368. tmpdir #Defines $SECURE_TMPDIR
  369. local tmp_file="$(mktemp -u "$SECURE_TMPDIR/XXXXXX")-${path//\//-}.txt"
  370. local action="Add"
  371. if [[ -f $passfile ]]; then
  372. $GPG -d -o "$tmp_file" "${GPG_OPTS[@]}" "$passfile" || exit 1
  373. action="Edit"
  374. fi
  375. ${EDITOR:-vi} "$tmp_file"
  376. [[ -f $tmp_file ]] || die "New password not saved."
  377. $GPG -d -o - "${GPG_OPTS[@]}" "$passfile" 2>/dev/null | diff - "$tmp_file" &>/dev/null && die "Password unchanged."
  378. while ! $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" "$tmp_file"; do
  379. yesno "GPG encryption failed. Would you like to try again?"
  380. done
  381. git_add_file "$passfile" "$action password for $path using ${EDITOR:-vi}."
  382. }
  383. cmd_generate() {
  384. local opts clip=0 force=0 symbols="-y" inplace=0
  385. opts="$($GETOPT -o ncif -l no-symbols,clip,in-place,force -n "$PROGRAM" -- "$@")"
  386. local err=$?
  387. eval set -- "$opts"
  388. while true; do case $1 in
  389. -n|--no-symbols) symbols=""; shift ;;
  390. -c|--clip) clip=1; shift ;;
  391. -f|--force) force=1; shift ;;
  392. -i|--in-place) inplace=1; shift ;;
  393. --) shift; break ;;
  394. esac done
  395. [[ $err -ne 0 || ( $# -ne 2 && $# -ne 1 ) || ( $force -eq 1 && $inplace -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--no-symbols,-n] [--clip,-c] [--in-place,-i | --force,-f] pass-name [pass-length]"
  396. local path="$1"
  397. local length="${2:-$GENERATED_LENGTH}"
  398. check_sneaky_paths "$path"
  399. [[ ! $length =~ ^[0-9]+$ ]] && die "Error: pass-length \"$length\" must be a number."
  400. mkdir -p -v "$PREFIX/$(dirname "$path")"
  401. set_gpg_recipients "$(dirname "$path")"
  402. local passfile="$PREFIX/$path.gpg"
  403. [[ $inplace -eq 0 && $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
  404. local pass="$(pwgen -s $symbols $length 1)"
  405. [[ -n $pass ]] || exit 1
  406. if [[ $inplace -eq 0 ]]; then
  407. $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" <<<"$pass" || die "Password encryption aborted."
  408. else
  409. local passfile_temp="${passfile}.tmp.${RANDOM}.${RANDOM}.${RANDOM}.${RANDOM}.--"
  410. if $GPG -d "${GPG_OPTS[@]}" "$passfile" | sed $'1c \\\n'"$(sed 's/[\/&]/\\&/g' <<<"$pass")"$'\n' | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile_temp" "${GPG_OPTS[@]}"; then
  411. mv "$passfile_temp" "$passfile"
  412. else
  413. rm -f "$passfile_temp"
  414. die "Could not reencrypt new password."
  415. fi
  416. fi
  417. local verb="Add"
  418. [[ $inplace -eq 1 ]] && verb="Replace"
  419. git_add_file "$passfile" "$verb generated password for ${path}."
  420. if [[ $clip -eq 0 ]]; then
  421. printf "\e[1m\e[37mThe generated password for \e[4m%s\e[24m is:\e[0m\n\e[1m\e[93m%s\e[0m\n" "$path" "$pass"
  422. else
  423. clip "$pass" "$path"
  424. fi
  425. }
  426. cmd_delete() {
  427. local opts recursive="" force=0
  428. opts="$($GETOPT -o rf -l recursive,force -n "$PROGRAM" -- "$@")"
  429. local err=$?
  430. eval set -- "$opts"
  431. while true; do case $1 in
  432. -r|--recursive) recursive="-r"; shift ;;
  433. -f|--force) force=1; shift ;;
  434. --) shift; break ;;
  435. esac done
  436. [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND [--recursive,-r] [--force,-f] pass-name"
  437. local path="$1"
  438. check_sneaky_paths "$path"
  439. local passdir="$PREFIX/${path%/}"
  440. local passfile="$PREFIX/$path.gpg"
  441. [[ -f $passfile && -d $passdir && $path == */ || ! -f $passfile ]] && passfile="$passdir"
  442. [[ -e $passfile ]] || die "Error: $path is not in the password store."
  443. [[ $force -eq 1 ]] || yesno "Are you sure you would like to delete $path?"
  444. rm $recursive -f -v "$passfile"
  445. if [[ -d $GIT_DIR && ! -e $passfile ]]; then
  446. git rm -qr "$passfile"
  447. git_commit "Remove $path from store."
  448. fi
  449. rmdir -p "${passfile%/*}" 2>/dev/null
  450. }
  451. cmd_copy_move() {
  452. local opts move=1 force=0
  453. [[ $1 == "copy" ]] && move=0
  454. shift
  455. opts="$($GETOPT -o f -l force -n "$PROGRAM" -- "$@")"
  456. local err=$?
  457. eval set -- "$opts"
  458. while true; do case $1 in
  459. -f|--force) force=1; shift ;;
  460. --) shift; break ;;
  461. esac done
  462. [[ $# -ne 2 ]] && die "Usage: $PROGRAM $COMMAND [--force,-f] old-path new-path"
  463. check_sneaky_paths "$@"
  464. local old_path="$PREFIX/${1%/}"
  465. local old_dir="$old_path"
  466. local new_path="$PREFIX/$2"
  467. if ! [[ -f $old_path.gpg && -d $old_path && $1 == */ || ! -f $old_path.gpg ]]; then
  468. old_dir="${old_path%/*}"
  469. old_path="${old_path}.gpg"
  470. fi
  471. echo "$old_path"
  472. [[ -e $old_path ]] || die "Error: $1 is not in the password store."
  473. mkdir -p -v "${new_path%/*}"
  474. [[ -d $old_path || -d $new_path || $new_path == */ ]] || new_path="${new_path}.gpg"
  475. local interactive="-i"
  476. [[ ! -t 0 || $force -eq 1 ]] && interactive="-f"
  477. if [[ $move -eq 1 ]]; then
  478. mv $interactive -v "$old_path" "$new_path" || exit 1
  479. [[ -e "$new_path" ]] && reencrypt_path "$new_path"
  480. if [[ -d $GIT_DIR && ! -e $old_path ]]; then
  481. git rm -qr "$old_path"
  482. git_add_file "$new_path" "Rename ${1} to ${2}."
  483. fi
  484. rmdir -p "$old_dir" 2>/dev/null
  485. else
  486. cp $interactive -r -v "$old_path" "$new_path" || exit 1
  487. [[ -e "$new_path" ]] && reencrypt_path "$new_path"
  488. git_add_file "$new_path" "Copy ${1} to ${2}."
  489. fi
  490. }
  491. cmd_git() {
  492. if [[ $1 == "init" ]]; then
  493. git "$@" || exit 1
  494. git_add_file "$PREFIX" "Add current contents of password store."
  495. echo '*.gpg diff=gpg' > "$PREFIX/.gitattributes"
  496. git_add_file .gitattributes "Configure git repository for gpg file diff."
  497. git config --local diff.gpg.binary true
  498. git config --local diff.gpg.textconv "$GPG -d ${GPG_OPTS[*]}"
  499. elif [[ -d $GIT_DIR ]]; then
  500. tmpdir nowarn #Defines $SECURE_TMPDIR. We don't warn, because at most, this only copies encrypted files.
  501. export TMPDIR="$SECURE_TMPDIR"
  502. git "$@"
  503. else
  504. die "Error: the password store is not a git repository. Try \"$PROGRAM git init\"."
  505. fi
  506. }
  507. #
  508. # END subcommand functions
  509. #
  510. PROGRAM="${0##*/}"
  511. COMMAND="$1"
  512. case "$1" in
  513. init) shift; cmd_init "$@" ;;
  514. help|--help) shift; cmd_usage "$@" ;;
  515. version|--version) shift; cmd_version "$@" ;;
  516. show|ls|list) shift; cmd_show "$@" ;;
  517. find|search) shift; cmd_find "$@" ;;
  518. grep) shift; cmd_grep "$@" ;;
  519. insert|add) shift; cmd_insert "$@" ;;
  520. edit) shift; cmd_edit "$@" ;;
  521. generate) shift; cmd_generate "$@" ;;
  522. delete|rm|remove) shift; cmd_delete "$@" ;;
  523. rename|mv) shift; cmd_copy_move "move" "$@" ;;
  524. copy|cp) shift; cmd_copy_move "copy" "$@" ;;
  525. git) shift; cmd_git "$@" ;;
  526. *) COMMAND="show"; cmd_show "$@" ;;
  527. esac
  528. exit 0