Marty Oehme
3f958a65d6
Allows auto-filling and copying secrets to the clipboard. Works on the secrets themselves and any fields contained within the secrets. Key bindings and options can be configured through a file or the environment.
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
|