From f262d810782904db38e059808bb6bad9bf63406a Mon Sep 17 00:00:00 2001 From: David Adam Date: Fri, 17 Apr 2015 11:06:52 +0800 Subject: [PATCH] pass: add local version of pass --- pass/bin/pass | 594 +++++++++++++++++++++++++++++++++++++ pass/share/man/man1/pass.1 | 450 ++++++++++++++++++++++++++++ 2 files changed, 1044 insertions(+) create mode 100755 pass/bin/pass create mode 100644 pass/share/man/man1/pass.1 diff --git a/pass/bin/pass b/pass/bin/pass new file mode 100755 index 0000000..564bec0 --- /dev/null +++ b/pass/bin/pass @@ -0,0 +1,594 @@ +#!/usr/bin/env bash + +# Copyright (C) 2012 - 2014 Jason A. Donenfeld . All Rights Reserved. +# This file is licensed under the GPLv2+. Please see COPYING for more information. + +umask "${PASSWORD_STORE_UMASK:-077}" +set -o pipefail + +GPG_OPTS=( $PASSWORD_STORE_GPG_OPTS "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" ) +GPG="gpg" +export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}" +which gpg2 &>/dev/null && GPG="gpg2" +[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" ) + +PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}" +X_SELECTION="${PASSWORD_STORE_X_SELECTION:-clipboard}" +CLIP_TIME="${PASSWORD_STORE_CLIP_TIME:-45}" + +export GIT_DIR="${PASSWORD_STORE_GIT:-$PREFIX}/.git" +export GIT_WORK_TREE="${PASSWORD_STORE_GIT:-$PREFIX}" + +# +# BEGIN helper functions +# + +git_add_file() { + [[ -d $GIT_DIR ]] || return + git add "$1" || return + [[ -n $(git status --porcelain "$1") ]] || return + git_commit "$2" +} +git_commit() { + local sign="" + [[ -d $GIT_DIR ]] || return + [[ $(git config --bool --get pass.signcommits) == "true" ]] && sign="-S" + git commit $sign -m "$1" +} +yesno() { + [[ -t 0 ]] || return 0 + local response + read -r -p "$1 [y/N] " response + [[ $response == [yY] ]] || exit 1 +} +die() { + echo "$@" >&2 + exit 1 +} +set_gpg_recipients() { + GPG_RECIPIENT_ARGS=( ) + GPG_RECIPIENTS=( ) + + if [[ -n $PASSWORD_STORE_KEY ]]; then + for gpg_id in $PASSWORD_STORE_KEY; do + GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" ) + GPG_RECIPIENTS+=( "$gpg_id" ) + done + return + fi + + local current="$PREFIX/$1" + while [[ $current != "$PREFIX" && ! -f $current/.gpg-id ]]; do + current="${current%/*}" + done + current="$current/.gpg-id" + + if [[ ! -f $current ]]; then + cat >&2 <<-_EOF + Error: You must run: + $PROGRAM init your-gpg-id + before you may use the password store. + + _EOF + cmd_usage + exit 1 + fi + + local gpg_id + while read -r gpg_id; do + GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" ) + GPG_RECIPIENTS+=( "$gpg_id" ) + done < "$current" +} + +reencrypt_path() { + local prev_gpg_recipients="" gpg_keys="" current_keys="" index passfile + local groups="$($GPG $PASSWORD_STORE_GPG_OPTS --list-config --with-colons | grep "^cfg:group:.*")" + while read -r -d "" passfile; do + local passfile_dir="${passfile%/*}" + passfile_dir="${passfile_dir#$PREFIX}" + passfile_dir="${passfile_dir#/}" + local passfile_display="${passfile#$PREFIX/}" + passfile_display="${passfile_display%.gpg}" + local passfile_temp="${passfile}.tmp.${RANDOM}.${RANDOM}.${RANDOM}.${RANDOM}.--" + + set_gpg_recipients "$passfile_dir" + if [[ $prev_gpg_recipients != "${GPG_RECIPIENTS[*]}" ]]; then + for index in "${!GPG_RECIPIENTS[@]}"; do + local group="$(sed -n "s/^cfg:group:$(sed 's/[\/&]/\\&/g' <<<"${GPG_RECIPIENTS[$index]}"):\\(.*\\)\$/\\1/p" <<<"$groups" | head -n 1)" + [[ -z $group ]] && continue + IFS=";" eval 'GPG_RECIPIENTS+=( $group )' # http://unix.stackexchange.com/a/92190 + unset GPG_RECIPIENTS[$index] + done + 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)" + fi + 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)" + + if [[ $gpg_keys != "$current_keys" ]]; then + echo "$passfile_display: reencrypting to ${gpg_keys//$'\n'/ }" + $GPG -d "${GPG_OPTS[@]}" "$passfile" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile_temp" "${GPG_OPTS[@]}" && + mv "$passfile_temp" "$passfile" || rm -f "$passfile_temp" + fi + prev_gpg_recipients="${GPG_RECIPIENTS[*]}" + done < <(find "$1" -iname '*.gpg' -print0) +} +check_sneaky_paths() { + local path + for path in "$@"; do + [[ $path =~ /\.\.$ || $path =~ ^\.\./ || $path =~ /\.\./ || $path =~ ^\.\.$ ]] && die "Error: You've attempted to pass a sneaky path to pass. Go home." + done +} + +# +# END helper functions +# + +# +# BEGIN platform definable +# + +clip() { + # This base64 business is because bash cannot store binary data in a shell + # variable. Specifically, it cannot store nulls nor (non-trivally) store + # trailing new lines. + local sleep_argv0="password store sleep on display $DISPLAY" + pkill -f "^$sleep_argv0" 2>/dev/null && sleep 0.5 + local before="$(xclip -o -selection "$X_SELECTION" 2>/dev/null | base64)" + echo -n "$1" | xclip -selection "$X_SELECTION" || die "Error: Could not copy data to the clipboard" + ( + ( exec -a "$sleep_argv0" sleep "$CLIP_TIME" ) + local now="$(xclip -o -selection "$X_SELECTION" | base64)" + [[ $now != $(echo -n "$1" | base64) ]] && before="$now" + + # It might be nice to programatically check to see if klipper exists, + # as well as checking for other common clipboard managers. But for now, + # this works fine -- if qdbus isn't there or if klipper isn't running, + # this essentially becomes a no-op. + # + # Clipboard managers frequently write their history out in plaintext, + # so we axe it here: + qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory &>/dev/null + + echo "$before" | base64 -d | xclip -selection "$X_SELECTION" + ) 2>/dev/null & disown + echo "Copied $2 to clipboard. Will clear in $CLIP_TIME seconds." +} +tmpdir() { + [[ -n $SECURE_TMPDIR ]] && return + local warn=1 + [[ $1 == "nowarn" ]] && warn=0 + local template="$PROGRAM.XXXXXXXXXXXXX" + if [[ -d /dev/shm && -w /dev/shm && -x /dev/shm ]]; then + SECURE_TMPDIR="$(mktemp -d "/dev/shm/$template")" + remove_tmpfile() { + rm -rf "$SECURE_TMPDIR" + } + trap remove_tmpfile INT TERM EXIT + else + [[ $warn -eq 1 ]] && yesno "$(cat <<-_EOF + Your system does not have /dev/shm, which means that it may + be difficult to entirely erase the temporary non-encrypted + password file after editing. + + Are you sure you would like to continue? + _EOF + )" + SECURE_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/$template")" + shred_tmpfile() { + find "$SECURE_TMPDIR" -type f -exec $SHRED {} + + rm -rf "$SECURE_TMPDIR" + } + trap shred_tmpfile INT TERM EXIT + fi + +} +GETOPT="getopt" +SHRED="shred -f -z" + + +# +# END platform definable +# + + +# +# BEGIN subcommand functions +# + +cmd_version() { + cat <<-_EOF + ============================================ + = pass: the standard unix password manager = + = = + = v1.6.5 = + = = + = Jason A. Donenfeld = + = Jason@zx2c4.com = + = = + = http://www.passwordstore.org/ = + ============================================ + _EOF +} + +cmd_usage() { + cmd_version + echo + cat <<-_EOF + Usage: + $PROGRAM init [--path=subfolder,-p subfolder] gpg-id... + Initialize new password storage and use gpg-id for encryption. + Selectively reencrypt existing passwords using new gpg-id. + $PROGRAM [ls] [subfolder] + List passwords. + $PROGRAM find pass-names... + List passwords that match pass-names. + $PROGRAM [show] [--clip,-c] pass-name + Show existing password and optionally put it on the clipboard. + If put on the clipboard, it will be cleared in $CLIP_TIME seconds. + $PROGRAM grep search-string + Search for password files containing search-string when decrypted. + $PROGRAM insert [--echo,-e | --multiline,-m] [--force,-f] pass-name + Insert new password. Optionally, echo the password back to the console + during entry. Or, optionally, the entry may be multiline. Prompt before + overwriting existing password unless forced. + $PROGRAM edit pass-name + Insert a new password or edit an existing password using ${EDITOR:-vi}. + $PROGRAM generate [--no-symbols,-n] [--clip,-c] [--in-place,-i | --force,-f] pass-name pass-length + Generate a new password of pass-length with optionally no symbols. + Optionally put it on the clipboard and clear board after $CLIP_TIME seconds. + Prompt before overwriting existing password unless forced. + Optionally replace only the first line of an existing file with a new password. + $PROGRAM rm [--recursive,-r] [--force,-f] pass-name + Remove existing password or directory, optionally forcefully. + $PROGRAM mv [--force,-f] old-path new-path + Renames or moves old-path to new-path, optionally forcefully, selectively reencrypting. + $PROGRAM cp [--force,-f] old-path new-path + Copies old-path to new-path, optionally forcefully, selectively reencrypting. + $PROGRAM git git-command-args... + If the password store is a git repository, execute a git command + specified by git-command-args. + $PROGRAM help + Show this text. + $PROGRAM version + Show version information. + + More information may be found in the pass(1) man page. + _EOF +} + +cmd_init() { + local opts id_path="" + opts="$($GETOPT -o p: -l path: -n "$PROGRAM" -- "$@")" + local err=$? + eval set -- "$opts" + while true; do case $1 in + -p|--path) id_path="$2"; shift 2 ;; + --) shift; break ;; + esac done + + [[ $err -ne 0 || $# -lt 1 ]] && die "Usage: $PROGRAM $COMMAND [--path=subfolder,-p subfolder] gpg-id..." + [[ -n $id_path ]] && check_sneaky_paths "$id_path" + [[ -n $id_path && ! -d $PREFIX/$id_path && -e $PREFIX/$id_path ]] && die "Error: $PREFIX/$id_path exists but is not a directory." + + local gpg_id="$PREFIX/$id_path/.gpg-id" + + if [[ $# -eq 1 && -z $1 ]]; then + [[ ! -f "$gpg_id" ]] && die "Error: $gpg_id does not exist and so cannot be removed." + rm -v -f "$gpg_id" || exit 1 + if [[ -d $GIT_DIR ]]; then + git rm -qr "$gpg_id" + git_commit "Deinitialize ${gpg_id}." + fi + rmdir -p "${gpg_id%/*}" 2>/dev/null + else + mkdir -v -p "$PREFIX/$id_path" + printf "%s\n" "$@" > "$gpg_id" + local id_print="$(printf "%s, " "$@")" + echo "Password store initialized for ${id_print%, }" + git_add_file "$gpg_id" "Set GPG id to ${id_print%, }." + fi + + reencrypt_path "$PREFIX/$id_path" + git_add_file "$PREFIX/$id_path" "Reencrypt password store using new GPG id ${id_print%, }." +} + +cmd_show() { + local opts clip=0 + opts="$($GETOPT -o c -l clip -n "$PROGRAM" -- "$@")" + local err=$? + eval set -- "$opts" + while true; do case $1 in + -c|--clip) clip=1; shift ;; + --) shift; break ;; + esac done + + [[ $err -ne 0 ]] && die "Usage: $PROGRAM $COMMAND [--clip,-c] [pass-name]" + + local path="$1" + local passfile="$PREFIX/$path.gpg" + check_sneaky_paths "$path" + if [[ -f $passfile ]]; then + if [[ $clip -eq 0 ]]; then + $GPG -d "${GPG_OPTS[@]}" "$passfile" || exit $? + else + local pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | head -n 1)" + [[ -n $pass ]] || exit 1 + clip "$pass" "$path" + fi + elif [[ -d $PREFIX/$path ]]; then + if [[ -z $path ]]; then + echo "Password Store" + else + echo "${path%\/}" + fi + 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 + elif [[ -z $path ]]; then + die "Error: password store is empty. Try \"pass init\"." + else + die "Error: $path is not in the password store." + fi +} + +cmd_find() { + [[ -z "$@" ]] && die "Usage: $PROGRAM $COMMAND pass-names..." + IFS="," eval 'echo "Search Terms: $*"' + local terms="*$(printf '%s*|*' "$@")" + 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' +} + +cmd_grep() { + [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND search-string" + local search="$1" passfile grepresults + while read -r -d "" passfile; do + grepresults="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | grep --color=always "$search")" + [ $? -ne 0 ] && continue + passfile="${passfile%.gpg}" + passfile="${passfile#$PREFIX/}" + local passfile_dir="${passfile%/*}/" + [[ $passfile_dir == "${passfile}/" ]] && passfile_dir="" + passfile="${passfile##*/}" + printf "\e[94m%s\e[1m%s\e[0m:\n" "$passfile_dir" "$passfile" + echo "$grepresults" + done < <(find -L "$PREFIX" -iname '*.gpg' -print0) +} + +cmd_insert() { + local opts multiline=0 noecho=1 force=0 + opts="$($GETOPT -o mef -l multiline,echo,force -n "$PROGRAM" -- "$@")" + local err=$? + eval set -- "$opts" + while true; do case $1 in + -m|--multiline) multiline=1; shift ;; + -e|--echo) noecho=0; shift ;; + -f|--force) force=1; shift ;; + --) shift; break ;; + esac done + + [[ $err -ne 0 || ( $multiline -eq 1 && $noecho -eq 0 ) || $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND [--echo,-e | --multiline,-m] [--force,-f] pass-name" + local path="$1" + local passfile="$PREFIX/$path.gpg" + check_sneaky_paths "$path" + + [[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?" + + mkdir -p -v "$PREFIX/$(dirname "$path")" + set_gpg_recipients "$(dirname "$path")" + + if [[ $multiline -eq 1 ]]; then + echo "Enter contents of $path and press Ctrl+D when finished:" + echo + $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" + elif [[ $noecho -eq 1 ]]; then + local password password_again + while true; do + read -r -p "Enter password for $path: " -s password || exit 1 + echo + read -r -p "Retype password for $path: " -s password_again || exit 1 + echo + if [[ $password == "$password_again" ]]; then + $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" <<<"$password" + break + else + echo "Error: the entered passwords do not match." + fi + done + else + local password + read -r -p "Enter password for $path: " -e password + $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" <<<"$password" + fi + git_add_file "$passfile" "Add given password for $path to store." +} + +cmd_edit() { + [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND pass-name" + + local path="$1" + check_sneaky_paths "$path" + mkdir -p -v "$PREFIX/$(dirname "$path")" + set_gpg_recipients "$(dirname "$path")" + local passfile="$PREFIX/$path.gpg" + + tmpdir #Defines $SECURE_TMPDIR + local tmp_file="$(mktemp -u "$SECURE_TMPDIR/XXXXX")-${path//\//-}.txt" + + + local action="Add" + if [[ -f $passfile ]]; then + $GPG -d -o "$tmp_file" "${GPG_OPTS[@]}" "$passfile" || exit 1 + action="Edit" + fi + ${EDITOR:-vi} "$tmp_file" + [[ -f $tmp_file ]] || die "New password not saved." + $GPG -d -o - "${GPG_OPTS[@]}" "$passfile" 2>/dev/null | diff - "$tmp_file" &>/dev/null && die "Password unchanged." + while ! $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" "$tmp_file"; do + yesno "GPG encryption failed. Would you like to try again?" + done + git_add_file "$passfile" "$action password for $path using ${EDITOR:-vi}." +} + +cmd_generate() { + local opts clip=0 force=0 symbols="-y" inplace=0 + opts="$($GETOPT -o ncif -l no-symbols,clip,in-place,force -n "$PROGRAM" -- "$@")" + local err=$? + eval set -- "$opts" + while true; do case $1 in + -n|--no-symbols) symbols=""; shift ;; + -c|--clip) clip=1; shift ;; + -f|--force) force=1; shift ;; + -i|--in-place) inplace=1; shift ;; + --) shift; break ;; + esac done + + [[ $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" + local path="$1" + local length="$2" + check_sneaky_paths "$path" + [[ ! $length =~ ^[0-9]+$ ]] && die "Error: pass-length \"$length\" must be a number." + mkdir -p -v "$PREFIX/$(dirname "$path")" + set_gpg_recipients "$(dirname "$path")" + local passfile="$PREFIX/$path.gpg" + + [[ $inplace -eq 0 && $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?" + + local pass="$(pwgen -s $symbols $length 1)" + [[ -n $pass ]] || exit 1 + if [[ $inplace -eq 0 ]]; then + $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" <<<"$pass" + else + local passfile_temp="${passfile}.tmp.${RANDOM}.${RANDOM}.${RANDOM}.${RANDOM}.--" + 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 + mv "$passfile_temp" "$passfile" + else + rm -f "$passfile_temp" + die "Could not reencrypt new password." + fi + fi + local verb="Add" + [[ $inplace -eq 1 ]] && verb="Replace" + git_add_file "$passfile" "$verb generated password for ${path}." + + if [[ $clip -eq 0 ]]; then + 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" + else + clip "$pass" "$path" + fi +} + +cmd_delete() { + local opts recursive="" force=0 + opts="$($GETOPT -o rf -l recursive,force -n "$PROGRAM" -- "$@")" + local err=$? + eval set -- "$opts" + while true; do case $1 in + -r|--recursive) recursive="-r"; shift ;; + -f|--force) force=1; shift ;; + --) shift; break ;; + esac done + [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND [--recursive,-r] [--force,-f] pass-name" + local path="$1" + check_sneaky_paths "$path" + + local passfile="$PREFIX/${path%/}" + if [[ ! -d $passfile ]]; then + passfile="$PREFIX/$path.gpg" + [[ ! -f $passfile ]] && die "Error: $path is not in the password store." + fi + + [[ $force -eq 1 ]] || yesno "Are you sure you would like to delete $path?" + + rm $recursive -f -v "$passfile" + if [[ -d $GIT_DIR && ! -e $passfile ]]; then + git rm -qr "$passfile" + git_commit "Remove $path from store." + fi + rmdir -p "${passfile%/*}" 2>/dev/null +} + +cmd_copy_move() { + local opts move=1 force=0 + [[ $1 == "copy" ]] && move=0 + shift + opts="$($GETOPT -o f -l force -n "$PROGRAM" -- "$@")" + local err=$? + eval set -- "$opts" + while true; do case $1 in + -f|--force) force=1; shift ;; + --) shift; break ;; + esac done + [[ $# -ne 2 ]] && die "Usage: $PROGRAM $COMMAND [--force,-f] old-path new-path" + check_sneaky_paths "$@" + local old_path="$PREFIX/${1%/}" + local new_path="$PREFIX/$2" + local old_dir="$old_path" + + if [[ ! -d $old_path ]]; then + old_dir="${old_path%/*}" + old_path="${old_path}.gpg" + [[ ! -f $old_path ]] && die "Error: $1 is not in the password store." + fi + + mkdir -p -v "${new_path%/*}" + [[ -d $old_path || -d $new_path || $new_path =~ /$ ]] || new_path="${new_path}.gpg" + + local interactive="-i" + [[ ! -t 0 || $force -eq 1 ]] && interactive="-f" + + if [[ $move -eq 1 ]]; then + mv $interactive -v "$old_path" "$new_path" || exit 1 + [[ -e "$new_path" ]] && reencrypt_path "$new_path" + + if [[ -d $GIT_DIR && ! -e $old_path ]]; then + git rm -qr "$old_path" + git_add_file "$new_path" "Rename ${1} to ${2}." + fi + rmdir -p "$old_dir" 2>/dev/null + else + cp $interactive -r -v "$old_path" "$new_path" || exit 1 + [[ -e "$new_path" ]] && reencrypt_path "$new_path" + git_add_file "$new_path" "Copy ${1} to ${2}." + fi +} + +cmd_git() { + if [[ $1 == "init" ]]; then + git "$@" || exit 1 + git_add_file "$PREFIX" "Add current contents of password store." + + echo '*.gpg diff=gpg' > "$PREFIX/.gitattributes" + git_add_file .gitattributes "Configure git repository for gpg file diff." + git config --local diff.gpg.binary true + git config --local diff.gpg.textconv "$GPG -d ${GPG_OPTS[*]}" + elif [[ -d $GIT_DIR ]]; then + tmpdir nowarn #Defines $SECURE_TMPDIR. We don't warn, because at most, this only copies encrypted files. + export TMPDIR="$SECURE_TMPDIR" + git "$@" + else + die "Error: the password store is not a git repository. Try \"$PROGRAM git init\"." + fi +} + +# +# END subcommand functions +# + +PROGRAM="${0##*/}" +COMMAND="$1" + +case "$1" in + init) shift; cmd_init "$@" ;; + help|--help) shift; cmd_usage "$@" ;; + version|--version) shift; cmd_version "$@" ;; + show|ls|list) shift; cmd_show "$@" ;; + find|search) shift; cmd_find "$@" ;; + grep) shift; cmd_grep "$@" ;; + insert|add) shift; cmd_insert "$@" ;; + edit) shift; cmd_edit "$@" ;; + generate) shift; cmd_generate "$@" ;; + delete|rm|remove) shift; cmd_delete "$@" ;; + rename|mv) shift; cmd_copy_move "move" "$@" ;; + copy|cp) shift; cmd_copy_move "copy" "$@" ;; + git) shift; cmd_git "$@" ;; + *) COMMAND="show"; cmd_show "$@" ;; +esac +exit 0 diff --git a/pass/share/man/man1/pass.1 b/pass/share/man/man1/pass.1 new file mode 100644 index 0000000..e1fe605 --- /dev/null +++ b/pass/share/man/man1/pass.1 @@ -0,0 +1,450 @@ +.TH PASS 1 "2014 March 18" ZX2C4 "Password Store" + +.SH NAME +pass - stores, retrieves, generates, and synchronizes passwords securely + +.SH SYNOPSIS +.B pass +[ +.I COMMAND +] [ +.I OPTIONS +]... [ +.I ARGS +]... + +.SH DESCRIPTION + +.B pass +is a very simple password store that keeps passwords inside +.BR gpg2 (1) +encrypted files inside a simple directory tree residing at +.IR ~/.password-store . +The +.B pass +utility provides a series of commands for manipulating the password store, +allowing the user to add, remove, edit, synchronize, generate, and manipulate +passwords. + +If no COMMAND is specified, COMMAND defaults to either +.B show +or +.BR ls , +depending on the type of specifier in ARGS. Otherwise COMMAND must be one of +the valid commands listed below. + +Several of the commands below rely on or provide additional functionality if +the password store directory is also a git repository. If the password store +directory is a git repository, all password store modification commands will +cause a corresponding git commit. See the \fIEXTENDED GIT EXAMPLE\fP section +for a detailed description using \fBinit\fP and +.BR git (1). + +The \fBinit\fP command must be run before other commands in order to initialize +the password store with the correct gpg key id. Passwords are encrypting using +the gpg key set with \fBinit\fP. + +There is a corresponding bash completion script for use with tab completing +password names in +.BR bash (1). + +.SH COMMANDS + +.TP +\fBinit\fP [ \fI--path=sub-folder\fP, \fI-p sub-folder\fP ] \fIgpg-id...\fP +Initialize new password storage and use +.I gpg-id +for encryption. Multiple gpg-ids may be specified, in order to encrypt each +password with multiple ids. This command must be run first before a password +store can be used. If the specified \fIgpg-id\fP is different from the key +used in any existing files, these files will be reencrypted to use the new id. +Note that use of +.BR gpg-agent (1) +is recommended so that the batch decryption does not require as much user +intervention. If \fI--path\fP or \fI-p\fP is specified, along with an argument, +a specific gpg-id or set of gpg-ids is assigned for that specific sub folder of +the password store. If only one \fIgpg-id\fP is given, and it is an empty string, +then the current \fI.gpg-id\fP file for the specified \fIsub-folder\fP (or root if +unspecified) is removed. +.TP +\fBls\fP \fIsubfolder\fP +List names of passwords inside the tree at +.I subfolder +by using the +.BR tree (1) +program. This command is alternatively named \fBlist\fP. +.TP +\fBgrep\fP \fIsearch-string\fP +Searches inside each decrypted password file for \fIsearch-string\fP, and displays line +containing matched string along with filename. Uses +.BR grep (1) +for matching. Make use of the \fIGREP_OPTIONS\fP environment variable to set particular +options. +.TP +\fBfind\fP \fIpass-names\fP... +List names of passwords inside the tree that match \fIpass-names\fP by using the +.BR tree (1) +program. This command is alternatively named \fBsearch\fP. +.TP +\fBshow\fP [ \fI--clip\fP, \fI-c\fP ] \fIpass-name\fP +Decrypt and print a password named \fIpass-name\fP. If \fI--clip\fP or \fI-c\fP +is specified, do not print the password but instead copy the first line to the +clipboard using +.BR xclip (1) +and then restore the clipboard after 45 (or \fIPASSWORD_STORE_CLIP_TIME\fP) seconds. +.TP +\fBinsert\fP [ \fI--echo\fP, \fI-e\fP | \fI--multiline\fP, \fI-m\fP ] [ \fI--force\fP, \fI-f\fP ] \fIpass-name\fP +Insert a new password into the password store called \fIpass-name\fP. This will +read the new password from standard in. If \fI--echo\fP or \fI-e\fP is \fInot\fP specified, +disable keyboard echo when the password is entered and confirm the password by asking +for it twice. If \fI--multiline\fP or \fI-m\fP is specified, lines will be read until +EOF or Ctrl+D is reached. Otherwise, only a single line from standard in is read. Prompt +before overwriting an existing password, unless \fI--force\fP or \fI-f\fP is specified. This +command is alternatively named \fBadd\fP. +.TP +\fBedit\fP \fIpass-name\fP +Insert a new password or edit an existing password using the default text editor specified +by the environment variable \fIEDITOR\fP or using +.BR vi (1) +as a fallback. This mode makes use of temporary files for editing, but care is taken to +ensure that temporary files are created in \fI/dev/shm\fP in order to avoid writing to +difficult-to-erase disk sectors. If \fI/dev/shm\fP is not accessible, fallback to +the ordinary \fITMPDIR\fP location, and print a warning. +.TP +\fBgenerate\fP [ \fI--no-symbols\fP, \fI-n\fP ] [ \fI--clip\fP, \fI-c\fP ] [ \fI--in-place\fP, \fI-i\fP | \fI--force\fP, \fI-f\fP ] \fIpass-name pass-length\fP +Generate a new password using +.BR pwgen (1) +of length \fIpass-length\fP and insert into \fIpass-name\fP. If \fI--no-symbols\fP or \fI-n\fP +is specified, do not use any non-alphanumeric characters in the generated password. +If \fI--clip\fP or \fI-c\fP is specified, do not print the password but instead copy +it to the clipboard using +.BR xclip (1) +and then restore the clipboard after 45 (or \fIPASSWORD_STORE_CLIP_TIME\fP) seconds. +Prompt before overwriting an existing password, +unless \fI--force\fP or \fI-f\fP is specified. If \fI--in-place\fP or \fI-i\fP is +specified, do not interactively prompt, and only replace the first line of the password +file with the new generated password, keeping the remainder of the file intact. +.TP +\fBrm\fP [ \fI--recursive\fP, \fI-r\fP ] [ \fI--force\fP, \fI-f\fP ] \fIpass-name\fP +Remove the password named \fIpass-name\fP from the password store. This command is +alternatively named \fBremove\fP or \fBdelete\fP. If \fI--recursive\fP or \fI-r\fP +is specified, delete pass-name recursively if it is a directory. If \fI--force\fP +or \fI-f\fP is specified, do not interactively prompt before removal. +.TP +\fBmv\fP [ \fI--force\fP, \fI-f\fP ] \fIold-path\fP \fInew-path\fP +Renames the password or directory named \fIold-path\fP to \fInew-path\fP. This +command is alternatively named \fBrename\fP. If \fI--force\fP is specified, +silently overwrite \fInew-path\fP if it exists. If \fInew-path\fP ends in a +trailing \fI/\fP, it is always treated as a directory. Passwords are selectively +reencrypted to the corresponding keys of their new destination. +.TP +\fBcp\fP [ \fI--force\fP, \fI-f\fP ] \fIold-path\fP \fInew-path\fP +Copies the password or directory named \fIold-path\fP to \fInew-path\fP. This +command is alternatively named \fBcopy\fP. If \fI--force\fP is specified, +silently overwrite \fInew-path\fP if it exists. If \fInew-path\fP ends in a +trailing \fI/\fP, it is always treated as a directory. Passwords are selectively +reencrypted to the corresponding keys of their new destination. +.TP +\fBgit\fP \fIgit-command-args\fP... +If the password store is a git repository, pass \fIgit-command-args\fP as arguments to +.BR git (1) +using the password store as the git repository. If \fIgit-command-args\fP is \fBinit\fP, +in addition to initializing the git repository, add the current contents of the password +store to the repository in an initial commit. If the git config key \fIpass.signcommits\fP +is set to \fItrue\fP, then all commits will be signed using \fIuser.signingkey\fP or the +default git signing key. This config key may be turned on using: +.B `pass git config --bool --add pass.signcommits true` +.TP +\fBhelp\fP +Show usage message. +.TP +\fBversion\fP +Show version information. + +.SH SIMPLE EXAMPLES + +.TP +Initialize password store +.B zx2c4@laptop ~ $ pass init Jason@zx2c4.com +.br +mkdir: created directory \[u2018]/home/zx2c4/.password-store\[u2019] +.br +Password store initialized for Jason@zx2c4.com. +.TP +List existing passwords in store +.B zx2c4@laptop ~ $ pass +.br +Password Store +.br +\[u251C]\[u2500]\[u2500] Business +.br +\[u2502] \[u251C]\[u2500]\[u2500] some-silly-business-site.com +.br +\[u2502] \[u2514]\[u2500]\[u2500] another-business-site.net +.br +\[u251C]\[u2500]\[u2500] Email +.br +\[u2502] \[u251C]\[u2500]\[u2500] donenfeld.com +.br +\[u2502] \[u2514]\[u2500]\[u2500] zx2c4.com +.br +\[u2514]\[u2500]\[u2500] France +.br + \[u251C]\[u2500]\[u2500] bank +.br + \[u251C]\[u2500]\[u2500] freebox +.br + \[u2514]\[u2500]\[u2500] mobilephone +.br + +.br +Alternatively, "\fBpass ls\fP". +.TP +Find existing passwords in store that match .com +.B zx2c4@laptop ~ $ pass find .com +.br +Search Terms: .com +.br +\[u251C]\[u2500]\[u2500] Business +.br +\[u2502] \[u251C]\[u2500]\[u2500] some-silly-business-site.com +.br +\[u2514]\[u2500]\[u2500] Email +.br + \[u251C]\[u2500]\[u2500] donenfeld.com +.br + \[u2514]\[u2500]\[u2500] zx2c4.com +.br + +.br +Alternatively, "\fBpass search .com\fP". +.TP +Show existing password +.B zx2c4@laptop ~ $ pass Email/zx2c4.com +.br +sup3rh4x3rizmynam3 +.TP +Copy existing password to clipboard +.B zx2c4@laptop ~ $ pass -c Email/zx2c4.com +.br +Copied Email/jason@zx2c4.com to clipboard. Will clear in 45 seconds. +.TP +Add password to store +.B zx2c4@laptop ~ $ pass insert Business/cheese-whiz-factory +.br +Enter password for Business/cheese-whiz-factory: omg so much cheese what am i gonna do +.TP +Add multiline password to store +.B zx2c4@laptop ~ $ pass insert -m Business/cheese-whiz-factory +.br +Enter contents of Business/cheese-whiz-factory and press Ctrl+D when finished: +.br + +.br +Hey this is my +.br +awesome +.br +multi +.br +line +.br +passworrrrrrrrd. +.br +^D +.TP +Generate new password +.B zx2c4@laptop ~ $ pass generate Email/jasondonenfeld.com 15 +.br +The generated password to Email/jasondonenfeld.com is: +.br +$(-QF&Q=IN2nFBx +.TP +Generate new alphanumeric password +.B zx2c4@laptop ~ $ pass generate -n Email/jasondonenfeld.com 12 +.br +The generated password to Email/jasondonenfeld.com is: +.br +YqFsMkBeO6di +.TP +Generate new password and copy it to the clipboard +.B zx2c4@laptop ~ $ pass generate -c Email/jasondonenfeld.com 19 +.br +Copied Email/jasondonenfeld.com to clipboard. Will clear in 45 seconds. +.TP +Remove password from store +.B zx2c4@laptop ~ $ pass remove Business/cheese-whiz-factory +.br +rm: remove regular file \[u2018]/home/zx2c4/.password-store/Business/cheese-whiz-factory.gpg\[u2019]? y +.br +removed \[u2018]/home/zx2c4/.password-store/Business/cheese-whiz-factory.gpg\[u2019] + +.SH EXTENDED GIT EXAMPLE +Here, we initialize new password store, create a git repository, and then manipulate and sync passwords. Make note of the arguments to the first call of \fBpass git push\fP; consult +.BR git-push (1) +for more information. + +.B zx2c4@laptop ~ $ pass init Jason@zx2c4.com +.br +mkdir: created directory \[u2018]/home/zx2c4/.password-store\[u2019] +.br +Password store initialized for Jason@zx2c4.com. + +.B zx2c4@laptop ~ $ pass git init +.br +Initialized empty Git repository in /home/zx2c4/.password-store/.git/ +.br +[master (root-commit) 998c8fd] Added current contents of password store. +.br + 1 file changed, 1 insertion(+) +.br + create mode 100644 .gpg-id + +.B zx2c4@laptop ~ $ pass git remote add origin kexec.com:pass-store + +.B zx2c4@laptop ~ $ pass generate Amazon/amazonemail@email.com 21 +.br +mkdir: created directory \[u2018]/home/zx2c4/.password-store/Amazon\[u2019] +.br +[master 30fdc1e] Added generated password for Amazon/amazonemail@email.com to store. +.br +1 file changed, 0 insertions(+), 0 deletions(-) +.br +create mode 100644 Amazon/amazonemail@email.com.gpg +.br +The generated password to Amazon/amazonemail@email.com is: +.br +<5m,_BrZY`antNDxKN<0A + +.B zx2c4@laptop ~ $ pass git push -u --all +.br +Counting objects: 4, done. +.br +Delta compression using up to 2 threads. +.br +Compressing objects: 100% (3/3), done. +.br +Writing objects: 100% (4/4), 921 bytes, done. +.br +Total 4 (delta 0), reused 0 (delta 0) +.br +To kexec.com:pass-store +.br +* [new branch] master -> master +.br +Branch master set up to track remote branch master from origin. + +.B zx2c4@laptop ~ $ pass insert Amazon/otheraccount@email.com +.br +Enter password for Amazon/otheraccount@email.com: som3r3a11yb1gp4ssw0rd!!88** +.br +[master b9b6746] Added given password for Amazon/otheraccount@email.com to store. +.br +1 file changed, 0 insertions(+), 0 deletions(-) +.br +create mode 100644 Amazon/otheraccount@email.com.gpg + +.B zx2c4@laptop ~ $ pass rm Amazon/amazonemail@email.com +.br +rm: remove regular file \[u2018]/home/zx2c4/.password-store/Amazon/amazonemail@email.com.gpg\[u2019]? y +.br +removed \[u2018]/home/zx2c4/.password-store/Amazon/amazonemail@email.com.gpg\[u2019] +.br +rm 'Amazon/amazonemail@email.com.gpg' +.br +[master 288b379] Removed Amazon/amazonemail@email.com from store. +.br +1 file changed, 0 insertions(+), 0 deletions(-) +.br +delete mode 100644 Amazon/amazonemail@email.com.gpg + +.B zx2c4@laptop ~ $ pass git push +.br +Counting objects: 9, done. +.br +Delta compression using up to 2 threads. +.br +Compressing objects: 100% (5/5), done. +.br +Writing objects: 100% (7/7), 1.25 KiB, done. +.br +Total 7 (delta 0), reused 0 (delta 0) +.br +To kexec.com:pass-store + +.SH FILES + +.TP +.B ~/.password-store +The default password storage directory. +.TP +.B ~/.password-store/.gpg-id +Contains the default gpg key identification used for encryption and decryption. +Multiple gpg keys may be specified in this file, one per line. If this file +exists in any sub directories, passwords inside those sub directories are +encrypted using those keys. This should be set using the \fBinit\fP command. + +.SH ENVIRONMENT VARIABLES + +.TP +.I PASSWORD_STORE_DIR +Overrides the default password storage directory. +.TP +.I PASSWORD_STORE_KEY +Overrides the default gpg key identification set by \fBinit\fP. Keys must not +contain spaces and thus use of the hexidecimal key signature is recommended. +Multiple keys may be specified separated by spaces. +.TP +.I PASSWORD_STORE_GIT +Overrides the default root of the git repository, which is helpful if +\fIPASSWORD_STORE_DIR\fP is temporarily set to a sub-directory of the default +password store. +.TP +.I PASSWORD_STORE_GPG_OPTS +Additional options to be passed to all invocations of GPG. +.TP +.I PASSWORD_STORE_X_SELECTION +Overrides the selection passed to \fBxclip\fP, by default \fIclipboard\fP. See +.BR xclip (1) +for more info. +.TP +.I PASSWORD_STORE_CLIP_TIME +Specifies the number of seconds to wait before restoring the clipboard, by default +\fI45\fP seconds. +.TP +.I PASSWORD_STORE_UMASK +Sets the umask of all files modified by pass, by default \fI077\fP. +.TP +.I EDITOR +The location of the text editor used by \fBedit\fP. +.SH SEE ALSO +.BR gpg2 (1), +.BR pwgen (1), +.BR git (1), +.BR xclip (1). + +.SH AUTHOR +.B pass +was written by +.MT Jason@zx2c4.com +Jason A. Donenfeld +.ME . +For updates and more information, a project page is available on the +.UR http://\:www.passwordstore.org/ +World Wide Web +.UE . + +.SH COPYING +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -- 2.20.1