#!/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