Guest User

pass

a guest
Oct 11th, 2020
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 32.08 KB | None | 0 0
  1. #!/usr/pkg/bin/bash                                                                                          
  2.  
  3. # Copyright (C) 2012 - 2018 Jason A. Donenfeld <Jason@zx2c4.com>. 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. EXTENSIONS="${PASSWORD_STORE_EXTENSIONS_DIR:-$PREFIX/.extensions}"
  17. X_SELECTION="${PASSWORD_STORE_X_SELECTION:-clipboard}"
  18. CLIP_TIME="${PASSWORD_STORE_CLIP_TIME:-45}"
  19. GENERATED_LENGTH="${PASSWORD_STORE_GENERATED_LENGTH:-25}"
  20. CHARACTER_SET="${PASSWORD_STORE_CHARACTER_SET:-[:graph:]}"
  21. CHARACTER_SET_NO_SYMBOLS="${PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS:-[:alnum:]}"
  22.  
  23. export GIT_CEILING_DIRECTORIES="$PREFIX/.."
  24.  
  25. #
  26. # BEGIN helper functions
  27. #
  28.  
  29. set_git() {
  30.         INNER_GIT_DIR="${1%/*}"
  31.         while [[ ! -d $INNER_GIT_DIR && ${INNER_GIT_DIR%/*}/ == "${PREFIX%/}/"* ]]; do
  32.                 INNER_GIT_DIR="${INNER_GIT_DIR%/*}"
  33.         done
  34.         [[ $(git -C "$INNER_GIT_DIR" rev-parse --is-inside-work-tree 2>/dev/null) == true ]] || INNER_GIT_DIR=""
  35. }
  36. git_add_file() {
  37.         [[ -n $INNER_GIT_DIR ]] || return
  38.         git -C "$INNER_GIT_DIR" add "$1" || return
  39.         [[ -n $(git -C "$INNER_GIT_DIR" status --porcelain "$1") ]] || return
  40.         git_commit "$2"
  41. }
  42. git_commit() {
  43.         local sign=""
  44.         [[ -n $INNER_GIT_DIR ]] || return
  45.         [[ $(git -C "$INNER_GIT_DIR" config --bool --get pass.signcommits) == "true" ]] && sign="-S"
  46.         git -C "$INNER_GIT_DIR" commit $sign -m "$1"
  47. }
  48. yesno() {
  49.         [[ -t 0 ]] || return 0
  50.         local response
  51.         read -r -p "$1 [y/N] " response
  52.         [[ $response == [yY] ]] || exit 1
  53. }
  54. die() {
  55.         echo "$@" >&2
  56.         exit 1
  57. }
  58. verify_file() {
  59.         [[ -n $PASSWORD_STORE_SIGNING_KEY ]] || return 0
  60.         [[ -f $1.sig ]] || die "Signature for $1 does not exist."
  61.         local fingerprints="$($GPG $PASSWORD_STORE_GPG_OPTS --verify --status-fd=1 "$1.sig" "$1" 2>/dev/null | sed -n 's/^\[GNUPG:\] VALIDSIG \([A-F0-9]\{40\}\) .* \([A-F0-9]\{40\}\)$/\1\n\2/p')"
  62.         local fingerprint found=0
  63.         for fingerprint in $PASSWORD_STORE_SIGNING_KEY; do
  64.                 [[ $fingerprint =~ ^[A-F0-9]{40}$ ]] || continue
  65.                 [[ $fingerprints == *$fingerprint* ]] && { found=1; break; }
  66.         done
  67.         [[ $found -eq 1 ]] || die "Signature for $1 is invalid."
  68. }
  69. set_gpg_recipients() {
  70.         GPG_RECIPIENT_ARGS=( )
  71.         GPG_RECIPIENTS=( )
  72.  
  73.         if [[ -n $PASSWORD_STORE_KEY ]]; then
  74.                 for gpg_id in $PASSWORD_STORE_KEY; do
  75.                         GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" )
  76.                         GPG_RECIPIENTS+=( "$gpg_id" )
  77.                 done
  78.                 return
  79.         fi
  80.  
  81.         local current="$PREFIX/$1"
  82.         while [[ $current != "$PREFIX" && ! -f $current/.gpg-id ]]; do
  83.                 current="${current%/*}"
  84.         done
  85.         current="$current/.gpg-id"
  86.  
  87.         if [[ ! -f $current ]]; then
  88.                 cat >&2 <<-_EOF
  89.                 Error: You must run:
  90.                     $PROGRAM init your-gpg-id
  91.                 before you may use the password store.
  92.  
  93.                 _EOF
  94.                 cmd_usage
  95.                 exit 1
  96.         fi
  97.  
  98.         verify_file "$current"
  99.  
  100.         local gpg_id
  101.         while read -r gpg_id; do
  102.                 GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" )
  103.                 GPG_RECIPIENTS+=( "$gpg_id" )
  104.         done < "$current"
  105. }
  106. reencrypt_path() {
  107.         local prev_gpg_recipients="" gpg_keys="" current_keys="" index passfile
  108.         local groups="$($GPG $PASSWORD_STORE_GPG_OPTS --list-config --with-colons | grep "^cfg:group:.*")"
  109.         while read -r -d "" passfile; do
  110.                 local passfile_dir="${passfile%/*}"
  111.                 passfile_dir="${passfile_dir#$PREFIX}"
  112.                 passfile_dir="${passfile_dir#/}"
  113.                 local passfile_display="${passfile#$PREFIX/}"
  114.                 passfile_display="${passfile_display%.gpg}"
  115.                 local passfile_temp="${passfile}.tmp.${RANDOM}.${RANDOM}.${RANDOM}.${RANDOM}.--"
  116.  
  117.                 set_gpg_recipients "$passfile_dir"
  118.                 if [[ $prev_gpg_recipients != "${GPG_RECIPIENTS[*]}" ]]; then
  119.                         for index in "${!GPG_RECIPIENTS[@]}"; do
  120.                                 local group="$(sed -n "s/^cfg:group:$(sed 's/[\/&]/\\&/g' <<<"${GPG_RECIPIENTS[$index]}"):\\(.*\\)\$/\\1/p" <<<"$groups" | head -n 1)"
  121.                                 [[ -z $group ]] && continue
  122.                                 IFS=";" eval 'GPG_RECIPIENTS+=( $group )' # http://unix.stackexchange.com/a/92190
  123.                                 unset "GPG_RECIPIENTS[$index]"
  124.                         done
  125.                         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)"
  126.                fi
  127.                current_keys="$(LC_ALL=C $GPG $PASSWORD_STORE_GPG_OPTS -v --no-secmem-warning --no-permission-warning --decrypt --list-only --keyid-format long "$passfile" 2>&1 | sed -n 's/^gpg: public key is \([A-F0-9]\+\)$/\1/p' | LC_ALL=C sort -u)"
  128.  
  129.                if [[ $gpg_keys != "$current_keys" ]]; then
  130.                        echo "$passfile_display: reencrypting to ${gpg_keys//$'\n'/ }"
  131.                        $GPG -d "${GPG_OPTS[@]}" "$passfile" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile_temp" "${GPG_OPTS[@]}" &&
  132.                        mv "$passfile_temp" "$passfile" || rm -f "$passfile_temp"
  133.                fi
  134.                prev_gpg_recipients="${GPG_RECIPIENTS[*]}"
  135.        done < <(find "$1" -path '*/.git' -prune -o -iname '*.gpg' -print0)
  136. }
  137. check_sneaky_paths() {
  138.        local path
  139.        for path in "$@"; do
  140.                [[ $path =~ /\.\.$ || $path =~ ^\.\./ || $path =~ /\.\./ || $path =~ ^\.\.$ ]] && die "Error: You've attempted to pass a sneaky path to pass. Go home."
  141.        done
  142. }
  143.  
  144. #
  145. # END helper functions
  146. #
  147.  
  148. #
  149. # BEGIN platform definable
  150. #
  151.  
  152. clip() {
  153.        # This base64 business is because bash cannot store binary data in a shell
  154.        # variable. Specifically, it cannot store nulls nor (non-trivally) store
  155.        # trailing new lines.
  156.        local sleep_argv0="password store sleep on display $DISPLAY"
  157.        pkill -f "^$sleep_argv0" 2>/dev/null && sleep 0.5
  158.        local before="$(xclip -o -selection "$X_SELECTION" 2>/dev/null | $BASE64)"
  159.        # Explicitly ignore SIGHUP because "disown" only works in interactive shells.
  160.        # Otherwise, in non-interactive cases like "xterm -e passmenu" or "xterm -e pass",
  161.        # which is needed to supply a terminal for pinentry when the script is called from
  162.        # a GUI, bash will kill xclip when the script finishes ie. before $CLIP_TIME has
  163.        # elapsed.
  164.        trap '' HUP
  165.        echo -n "$1" | xclip -selection "$X_SELECTION" || die "Error: Could not copy data to the clipboard"
  166.        (
  167.                ( exec -a "$sleep_argv0" bash <<<"trap 'kill %1' TERM; sleep '$CLIP_TIME' & wait" )
  168.                local now="$(xclip -o -selection "$X_SELECTION" | $BASE64)"
  169.                [[ $now != $(echo -n "$1" | $BASE64) ]] && before="$now"
  170.  
  171.                # It might be nice to programatically check to see if klipper exists,
  172.                # as well as checking for other common clipboard managers. But for now,
  173.                # this works fine -- if qdbus isn't there or if klipper isn't running,
  174.                # this essentially becomes a no-op.
  175.                #
  176.                # Clipboard managers frequently write their history out in plaintext,
  177.                # so we axe it here:
  178.                qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory &>/dev/null
  179.  
  180.                echo "$before" | $BASE64 -d | xclip -selection "$X_SELECTION"
  181.        ) >/dev/null 2>&1 &
  182.        echo "Copied $2 to clipboard. Will clear in $CLIP_TIME seconds."
  183. }
  184.  
  185. qrcode() {
  186.        if [[ -n $DISPLAY || -n $WAYLAND_DISPLAY ]]; then
  187.                if type feh >/dev/null 2>&1; then
  188.                        echo -n "$1" | qrencode --size 10 -o - | feh -x --title "pass: $2" -g +200+200 -
  189.                        return
  190.                elif type gm >/dev/null 2>&1; then
  191.                        echo -n "$1" | qrencode --size 10 -o - | gm display -title "pass: $2" -geometry +200+200 -
  192.                        return
  193.                elif type display >/dev/null 2>&1; then
  194.                        echo -n "$1" | qrencode --size 10 -o - | display -title "pass: $2" -geometry +200+200 -
  195.                        return
  196.                fi
  197.        fi
  198.        echo -n "$1" | qrencode -t utf8
  199. }
  200.  
  201. tmpdir() {
  202.        [[ -n $SECURE_TMPDIR ]] && return
  203.        local warn=1          
  204. [[ $1 == "nowarn" ]] && warn=0
  205.        local template="$PROGRAM.XXXXXXXXXXXXX"
  206.        if [[ -d /dev/shm && -w /dev/shm && -x /dev/shm ]]; then
  207.                SECURE_TMPDIR="$(mktemp -d "/dev/shm/$template")"
  208.                remove_tmpfile() {
  209.                        rm -rf "$SECURE_TMPDIR"
  210.                }
  211.                trap remove_tmpfile EXIT
  212.        else
  213.                [[ $warn -eq 1 ]] && yesno "$(cat <<-_EOF
  214.                 Your system does not have /dev/shm, which means that it may
  215.                 be difficult to entirely erase the temporary non-encrypted
  216.                 password file after editing.
  217.  
  218.                 Are you sure you would like to continue?
  219.                 _EOF
  220.                 )"
  221.                SECURE_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/$template")"
  222.                shred_tmpfile() {
  223.                        find "$SECURE_TMPDIR" -type f -exec $SHRED {} +
  224.                        rm -rf "$SECURE_TMPDIR"
  225.                }
  226.                trap shred_tmpfile EXIT
  227.        fi
  228.  
  229. }
  230. GETOPT="/usr/pkg/bin/getopt"
  231. SHRED="/bin/rm -f -P"
  232. BASE64="/usr/pkg/bin/base64"
  233.  
  234. source "$(dirname "$0")/platform/$(uname | cut -d _ -f 1 | tr '[:upper:]' '[:lower:]').sh" 2>/dev/null # PLATFORM_FUNCTION_FILE
  235.  
  236. #
  237. # END platform definable
  238. #
  239.  
  240.  
  241. #
  242. # BEGIN subcommand functions
  243. #
  244.  
  245. cmd_version() {
  246.        cat <<-_EOF
  247.        ============================================
  248.        = pass: the standard unix password manager =
  249.        =                                          =
  250.        =                  v1.7.3                  =
  251.        =                                          =
  252.        =             Jason A. Donenfeld           =
  253.        =               Jason@zx2c4.com            =
  254.        =                                          =
  255.        =      http://www.passwordstore.org/       =
  256.        ============================================                                                          
  257.                                                      
  258.  _EOF
  259. }
  260.  
  261. cmd_usage() {
  262.        cmd_version
  263.        echo
  264.        cat <<-_EOF
  265.        Usage:
  266.            $PROGRAM init [--path=subfolder,-p subfolder] gpg-id...
  267.                Initialize new password storage and use gpg-id for encryption.
  268.                Selectively reencrypt existing passwords using new gpg-id.
  269.            $PROGRAM [ls] [subfolder]
  270.                List passwords.
  271.            $PROGRAM find pass-names...
  272.                List passwords that match pass-names.
  273.            $PROGRAM [show] [--clip[=line-number],-c[line-number]] pass-name
  274.                Show existing password and optionally put it on the clipboard.
  275.                If put on the clipboard, it will be cleared in $CLIP_TIME seconds.
  276.            $PROGRAM grep [GREPOPTIONS] search-string
  277.                Search for password files containing search-string when decrypted.
  278.            $PROGRAM insert [--echo,-e | --multiline,-m] [--force,-f] pass-name
  279.                Insert new password. Optionally, echo the password back to the console
  280.                during entry. Or, optionally, the entry may be multiline. Prompt before
  281.                overwriting existing password unless forced.
  282.            $PROGRAM edit pass-name
  283.                Insert a new password or edit an existing password using ${EDITOR:-vi}.
  284.            $PROGRAM generate [--no-symbols,-n] [--clip,-c] [--in-place,-i | --force,-f] pass-name [pass-length]
  285.                Generate a new password of pass-length (or $GENERATED_LENGTH if unspecified) with optionally no symbols.
  286.                Optionally put it on the clipboard and clear board after $CLIP_TIME seconds.
  287.                Prompt before overwriting existing password unless forced.
  288.                Optionally replace only the first line of an existing file with a new password.
  289.            $PROGRAM rm [--recursive,-r] [--force,-f] pass-name
  290.                Remove existing password or directory, optionally forcefully.
  291.            $PROGRAM mv [--force,-f] old-path new-path
  292.                Renames or moves old-path to new-path, optionally forcefully, selectively reencrypting.
  293.            $PROGRAM cp [--force,-f] old-path new-path
  294.                Copies old-path to new-path, optionally forcefully, selectively reencrypting.
  295.            $PROGRAM git git-command-args...
  296.                If the password store is a git repository, execute a git command
  297.                specified by git-command-args.
  298.            $PROGRAM help
  299.                Show this text.
  300.            $PROGRAM version
  301.                Show version information.
  302. More information may be found in the pass(1) man page.
  303.        _EOF
  304. }
  305.  
  306. cmd_init() {
  307.        local opts id_path=""
  308.        opts="$($GETOPT -o p: -l path: -n "$PROGRAM" -- "$@")"
  309.        local err=$?
  310.        eval set -- "$opts"
  311.        while true; do case $1 in
  312.                -p|--path) id_path="$2"; shift 2 ;;
  313.                --) shift; break ;;
  314.        esac done
  315.  
  316.        [[ $err -ne 0 || $# -lt 1 ]] && die "Usage: $PROGRAM $COMMAND [--path=subfolder,-p subfolder] gpg-id..."
  317.        [[ -n $id_path ]] && check_sneaky_paths "$id_path"
  318.        [[ -n $id_path && ! -d $PREFIX/$id_path && -e $PREFIX/$id_path ]] && die "Error: $PREFIX/$id_path exists but is not a directory."
  319.  
  320.        local gpg_id="$PREFIX/$id_path/.gpg-id"
  321.        set_git "$gpg_id"
  322.  
  323.        if [[ $# -eq 1 && -z $1 ]]; then
  324.                [[ ! -f "$gpg_id" ]] && die "Error: $gpg_id does not exist and so cannot be removed."
  325.                rm -v -f "$gpg_id" || exit 1
  326.                if [[ -n $INNER_GIT_DIR ]]; then
  327.                        git -C "$INNER_GIT_DIR" rm -qr "$gpg_id"
  328.                        git_commit "Deinitialize ${gpg_id}${id_path:+ ($id_path)}."
  329.                fi
  330.                rmdir -p "${gpg_id%/*}" 2>/dev/null
  331.         else
  332.                 mkdir -p "$PREFIX/$id_path"
  333.                 printf "%s\n" "$@" > "$gpg_id"
  334.                 local id_print="$(printf "%s, " "$@")"
  335.                 echo "Password store initialized for ${id_print%, }${id_path:+ ($id_path)}"
  336.                 git_add_file "$gpg_id" "Set GPG id to ${id_print%, }${id_path:+ ($id_path)}."
  337.                 if [[ -n $PASSWORD_STORE_SIGNING_KEY ]]; then
  338.                         local signing_keys=( ) key
  339.                         for key in $PASSWORD_STORE_SIGNING_KEY; do
  340.                                 signing_keys+=( --default-key $key )
  341.                         done
  342.                         $GPG "${GPG_OPTS[@]}" "${signing_keys[@]}" --detach-sign "$gpg_id" || die "Could not sign .gpg_id."
  343.                         key="$($GPG --verify --status-fd=1 "$gpg_id.sig" "$gpg_id" 2>/dev/null | sed -n 's/^\[GNUPG:\] VALIDSIG [A-F0-9]\{40\} .* \([A-F0-9]\{40\}\)$/\1/p')"
  344.                         [[ -n $key ]] || die "Signing of .gpg_id unsuccessful."
  345.                         git_add_file "$gpg_id.sig" "Signing new GPG id with ${key//[$IFS]/,}."
  346.                 fi
  347.         fi
  348.  
  349.         reencrypt_path "$PREFIX/$id_path"
  350.         git_add_file "$PREFIX/$id_path" "Reencrypt password store using new GPG id ${id_print%, }${id_path:+ ($id_path)}."              
  351. }
  352.  
  353. cmd_show() {
  354.         local opts selected_line clip=0 qrcode=0
  355.         opts="$($GETOPT -o q::c:: -l qrcode::,clip:: -n "$PROGRAM" -- "$@")"
  356.         local err=$?
  357.         eval set -- "$opts"
  358.         while true; do case $1 in
  359.                 -q|--qrcode) qrcode=1; selected_line="${2:-1}"; shift 2 ;;
  360.                 -c|--clip) clip=1; selected_line="${2:-1}"; shift 2 ;;
  361.                 --) shift; break ;;
  362.         esac done
  363.  
  364.         [[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [pass-name]"
  365.  
  366.         local pass
  367.         local path="$1"
  368.         local passfile="$PREFIX/$path.gpg"
  369.         check_sneaky_paths "$path"
  370.         if [[ -f $passfile ]]; then
  371.                 if [[ $clip -eq 0 && $qrcode -eq 0 ]]; then
  372.                         pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $?
  373.                         echo "$pass" | $BASE64 -d
  374.                 else
  375.                         [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number."
  376.                         pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | tail -n +${selected_line} | head -n 1)" || exit $?
  377.                         [[ -n $pass ]] || die "There is no password to put on the clipboard at line ${selected_line}."
  378.                         if [[ $clip -eq 1 ]]; then
  379.                                 clip "$pass" "$path"
  380.                         elif [[ $qrcode -eq 1 ]]; then
  381.                                 qrcode "$pass" "$path"
  382.                         fi
  383.                 fi
  384.         elif [[ -d $PREFIX/$path ]]; then
  385.                 if [[ -z $path ]]; then
  386.                         echo "Password Store"
  387.                 else
  388.                         echo "${path%\/}"
  389.                 fi
  390.                 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
  391.         elif [[ -z $path ]]; then
  392.                 die "Error: password store is empty. Try \"pass init\"."
  393.         else
  394.                 die "Error: $path is not in the password store."
  395.         fi
  396. }
  397.  
  398. cmd_find() {                                                                                                  
  399.         [[ $# -eq 0 ]] && die "Usage: $PROGRAM $COMMAND pass-names..."
  400.          IFS="," eval 'echo "Search Terms: $*"'
  401.         local terms="*$(printf '%s*|*' "$@")"
  402.         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'
  403. }
  404.  
  405. cmd_grep() {
  406.         [[ $# -lt 1 ]] && die "Usage: $PROGRAM $COMMAND [GREPOPTIONS] search-string"
  407.         local passfile grepresults
  408.         while read -r -d "" passfile; do
  409.                 grepresults="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | grep --color=always "$@")"
  410.                 [[ $? -ne 0 ]] && continue
  411.                 passfile="${passfile%.gpg}"
  412.                 passfile="${passfile#$PREFIX/}"
  413.                 local passfile_dir="${passfile%/*}/"
  414.                 [[ $passfile_dir == "${passfile}/" ]] && passfile_dir=""
  415.                 passfile="${passfile##*/}"
  416.                printf "\e[94m%s\e[1m%s\e[0m:\n" "$passfile_dir" "$passfile"
  417.                echo "$grepresults"
  418.        done < <(find -L "$PREFIX" -path '*/.git' -prune -o -iname '*.gpg' -print0)
  419. }
  420.  
  421. cmd_insert() {
  422.        local opts multiline=0 noecho=1 force=0
  423.        opts="$($GETOPT -o mef -l multiline,echo,force -n "$PROGRAM" -- "$@")"
  424.        local err=$?
  425.        eval set -- "$opts"
  426.        while true; do case $1 in
  427.                -m|--multiline) multiline=1; shift ;;
  428.                -e|--echo) noecho=0; shift ;;
  429.                -f|--force) force=1; shift ;;
  430.                --) shift; break ;;
  431.        esac done
  432.  
  433.        [[ $err -ne 0 || ( $multiline -eq 1 && $noecho -eq 0 ) || $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND [--echo,-e | --multiline,-m] [--force,-f] pass-name"
  434.        local path="${1%/}"
  435.        local passfile="$PREFIX/$path.gpg"
  436.        check_sneaky_paths "$path"
  437.        set_git "$passfile"
  438.  
  439.        [[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
  440.  
  441.        mkdir -p "$PREFIX/$(dirname -- "$path")"
  442.        set_gpg_recipients "$(dirname -- "$path")"
  443.  
  444.        if [[ $multiline -eq 1 ]]; then
  445.                echo "Enter contents of $path and press Ctrl+D when finished:"
  446.                echo
  447.                $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" || die "Password encryption aborted."
  448.        elif [[ $noecho -eq 1 ]]; then
  449.                local password password_again
  450.                while true; do  
  451. read -r -p "Enter password for $path: " -s password || exit 1
  452.                        echo
  453.                        read -r -p "Retype password for $path: " -s password_again || exit 1
  454.                        echo
  455.                        if [[ $password == "$password_again" ]]; then
  456.                                echo "$password" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" || die "Password encryption aborted."
  457.                                break
  458.                        else
  459.                                die "Error: the entered passwords do not match."
  460.                        fi
  461.                done
  462.        else
  463.                local password
  464.                read -r -p "Enter password for $path: " -e password
  465.                echo "$password" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" || die "Password encryption aborted."
  466.        fi
  467.        git_add_file "$passfile" "Add given password for $path to store."
  468. }
  469.  
  470. cmd_edit() {
  471.        [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND pass-name"
  472.  
  473.        local path="${1%/}"
  474.        check_sneaky_paths "$path"
  475.        mkdir -p "$PREFIX/$(dirname -- "$path")"
  476.        set_gpg_recipients "$(dirname -- "$path")"
  477.        local passfile="$PREFIX/$path.gpg"
  478.        set_git "$passfile"
  479.  
  480.        tmpdir #Defines $SECURE_TMPDIR
  481.        local tmp_file="$(mktemp -u "$SECURE_TMPDIR/XXXXXX")-${path//\//-}.txt"
  482.  
  483.         local action="Add"
  484.         if [[ -f $passfile ]]; then
  485.                 $GPG -d -o "$tmp_file" "${GPG_OPTS[@]}" "$passfile" || exit 1
  486.                 action="Edit"
  487.         fi
  488.         ${EDITOR:-vi} "$tmp_file"
  489.         [[ -f $tmp_file ]] || die "New password not saved."
  490.         $GPG -d -o - "${GPG_OPTS[@]}" "$passfile" 2>/dev/null | diff - "$tmp_file" &>/dev/null && die "Password unchanged."
  491.         while ! $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" "$tmp_file"; do
  492.                 yesno "GPG encryption failed. Would you like to try again?"
  493.         done
  494.         git_add_file "$passfile" "$action password for $path using ${EDITOR:-vi}."
  495. }
  496.  
  497. cmd_generate() {
  498.         local opts qrcode=0 clip=0 force=0 characters="$CHARACTER_SET" inplace=0 pass
  499.         opts="$($GETOPT -o nqcif -l no-symbols,qrcode,clip,in-place,force -n "$PROGRAM" -- "$@")"
  500.         local err=$?
  501.         eval set -- "$opts"  
  502.  while true; do case $1 in
  503.                 -n|--no-symbols) characters="$CHARACTER_SET_NO_SYMBOLS"; shift ;;
  504.                 -q|--qrcode) qrcode=1; shift ;;
  505.                 -c|--clip) clip=1; shift ;;
  506.                 -f|--force) force=1; shift ;;
  507.                 -i|--in-place) inplace=1; shift ;;
  508.                 --) shift; break ;;
  509.         esac done
  510.  
  511.         [[ $err -ne 0 || ( $# -ne 2 && $# -ne 1 ) || ( $force -eq 1 && $inplace -eq 1 ) || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--no-symbols,-n] [--clip,-c] [--qrcode,-q] [--in-place,-i | --force,-f] pass-name [pass-length]"
  512.         local path="$1"
  513.         local length="${2:-$GENERATED_LENGTH}"
  514.         check_sneaky_paths "$path"
  515.         [[ $length =~ ^[0-9]+$ ]] || die "Error: pass-length \"$length\" must be a number."
  516.         [[ $length -gt 0 ]] || die "Error: pass-length must be greater than zero."
  517.         mkdir -p "$PREFIX/$(dirname -- "$path")"
  518.         set_gpg_recipients "$(dirname -- "$path")"
  519.         local passfile="$PREFIX/$path.gpg"
  520.         set_git "$passfile"
  521.  
  522.         [[ $inplace -eq 0 && $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
  523.  
  524.         read -r -n $length pass < <(LC_ALL=C tr -dc "$characters" < /dev/urandom)
  525.         [[ ${#pass} -eq $length ]] || die "Could not generate password from /dev/urandom."
  526.         if [[ $inplace -eq 0 ]]; then
  527.                 echo "$pass" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" || die "Password encryption aborted."
  528.         else
  529.                 local passfile_temp="${passfile}.tmp.${RANDOM}.${RANDOM}.${RANDOM}.${RANDOM}.--"
  530.                 if { echo "$pass"; $GPG -d "${GPG_OPTS[@]}" "$passfile" | tail -n +2; } | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile_temp" "${GPG_OPTS[@]}"; then
  531.                         mv "$passfile_temp" "$passfile"
  532.                 else
  533.                         rm -f "$passfile_temp"
  534.                         die "Could not reencrypt new password."
  535.                 fi
  536.         fi
  537.         local verb="Add"
  538.         [[ $inplace -eq 1 ]] && verb="Replace"
  539.         git_add_file "$passfile" "$verb generated password for ${path}."
  540.  
  541.         if [[ $clip -eq 1 ]]; then
  542.                 clip "$pass" "$path"
  543.         elif [[ $qrcode -eq 1 ]]; then
  544.                 qrcode "$pass" "$path"
  545.         else
  546.                 printf "\e[1mThe generated password for \e[4m%s\e[24m is:\e[0m\n\e[1m\e[93m%s\e[0m\n" "$path" "$pass"
  547.         fi
  548. }
  549.                      
  550. cmd_delete() {
  551.         local opts recursive="" force=0
  552.         opts="$($GETOPT -o rf -l recursive,force -n "$PROGRAM" -- "$@")"
  553.         local err=$?
  554.         eval set -- "$opts"
  555.         while true; do case $1 in
  556.                 -r|--recursive) recursive="-r"; shift ;;
  557.                 -f|--force) force=1; shift ;;
  558.                 --) shift; break ;;
  559.         esac done
  560.         [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND [--recursive,-r] [--force,-f] pass-name"
  561.         local path="$1"
  562.         check_sneaky_paths "$path"
  563.  
  564.         local passdir="$PREFIX/${path%/}"
  565.         local passfile="$PREFIX/$path.gpg"
  566.         [[ -f $passfile && -d $passdir && $path == */ || ! -f $passfile ]] && passfile="${passdir%/}/"
  567.         [[ -e $passfile ]] || die "Error: $path is not in the password store."
  568.         set_git "$passfile"
  569.  
  570.         [[ $force -eq 1 ]] || yesno "Are you sure you would like to delete $path?"
  571.  
  572.         rm $recursive -f -v "$passfile"
  573.         set_git "$passfile"
  574.         if [[ -n $INNER_GIT_DIR && ! -e $passfile ]]; then
  575.                 git -C "$INNER_GIT_DIR" rm -qr "$passfile"
  576.                 set_git "$passfile"
  577.                 git_commit "Remove $path from store."
  578.         fi
  579.         rmdir -p "${passfile%/*}" 2>/dev/null
  580. }
  581.  
  582. cmd_copy_move() {
  583.         local opts move=1 force=0
  584.         [[ $1 == "copy" ]] && move=0
  585.         shift
  586.         opts="$($GETOPT -o f -l force -n "$PROGRAM" -- "$@")"
  587.         local err=$?
  588.         eval set -- "$opts"
  589.         while true; do case $1 in
  590.                 -f|--force) force=1; shift ;;
  591.                 --) shift; break ;;
  592.         esac done
  593.         [[ $# -ne 2 ]] && die "Usage: $PROGRAM $COMMAND [--force,-f] old-path new-path"
  594.         check_sneaky_paths "$@"
  595.         local old_path="$PREFIX/${1%/}"
  596.         local old_dir="$old_path"
  597.         local new_path="$PREFIX/$2"
  598.  
  599.         if ! [[ -f $old_path.gpg && -d $old_path && $1 == */ || ! -f $old_path.gpg ]]; then
  600.                 old_dir="${old_path%/*}"
  601.                 old_path="${old_path}.gpg"
  602.         fi
  603.         echo "$old_path"                                                                                      
  604.    [[ -e $old_path ]] || die "Error: $1 is not in the password store."
  605.  
  606.         mkdir -p "${new_path%/*}"
  607.         [[ -d $old_path || -d $new_path || $new_path == */ ]] || new_path="${new_path}.gpg"
  608.  
  609.         local interactive="-i"
  610.         [[ ! -t 0 || $force -eq 1 ]] && interactive="-f"
  611.  
  612.         set_git "$new_path"
  613.         if [[ $move -eq 1 ]]; then
  614.                 mv $interactive -v "$old_path" "$new_path" || exit 1
  615.                 [[ -e "$new_path" ]] && reencrypt_path "$new_path"
  616.  
  617.                 set_git "$new_path"
  618.                 if [[ -n $INNER_GIT_DIR && ! -e $old_path ]]; then
  619.                         git -C "$INNER_GIT_DIR" rm -qr "$old_path" 2>/dev/null
  620.                         set_git "$new_path"
  621.                         git_add_file "$new_path" "Rename ${1} to ${2}."
  622.                 fi
  623.                 set_git "$old_path"
  624.                 if [[ -n $INNER_GIT_DIR && ! -e $old_path ]]; then
  625.                         git -C "$INNER_GIT_DIR" rm -qr "$old_path" 2>/dev/null
  626.                         set_git "$old_path"
  627.                         [[ -n $(git -C "$INNER_GIT_DIR" status --porcelain "$old_path") ]] && git_commit "Remove ${1}."
  628.                 fi
  629.                 rmdir -p "$old_dir" 2>/dev/null
  630.         else
  631.                 cp $interactive -r -v "$old_path" "$new_path" || exit 1
  632.                 [[ -e "$new_path" ]] && reencrypt_path "$new_path"
  633.                 git_add_file "$new_path" "Copy ${1} to ${2}."
  634.         fi
  635. }
  636.  
  637. cmd_git() {
  638.         set_git "$PREFIX/"
  639.         if [[ $1 == "init" ]]; then
  640.                 INNER_GIT_DIR="$PREFIX"
  641.                 git -C "$INNER_GIT_DIR" "$@" || exit 1
  642.                 git_add_file "$PREFIX" "Add current contents of password store."
  643.  
  644.                 echo '*.gpg diff=gpg' > "$PREFIX/.gitattributes"
  645.                 git_add_file .gitattributes "Configure git repository for gpg file diff."
  646.                 git -C "$INNER_GIT_DIR" config --local diff.gpg.binary true
  647.                 git -C "$INNER_GIT_DIR" config --local diff.gpg.textconv "$GPG -d ${GPG_OPTS[*]}"
  648.         elif [[ -n $INNER_GIT_DIR ]]; then
  649.                 tmpdir nowarn #Defines $SECURE_TMPDIR. We don't warn, because at most, this only copies encrypted files.
  650.                 export TMPDIR="$SECURE_TMPDIR"
  651.                 git -C "$INNER_GIT_DIR" "$@"
  652.         else
  653.                 die "Error: the password store is not a git repository. Try \"$PROGRAM git init\"."
  654.         fi
  655. }          
  656. cmd_extension_or_show() {
  657.         if ! cmd_extension "$@"; then
  658.                 COMMAND="show"
  659.                 cmd_show "$@"
  660.         fi
  661. }
  662.  
  663. SYSTEM_EXTENSION_DIR=""
  664. cmd_extension() {
  665.         check_sneaky_paths "$1"
  666.         local user_extension system_extension extension
  667.         [[ -n $SYSTEM_EXTENSION_DIR ]] && system_extension="$SYSTEM_EXTENSION_DIR/$1.bash"
  668.         [[ $PASSWORD_STORE_ENABLE_EXTENSIONS == true ]] && user_extension="$EXTENSIONS/$1.bash"
  669.         if [[ -n $user_extension && -f $user_extension && -x $user_extension ]]; then
  670.                 verify_file "$user_extension"
  671.                 extension="$user_extension"
  672.         elif [[ -n $system_extension && -f $system_extension && -x $system_extension ]]; then
  673.                 extension="$system_extension"
  674.         else
  675.                 return 1
  676.         fi
  677.         shift
  678.         source "$extension" "$@"
  679.         return 0
  680. }
  681.  
  682. #
  683. # END subcommand functions
  684. #
  685.  
  686. PROGRAM="${0##*/}"
  687. COMMAND="$1"
  688.  
  689. case "$1" in
  690.         init) shift;                    cmd_init "$@" ;;
  691.         help|--help) shift;             cmd_usage "$@" ;;
  692.         version|--version) shift;       cmd_version "$@" ;;
  693.         show|ls|list) shift;            cmd_show "$@" ;;
  694.         find|search) shift;             cmd_find "$@" ;;
  695.         grep) shift;                    cmd_grep "$@" ;;
  696.         insert|add) shift;              cmd_insert "$@" ;;
  697.         edit) shift;                    cmd_edit "$@" ;;
  698.         generate) shift;                cmd_generate "$@" ;;
  699.         delete|rm|remove) shift;        cmd_delete "$@" ;;
  700.         rename|mv) shift;               cmd_copy_move "move" "$@" ;;
  701.         copy|cp) shift;                 cmd_copy_move "copy" "$@" ;;
  702.         git) shift;                     cmd_git "$@" ;;
  703.         *)                              cmd_extension_or_show "$@" ;;
  704. esac
  705. exit 0                                                                                                        
  706.                    
Add Comment
Please, Sign In to add comment