diff --git a/pass/.local/bin/rofi-pass b/pass/.local/bin/rofi-pass new file mode 100755 index 0000000..e843b50 --- /dev/null +++ b/pass/.local/bin/rofi-pass @@ -0,0 +1,301 @@ +#!/usr/bin/env bash +# + +# selector wrapper +# uses rofi if found, or dmenu if found, complains if no selector available +# passes along any options given to main script +rofi_opts=("$@") +_rofi() { + if type rofi 1>/dev/null 2>/dev/null; then + rofi -dmenu -no-auto-select -i "${rofi_opts[@]}" "$@" -p "Entry" + elif type dmenu 1>/dev/null 2>/dev/null; then + dmenu -i "${rofi_opts[@]}" "$@" -p "Entry" + else + printf "%s: 📦 %s must be installed for %s function.\n" "critical" "rofi/dmenu" "this" >&2 + notify-send "📦 rofi/dmenu" --urgency="critical" "must be installed for this function." + exit 1 + 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- +} + +# read config file +get_config() { + local locations=( + "$RP_CONFIGURATION_FILE" + "${XDG_CONFIG_HOME:-$HOME/.config}/rofi-pass/rofi-pass.conf" + "$HOME/.rofi-pass.conf" + "/etc/rofi-pass.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 + + # 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 rofi-pass config file + # RP_CONFIGURATION_FILE="~/.config/rofi-pass/rofi-pass.conf" + # set options, leaving already set environment variables intact + # try to read any settings from config files + KEY_AUTOFILL="${RP_KEY_AUTOFILL:-$(get_config KEY_AUTOFILL Return)}" + KEY_ENTRY_OPEN="${RP_KEY_ENTRY_OPEN:-$(get_config KEY_ENTRY_OPEN Alt+Return)}" + KEY_FILL_USER="${RP_KEY_FILL_USER:-$(get_config KEY_FILL_USER Alt+u)}" + KEY_CLIP_USER="${RP_KEY_CLIP_USER:-$(get_config KEY_CLIP_USER Ctrl+Alt+u)}" + KEY_FILL_PASS="${RP_KEY_FILL_PASS:-$(get_config KEY_FILL_PASS Alt+p)}" + KEY_CLIP_PASS="${RP_KEY_CLIP_PASS:-$(get_config KEY_CLIP_PASS Ctrl+Alt+p)}" + KEY_ENTRYMENU_FILL="${RP_KEY_ENTRYMENU_FILL:-$(get_config KEY_ENTRYMENU_FILL Return)}" + KEY_ENTRYMENU_CLIP="${RP_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="${RP_KEY_ENTRYMENU_QUIT:-$(get_config KEY_ENTRYMENU_QUIT Alt+BackSpace)}" + + AUTOFILL_BACKEND="${RP_BACKEND:-$(get_config AUTOFILL_BACKEND xdotool)}" + AUTOFILL_CHAIN="${RP_AUTOENTRY_CHAIN:-$(get_config AUTOFILL_CHAIN 'username :tab password')}" + AUTOFILL_DELAY="${RP_AUTOENTRY_DELAY:-$(get_config AUTOFILL_DELAY 30)}" + PASS_USERNAME_FIELD="${RP_PASS_USERNAME_FIELD:-$(get_config PASS_USERNAME_FIELD 'username user login')}" +} + +# exit on escape pressed +# rofi returns exit code 1 on esc +exit_check() { + [ "$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}") + + printf '%s\n' "${password_files[@]}" +} + +# return password for argument passed +show_password() { + pass show "$1" | head -n1 +} + +# send password to clipboard +clip_password() { + 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 clip="$3" + + # 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 + + if [ -n "$clip" ]; then + # copies to clipboard, removes any trailing newlines, + # and only keeps it in for 1 paste (1 loop to read in script, 1 to output) + echo "$value" | xclip -i -selection 'clipboard' -loops 2 -rmlastnl && break + else + echo "$value" && break + fi + + fi + done +} + +# returns the corresponding value for the key passed in +# arguments: +# $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") + + # get everything after first colon, remove whitespace + echo "$value" | cut -d':' -f2- | tr -d '[:blank:]' +} + +# return username for argument passed +show_username() { + _p_get_field "$1" "${PASS_USERNAME_FIELD}" +} + +clip_username() { + _p_get_field "$1" "${PASS_USERNAME_FIELD}" "-c" +} + +show_field() { + _p_get_field "$1" "$2" +} + +clip_field() { + _p_get_field "$1" "$2" "-c" +} + +list_fields() { + pass show "$1" | tail -n+2 +} + +# invoke the dotool to type inputs +_type() { + local tool="${AUTOFILL_BACKEND}" + local toolmode="$1" + local key="$2" + + "$tool" "$toolmode" --delay "${AUTOFILL_DELAY}" "$key" +} + +# automatically fill out fields +# transform special chain entries into valid dotool commands +autofill() { + 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 +} + +# 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 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")" | + _rofi \ + -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 + "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") + entrymenu "$entry" "true" + ;; + esac +} + +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}" + + entry="$( + list_passwords | + _rofi -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=$? + + exit_check "$exit_value" + case "$exit_value" in + "10") + autofill "$entry" "$autoentry_chain" + exit 0 + ;; + "11") + clip_username "$entry" + exit 0 + ;; + "12") + clip_password "$entry" + exit + ;; + "13") + autofill "$entry" "username" + exit + ;; + "14") + autofill "$entry" "password" + exit + ;; + "15") + entrymenu "$entry" + exit + ;; + esac +} + +set_defaults +main