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

UCC git Repository :: git.ucc.asn.au