diff --git a/pass-pick b/pass-pick index 5c76db1..48ddeec 100755 --- a/pass-pick +++ b/pass-pick @@ -57,137 +57,137 @@ # Passes along any options given to main script. rofi_opts=("$@") _picker() { - if [ -n "$PICKER_BACKEND" ]; then - "${PICKER_BACKEND[@]}" - elif command -v rofi 1>/dev/null 2>/dev/null; then - rofi -dmenu -no-auto-select -i "${rofi_opts[@]}" "$@" -p "entry" - elif command -v bemenu 1>/dev/null 2>/dev/null; then - bemenu -l 20 -i -p "entry >" - elif command -v dmenu 1>/dev/null 2>/dev/null; then - dmenu -i -p "entry >" - else - printf "%s: 📦 %s must be installed for %s function.\n" "critical" "rofi/bemenu/dmenu" "this" >&2 - notify-send "📦 rofi/bemenu/dmenu" --urgency="critical" "must be installed for this function." - exit 1 - fi + if [ -n "$PICKER_BACKEND" ]; then + "${PICKER_BACKEND[@]}" + elif command -v rofi 1>/dev/null 2>/dev/null; then + rofi -dmenu -no-auto-select -i "${rofi_opts[@]}" "$@" -p "entry" + elif command -v bemenu 1>/dev/null 2>/dev/null; then + bemenu -l 20 -i -p "entry >" + elif command -v dmenu 1>/dev/null 2>/dev/null; then + dmenu -i -p "entry >" + else + printf "%s: 📦 %s must be installed for %s function.\n" "critical" "rofi/bemenu/dmenu" "this" >&2 + notify-send "📦 rofi/bemenu/dmenu" --urgency="critical" "must be installed for this function." + exit 1 + fi } # copies to clipboard, removes any trailing newlines, # and only keeps it in for 1 paste (1 loop to read in script, 1 to output) _clipper() { - if command -v wl-copy 1>/dev/null 2>/dev/null; then - wl-copy -o -n - elif command -v xclip 1>/dev/null 2>/dev/null; then - xclip -i -selection 'clipboard' -loops 2 -rmlastnl - elif command -v xsel 1>/dev/null 2>/dev/null; then - xsel -b - else - notify-send "No clipboard utility" "Install wl-copy, xclip or xsel." - fi + if command -v wl-copy 1>/dev/null 2>/dev/null; then + wl-copy -o -n + elif command -v xclip 1>/dev/null 2>/dev/null; then + xclip -i -selection 'clipboard' -loops 2 -rmlastnl + elif command -v xsel 1>/dev/null 2>/dev/null; then + xsel -b + else + notify-send "No clipboard utility" "Install wl-copy, xclip or xsel." + fi } # parse, see https://unix.stackexchange.com/a/331965/8541 _parse_config() { - (grep -e "^$2=" -m 1 "$1" 2>/dev/null || printf "var=__UNDEFINED__\n") | head -n1 | cut -d '=' -f 2- + (grep -e "^$2=" -m 1 "$1" 2>/dev/null || printf "var=__UNDEFINED__\n") | head -n1 | cut -d '=' -f 2- } # read config file get_config() { - local locations=( - "$PP_CONFIGURATION_FILE" - "${xdg_config_home:-$HOME/.config}/pass-picker/pass-picker.conf" - "$HOME/.pass-picker.conf" - "/etc/pass-picker.conf" - ) + local locations=( + "$PP_CONFIGURATION_FILE" + "${xdg_config_home:-$HOME/.config}/pass-picker/pass-picker.conf" + "$HOME/.pass-picker.conf" + "/etc/pass-picker.conf" + ) - # return the first config file with a valid path - for config in "${locations[@]}"; do - if [[ -n "$config" && -f "$config" ]]; then - # see if the config has been given a value - local val - val="$(_parse_config "$config" "$1")" - break - fi - done + # return the first config file with a valid path + for config in "${locations[@]}"; do + if [[ -n $config && -f $config ]]; then + # see if the config has been given a value + local val + val="$(_parse_config "$config" "$1")" + break + fi + done - # if there was a config file but no value - # or there was no config file at all - if [ "$val" = "__UNDEFINED__" ] || [ -z "$val" ]; then - val="$2" - fi - printf -- "%s" "$val" + # if there was a config file but no value + # or there was no config file at all + if [ "$val" = "__UNDEFINED__" ] || [ -z "$val" ]; then + val="$2" + fi + printf -- "%s" "$val" } set_defaults() { - # The location of the pass-picker config file - # PP_CONFIGURATION_FILE="~/.config/pass-picker/pass-picker.conf" - # set options, leaving already set environment variables intact - # try to read any settings from config files - PICKER_BACKEND="${PP_PICKER_BACKEND:-$(get_config PICKER_BACKEND)}" + # The location of the pass-picker config file + # PP_CONFIGURATION_FILE="~/.config/pass-picker/pass-picker.conf" + # set options, leaving already set environment variables intact + # try to read any settings from config files + PICKER_BACKEND="${PP_PICKER_BACKEND:-$(get_config PICKER_BACKEND)}" - KEY_AUTOFILL="${PP_KEY_AUTOFILL:-$(get_config KEY_AUTOFILL Return)}" - KEY_ENTRY_OPEN="${PP_KEY_ENTRY_OPEN:-$(get_config KEY_ENTRY_OPEN Alt+Return)}" - KEY_FILL_USER="${PP_KEY_FILL_USER:-$(get_config KEY_FILL_USER Alt+u)}" - KEY_CLIP_USER="${PP_KEY_CLIP_USER:-$(get_config KEY_CLIP_USER Ctrl+Alt+u)}" - KEY_FILL_PASS="${PP_KEY_FILL_PASS:-$(get_config KEY_FILL_PASS Alt+p)}" - KEY_CLIP_PASS="${PP_KEY_CLIP_PASS:-$(get_config KEY_CLIP_PASS Ctrl+Alt+p)}" - KEY_ENTRYMENU_FILL="${PP_KEY_ENTRYMENU_FILL:-$(get_config KEY_ENTRYMENU_FILL Return)}" - KEY_ENTRYMENU_CLIP="${PP_KEY_ENTRYMENU_CLIP:-$(get_config KEY_ENTRYMENU_CLIP Alt+Return)}" - KEY_ENTRYMENU_SHOWFIELD="${KEY_ENTRYMENU_SHOWFIELD:-$(get_config KEY_ENTRYMENU_SHOWFIELD Alt+s)}" - KEY_ENTRYMENU_QUIT="${PP_KEY_ENTRYMENU_QUIT:-$(get_config KEY_ENTRYMENU_QUIT Alt+BackSpace)}" + KEY_AUTOFILL="${PP_KEY_AUTOFILL:-$(get_config KEY_AUTOFILL Return)}" + KEY_ENTRY_OPEN="${PP_KEY_ENTRY_OPEN:-$(get_config KEY_ENTRY_OPEN Alt+Return)}" + KEY_FILL_USER="${PP_KEY_FILL_USER:-$(get_config KEY_FILL_USER Alt+u)}" + KEY_CLIP_USER="${PP_KEY_CLIP_USER:-$(get_config KEY_CLIP_USER Ctrl+Alt+u)}" + KEY_FILL_PASS="${PP_KEY_FILL_PASS:-$(get_config KEY_FILL_PASS Alt+p)}" + KEY_CLIP_PASS="${PP_KEY_CLIP_PASS:-$(get_config KEY_CLIP_PASS Ctrl+Alt+p)}" + KEY_ENTRYMENU_FILL="${PP_KEY_ENTRYMENU_FILL:-$(get_config KEY_ENTRYMENU_FILL Return)}" + KEY_ENTRYMENU_CLIP="${PP_KEY_ENTRYMENU_CLIP:-$(get_config KEY_ENTRYMENU_CLIP Alt+Return)}" + KEY_ENTRYMENU_SHOWFIELD="${KEY_ENTRYMENU_SHOWFIELD:-$(get_config KEY_ENTRYMENU_SHOWFIELD Alt+s)}" + KEY_ENTRYMENU_QUIT="${PP_KEY_ENTRYMENU_QUIT:-$(get_config KEY_ENTRYMENU_QUIT Alt+BackSpace)}" - AUTOFILL_BACKEND="${PP_AUTOFILL_BACKEND:-$(get_config AUTOFILL_BACKEND wtype)}" - AUTOFILL_CHAIN="${PP_AUTOENTRY_CHAIN:-$(get_config AUTOFILL_CHAIN 'username :tab password')}" - AUTOFILL_DELAY="${PP_AUTOENTRY_DELAY:-$(get_config AUTOFILL_DELAY 30)}" + AUTOFILL_BACKEND="${PP_AUTOFILL_BACKEND:-$(get_config AUTOFILL_BACKEND wtype)}" + AUTOFILL_CHAIN="${PP_AUTOENTRY_CHAIN:-$(get_config AUTOFILL_CHAIN 'username :tab password')}" + AUTOFILL_DELAY="${PP_AUTOENTRY_DELAY:-$(get_config AUTOFILL_DELAY 30)}" - PASS_USERNAME_FIELD="${PP_PASS_USERNAME_FIELD:-$(get_config PASS_USERNAME_FIELD 'username user login')}" - PASS_COFFIN_OPEN_TIME="${PP_PASS_COFFIN_OPEN_TIME:-$(get_config PASS_COFFIN_OPEN_TIME 0)}" - PASS_COFFIN_LOCATION="${PP_PASS_COFFIN_LOCATION:-$(get_config PASS_COFFIN_LOCATION)}" + PASS_USERNAME_FIELD="${PP_PASS_USERNAME_FIELD:-$(get_config PASS_USERNAME_FIELD 'username user login')}" + PASS_COFFIN_OPEN_TIME="${PP_PASS_COFFIN_OPEN_TIME:-$(get_config PASS_COFFIN_OPEN_TIME 0)}" + PASS_COFFIN_LOCATION="${PP_PASS_COFFIN_LOCATION:-$(get_config PASS_COFFIN_LOCATION)}" } # exit on escape pressed exit_check() { - [ "$1" -eq 1 ] && exit + [ "$1" -eq 1 ] && exit } # simply return a list of all passwords in pass store # TODO only show website names (+ folder names), and account names for multiple accounts on one site list_passwords() { - shopt -s nullglob globstar - prefix=${PASSWORD_STORE_DIR:-~/.password-store} - password_files=("$prefix"/**/*.gpg) - password_files=("${password_files[@]#"$prefix"/}") - password_files=("${password_files[@]%.gpg}") + shopt -s nullglob globstar + prefix=${PASSWORD_STORE_DIR:-~/.password-store} + password_files=("$prefix"/**/*.gpg) + password_files=("${password_files[@]#"$prefix"/}") + password_files=("${password_files[@]%.gpg}") - printf '%s\n' "${password_files[@]}" + printf '%s\n' "${password_files[@]}" } # return password for argument passed show_password() { - pass show "$1" | head -n1 + pass show "$1" | head -n1 } # send password to clipboard clip_password() { - pass show -c "$1" + pass show -c "$1" } # attempt to return the field specified # attempts all (space separated) fields until the # first one successfully returned _p_get_field() { - local gp_entry="$1" - local gp_field="$2" + local gp_entry="$1" + local gp_field="$2" - # return on first successfully returned key - for key in $gp_field; do - local value - value=$(_p_get_key_value "$gp_entry" "$key") + # return on first successfully returned key + for key in $gp_field; do + local value + value=$(_p_get_key_value "$gp_entry" "$key") - # found entry - if [ -n "$value" ]; then - echo "$value" && break - fi - done + # found entry + if [ -n "$value" ]; then + echo "$value" && break + fi + done } # returns the corresponding value for the key passed in @@ -195,210 +195,210 @@ _p_get_field() { # $1: pass (file) entry to search through # $2: string name of the containting key _p_get_key_value() { - local value - value=$(list_fields "$1" | grep "$2") + local value + value=$(list_fields "$1" | grep "$2") - # get everything after first colon, remove whitespace - echo "$value" | cut -d':' -f2- | tr -d '[:blank:]' + # get everything after first colon, remove whitespace + echo "$value" | cut -d':' -f2- | tr -d '[:blank:]' } # return username for argument passed # Prefers in-metadata username, falls back to filename show_username() { - result=$(_p_get_field "$1" "${PASS_USERNAME_FIELD}") - if [ -z "$result" ]; then - echo "${1##*/}" - fi + result=$(_p_get_field "$1" "${PASS_USERNAME_FIELD}") + if [ -z "$result" ]; then + echo "${1##*/}" + fi } clip_username() { - show_username "$1" "${PASS_USERNAME_FIELD}" | _clipper + show_username "$1" "${PASS_USERNAME_FIELD}" | _clipper } show_field() { - _p_get_field "$1" "$2" + _p_get_field "$1" "$2" } clip_field() { - show_field "$1" "$2" | _clipper + show_field "$1" "$2" | _clipper } list_fields() { - pass show "$1" | tail -n+2 + pass show "$1" | tail -n+2 } # invoke the dotool to type inputs _type() { - local tool="${AUTOFILL_BACKEND}" - local toolmode="$1" - local key="$2" + local tool="${AUTOFILL_BACKEND}" + local toolmode="$1" + local key="$2" - if [ "$tool" = "wtype" ]; then - if [ "$toolmode" = "type" ]; then - "$tool" -s "${AUTOFILL_DELAY}" -- "$key" - elif [ "$toolmode" = "key" ]; then - "$tool" -s "${AUTOFILL_DELAY}" -k "$key" - fi - elif [ "$tool" = "xdotool" ]; then - "$tool" "$toolmode" --delay "${AUTOFILL_DELAY}" "$key" - elif [ "$tool" = "ydotool" ]; then - "$tool" "$toolmode" --key-delay "${AUTOFILL_DELAY}" "$key" - else - "$tool" "$toolmode" "$key" - fi + if [ "$tool" = "wtype" ]; then + if [ "$toolmode" = "type" ]; then + "$tool" -s "${AUTOFILL_DELAY}" -- "$key" + elif [ "$toolmode" = "key" ]; then + "$tool" -s "${AUTOFILL_DELAY}" -k "$key" + fi + elif [ "$tool" = "xdotool" ]; then + "$tool" "$toolmode" --delay "${AUTOFILL_DELAY}" "$key" + elif [ "$tool" = "ydotool" ]; then + "$tool" "$toolmode" --key-delay "${AUTOFILL_DELAY}" "$key" + else + "$tool" "$toolmode" "$key" + fi } # automatically fill out fields # transform special chain entries into valid dotool commands autofill() { - local selected="${1}" - local autoentry_chain="${2}" + local selected="${1}" + local autoentry_chain="${2}" - for part in $autoentry_chain; do - case "$part" in - ":tab") _type key Tab ;; - ":return") _type key Return ;; - ":space") _type key space ;; - "username") _type type "$(show_username "$selected")" ;; - "password") _type type "$(show_password "$selected")" ;; - ":direct") _type type "$selected" ;; - *) printf '%s' "$selected" ;; - esac - done + for part in $autoentry_chain; do + case "$part" in + ":tab") _type key Tab ;; + ":return") _type key Return ;; + ":space") _type key space ;; + "username") _type type "$(show_username "$selected")" ;; + "password") _type type "$(show_password "$selected")" ;; + ":direct") _type type "$selected" ;; + *) printf '%s' "$selected" ;; + esac + done } # opens a menu for the specified pass entry, containing its individual fields entrymenu() { - local entry="$1" - local deobfuscate="$2" - local k_entrymenu_fill="${KEY_ENTRYMENU_FILL}" - local k_entrymenu_clip="${KEY_ENTRYMENU_CLIP}" - local k_entrymenu_showfield="${KEY_ENTRYMENU_SHOWFIELD}" - local k_entrymenu_quit="${KEY_ENTRYMENU_QUIT}" + local entry="$1" + local deobfuscate="$2" + local k_entrymenu_fill="${KEY_ENTRYMENU_FILL}" + local k_entrymenu_clip="${KEY_ENTRYMENU_CLIP}" + local k_entrymenu_showfield="${KEY_ENTRYMENU_SHOWFIELD}" + local k_entrymenu_quit="${KEY_ENTRYMENU_QUIT}" - local pass - if [ "$deobfuscate" = "true" ]; then - pass="$(show_password "$entry")" - else - pass="(hidden)" - fi + local pass + if [ "$deobfuscate" = "true" ]; then + pass="$(show_password "$entry")" + else + pass="(hidden)" + fi - local field - field=$( - printf "password: %s\n%s" "$pass" "$(list_fields "$entry")" | - _picker \ - -kb-accept-entry "" \ - -kb-custom-1 "$k_entrymenu_fill" \ - -kb-custom-2 "$k_entrymenu_clip" \ - -kb-custom-3 "$k_entrymenu_quit" \ - -kb-custom-4 "$k_entrymenu_showfield" \ - -mesg " ᐊ $k_entrymenu_quit ᐊ | $k_entrymenu_fill: fill selection | $k_entrymenu_clip: clip selection | $k_entrymenu_showfield: reveal password" - ) - exit_value=$? - exit_check "$exit_value" + local field + field=$( + printf "password: %s\n%s" "$pass" "$(list_fields "$entry")" \ + | _picker \ + -kb-accept-entry "" \ + -kb-custom-1 "$k_entrymenu_fill" \ + -kb-custom-2 "$k_entrymenu_clip" \ + -kb-custom-3 "$k_entrymenu_quit" \ + -kb-custom-4 "$k_entrymenu_showfield" \ + -mesg " ᐊ $k_entrymenu_quit ᐊ | $k_entrymenu_fill: fill selection | $k_entrymenu_clip: clip selection | $k_entrymenu_showfield: reveal password" + ) + exit_value=$? + exit_check "$exit_value" - # get field name - field=${field%%:*} - case "$exit_value" in - "0" | "10") - if [ "$field" = "password" ]; then - autofill "$entry" "password" - else - autofill "$(show_field "$entry" "$field")" ":direct" - fi - exit 0 - ;; - "11") - if [ "$field" = "password" ]; then - clip_password "$entry" - else - clip_field "$entry" "$field" - fi - exit 0 - ;; - "12") - main - ;; - "13") - local toggle - if [ "$deobfuscate" = "true" ]; then - toggle=false - else - toggle=true - fi - entrymenu "$entry" "$toggle" - ;; - esac + # get field name + field=${field%%:*} + case "$exit_value" in + "0" | "10") + if [ "$field" = "password" ]; then + autofill "$entry" "password" + else + autofill "$(show_field "$entry" "$field")" ":direct" + fi + exit 0 + ;; + "11") + if [ "$field" = "password" ]; then + clip_password "$entry" + else + clip_field "$entry" "$field" + fi + exit 0 + ;; + "12") + main + ;; + "13") + local toggle + if [ "$deobfuscate" = "true" ]; then + toggle=false + else + toggle=true + fi + entrymenu "$entry" "$toggle" + ;; + esac } open_coffin() { - ## there's a closed coffin in our directory - if [ -f "${PASS_COFFIN_LOCATION:-${PASSWORD_STORE_DIR:-~/.password-store}/.coffin/coffin.tar.gpg}" ]; then - if [ "$PASS_COFFIN_OPEN_TIME" -eq 0 ]; then - coffin_should_close_instantly=true - pass open -t 1h # we still set a maximum time limit just to hedge against failures - else - pass open -t "${PASS_COFFIN_OPEN_TIME:-3h}" - fi - fi + ## there's a closed coffin in our directory + if [ -f "${PASS_COFFIN_LOCATION:-${PASSWORD_STORE_DIR:-~/.password-store}/.coffin/coffin.tar.gpg}" ]; then + if [ "$PASS_COFFIN_OPEN_TIME" -eq 0 ]; then + coffin_should_close_instantly=true + pass open -t 1h # we still set a maximum time limit just to hedge against failures + else + pass open -t "${PASS_COFFIN_OPEN_TIME:-3h}" + fi + fi } # make sure we remember to close the coffin if the program terminates close_coffin() { - if [ "$coffin_should_close_instantly" = true ]; then - pass close - fi + if [ "$coffin_should_close_instantly" = true ]; then + pass close + fi } trap close_coffin SIGINT SIGTERM ERR EXIT main() { - local autoentry_chain="${AUTOFILL_CHAIN}" - local k_autofill="${KEY_AUTOFILL}" - local k_fill_user="${KEY_FILL_USER}" - local k_clip_user="${KEY_CLIP_USER}" - local k_fill_pass="${KEY_FILL_PASS}" - local k_clip_pass="${KEY_CLIP_PASS}" - local k_submenu="${KEY_ENTRY_OPEN}" + local autoentry_chain="${AUTOFILL_CHAIN}" + local k_autofill="${KEY_AUTOFILL}" + local k_fill_user="${KEY_FILL_USER}" + local k_clip_user="${KEY_CLIP_USER}" + local k_fill_pass="${KEY_FILL_PASS}" + local k_clip_pass="${KEY_CLIP_PASS}" + local k_submenu="${KEY_ENTRY_OPEN}" - open_coffin + open_coffin - entry="$( - list_passwords | - _picker -kb-accept-entry "" \ - -kb-custom-1 "$k_autofill" \ - -kb-custom-2 "$k_clip_user" \ - -kb-custom-3 "$k_clip_pass" \ - -kb-custom-4 "$k_fill_user" \ - -kb-custom-5 "$k_fill_pass" \ - -kb-custom-6 "$k_submenu" \ - -mesg "| $k_autofill: fill credentials | $k_submenu: open entry | $k_fill_user: fill username | $k_fill_pass: fill password | $k_clip_user: clip username | $k_clip_pass: clip password |" - )" - exit_value=$? + entry="$( + list_passwords \ + | _picker -kb-accept-entry "" \ + -kb-custom-1 "$k_autofill" \ + -kb-custom-2 "$k_clip_user" \ + -kb-custom-3 "$k_clip_pass" \ + -kb-custom-4 "$k_fill_user" \ + -kb-custom-5 "$k_fill_pass" \ + -kb-custom-6 "$k_submenu" \ + -mesg "| $k_autofill: fill credentials | $k_submenu: open entry | $k_fill_user: fill username | $k_fill_pass: fill password | $k_clip_user: clip username | $k_clip_pass: clip password |" + )" + exit_value=$? - echo "$entry" - exit_check "$exit_value" - case "$exit_value" in - "0" | "10") - autofill "$entry" "$autoentry_chain" - ;; - "11") - clip_username "$entry" - ;; - "12") - clip_password "$entry" - ;; - "13") - autofill "$entry" "username" - ;; - "14") - autofill "$entry" "password" - ;; - "15") - entrymenu "$entry" - ;; - esac + echo "$entry" + exit_check "$exit_value" + case "$exit_value" in + "0" | "10") + autofill "$entry" "$autoentry_chain" + ;; + "11") + clip_username "$entry" + ;; + "12") + clip_password "$entry" + ;; + "13") + autofill "$entry" "username" + ;; + "14") + autofill "$entry" "password" + ;; + "15") + entrymenu "$entry" + ;; + esac - exit 0 + exit 0 } set_defaults