Fixed entry selection not working with new type checking fallback mechanism, which would output to stdout, which the auto filling in turn attempted to read from.
261 lines
6.9 KiB
Bash
Executable file
261 lines
6.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Original inspiration from https://github.com/carnager/rofi-pass/
|
|
|
|
# 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
|
|
exist rofi critical "rofi-gopass" || exit 0
|
|
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=(
|
|
"$RGP_CONFIGURATION_FILE"
|
|
"${XDG_CONFIG_HOME:-$HOME/.config}/rofi-gopass/rofi-gopass.conf"
|
|
"$HOME/.rofi-gopass.conf"
|
|
"/etc/rofi-gopass.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-gopass config file
|
|
# ROFI_PASS_CONFIGURATION_FILE="~/.config/rofi-gopass"
|
|
# set options, leaving already set environment variables intact
|
|
# try to read any settings from config files
|
|
KEY_AUTOFILL="${RGP_KEY_AUTOFILL:-$(get_config KEY_AUTOFILL Return)}"
|
|
KEY_ENTRY_OPEN="${RGP_KEY_ENTRY_OPEN:-$(get_config KEY_ENTRY_OPEN Alt+Return)}"
|
|
KEY_FILL_USER="${RGP_KEY_FILL_USER:-$(get_config KEY_FILL_USER Alt+u)}"
|
|
KEY_CLIP_USER="${RGP_KEY_CLIP_USER:-$(get_config KEY_CLIP_USER Ctrl+Alt+u)}"
|
|
KEY_FILL_PASS="${RGP_KEY_FILL_PASS:-$(get_config KEY_FILL_PASS Alt+p)}"
|
|
KEY_CLIP_PASS="${RGP_KEY_CLIP_PASS:-$(get_config KEY_CLIP_PASS Ctrl+Alt+p)}"
|
|
KEY_ENTRYMENU_FILL="${RGP_KEY_ENTRYMENU_FILL:-$(get_config KEY_ENTRYMENU_FILL Return)}"
|
|
KEY_ENTRYMENU_CLIP="${RGP_KEY_ENTRYMENU_CLIP:-$(get_config KEY_ENTRYMENU_CLIP Alt+Return)}"
|
|
KEY_ENTRYMENU_QUIT="${RGP_KEY_ENTRYMENU_QUIT:-$(get_config KEY_ENTRYMENU_QUIT Alt+BackSpace)}"
|
|
|
|
AUTOFILL_BACKEND="${RGP_BACKEND:-$(get_config AUTOFILL_BACKEND xdotool)}"
|
|
AUTOFILL_CHAIN="${RGP_AUTOENTRY_CHAIN:-$(get_config AUTOFILL_CHAIN 'username :tab password')}"
|
|
AUTOFILL_DELAY="${RGP_AUTOENTRY_DELAY:-$(get_config AUTOFILL_DELAY 30)}"
|
|
GOPASS_USERNAME_FIELD="${RGP_GOPASS_USERNAME_FIELD:-$(get_config GOPASS_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 gopass store
|
|
# TODO choose password store
|
|
# TODO only show website names (+ folder names), and account names for multiple accounts on one site
|
|
list_passwords() {
|
|
gopass list --flat
|
|
}
|
|
|
|
# return password for argument passed
|
|
show_password() {
|
|
gopass show -f --password "$1"
|
|
}
|
|
|
|
# send password to clipboard
|
|
clip_password() {
|
|
gopass show -c "$1"
|
|
}
|
|
|
|
# attempt to return the field specified
|
|
# attempts all (space separated) fields until the
|
|
# first one successfully returned
|
|
_gp_get_field() {
|
|
local gp_entry="$1"
|
|
local gp_field="$2"
|
|
local clip="$3"
|
|
|
|
for key in $gp_field; do
|
|
# return on first successfully returned key
|
|
if [ -n "$clip" ]; then
|
|
gopass show -c "$gp_entry" "$key" && break
|
|
else
|
|
gopass show -f "$gp_entry" "$key" && break
|
|
fi
|
|
done
|
|
}
|
|
|
|
# return username for argument passed
|
|
show_username() {
|
|
_gp_get_field "$1" "${GOPASS_USERNAME_FIELD}"
|
|
}
|
|
|
|
clip_username() {
|
|
_gp_get_field "$1" "${GOPASS_USERNAME_FIELD}" "-c"
|
|
}
|
|
|
|
show_field() {
|
|
_gp_get_field "$1" "$2"
|
|
}
|
|
|
|
clip_field() {
|
|
_gp_get_field "$1" "$2" "-c"
|
|
}
|
|
|
|
list_fields() {
|
|
gopass show -f "$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 gopass entry, containing its individual fields
|
|
entrymenu() {
|
|
local entry="$1"
|
|
local k_entrymenu_fill="${KEY_ENTRYMENU_FILL}"
|
|
local k_entrymenu_clip="${KEY_ENTRYMENU_CLIP}"
|
|
local k_entrymenu_quit="${KEY_ENTRYMENU_QUIT}"
|
|
|
|
local pass_obfuscation="(hidden)"
|
|
|
|
local field
|
|
field=$(
|
|
printf "password: %s\n%s" "$pass_obfuscation" "$(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" \
|
|
-mesg " ᐊ $k_entrymenu_quit ᐊ | $k_entrymenu_fill: fill selection | $k_entrymenu_clip: clip selection |"
|
|
)
|
|
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
|
|
;;
|
|
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
|