#!/usr/bin/env bash # rofi-pass # (c) 2015 Rasmus Steinke basecommand=$(echo "$0" | gawk '{ print $1 }') # set default settings _rofi () { rofi -i -width 700 -no-levenshtein-sort "$@" } URL_field='url' USERNAME_field='user' AUTOTYPE_field='autotype' delay=2 default_do='menu' # menu, copyPass, typeUser, typePass, copyUser, copyUrl, viewEntry, typeMenu, actionMenu, copyMenu, openUrl auto_enter='false' notify='false' password_length='20' help_color="" clip=primary default_user=john_doe default_user2=mary_ann password_length=12 autotype="Alt+1" type_user="Alt+2" type_pass="Alt+3" open_url="Alt+4" copy_name="Alt+u" copy_url="Alt+l" copy_pass="Alt+p" show="Alt+o" copy_entry="Alt+2" type_entry="Alt+1" copy_menu="Alt+c" action_menu="Alt+a" type_menu="Alt+t" help="Alt+h" switch="Alt+x" insert_pass="Alt+n" # get all password files and create an array list_passwords() { cd "${root}" || exit passwords=( **/*.gpg ) for password in "${passwords[@]}"; do filename="${password}" filename="${filename%.gpg}" echo "$filename" done } doClip () { if [[ $clip == "primary" ]]; then xclip elif [[ $clip == "clipboard" ]]; then xclip -selection clipboard; elif [[ $clip == "both" ]]; then xclip; xclip -o | xclip -selection clipboard; fi } checkIfPass () { rm -f "$HOME/.cache/rofi-pass/last_used" echo "${root}: $selected_password" > "$HOME/.cache/rofi-pass/last_used" chmod 600 "$HOME/.cache/rofi-pass/last_used" } autopass () { rm -f "$HOME/.cache/rofi-pass/last_used" echo "${root}: $selected_password" > "$HOME/.cache/rofi-pass/last_used" chmod 600 "$HOME/.cache/rofi-pass/last_used" if [[ -z "${stuff["$AUTOTYPE_field"]}" ]]; then if [[ "${stuff["${USERNAME_field}"]}" ]]; then echo -n "${stuff["${USERNAME_field}"]}" | xdotool type --clearmodifiers --file - xdotool key Tab fi echo -n "${password}" | xdotool type --clearmodifiers --file - sleep 1 if [[ ${auto_enter} == "true" ]]; then xdotool key Return fi else for word in ${stuff["$AUTOTYPE_field"]}; do if [[ $word == ":tab" ]]; then xdotool key Tab; elif [[ $word == ":space" ]]; then xdotool key space elif [[ $word == ":delay" ]]; then sleep "${delay}"; elif [[ $word == ":enter" ]]; then xdotool key Return; elif [[ $word == "pass" ]]; then echo -n "${password}" | xdotool type --clearmodifiers --file - else echo -n "${stuff[${word}]}" | xdotool type --clearmodifiers --file - fi done if [[ ${auto_enter} == "true" ]]; then xdotool key Return fi fi clearUp } openURL () { checkIfPass $BROWSER "$(pass "$selected_password" | grep "${URL_field}: " | gawk '{sub(/:/,"")}{print $2}1' | head -1)"; exit; clearUp } typeUser () { checkIfPass echo -n "${stuff[${USERNAME_field}]}" | xdotool type --clearmodifiers --file - clearUp } typePass () { checkIfPass echo -n "${password}" | xdotool type --clearmodifiers --file - if [[ $notify == "true" ]]; then if [[ "${stuff[notify]}" == "false" ]]; then : else notify-send "rofi-pass" "finished typing password"; fi elif [[ $notify == "false" ]]; then if [[ "${stuff[notify]}" == "true" ]]; then notify-send "rofi-pass" "finished typing password"; else : fi fi clearUp } typeField () { checkIfPass echo -n "${stuff[${typefield}]}" | xdotool type --clearmodifiers --file - clearUp } copyUser () { checkIfPass echo -n "${stuff[${USERNAME_field}]}" | doClip clearUp } copyField () { checkIfPass echo -n "${stuff[${copyfield}]}" | doClip clearUp } copyURL () { checkIfPass echo -n "${stuff[${URL_field}]}" | doClip clearUp } copyPass () { checkIfPass echo -n "$password" | doClip notify-send "rofi-pass" "Copied Password\nClearing in 45 seconds" $(sleep 45; echo -n "" | xclip; echo "" | xclip -selection clipboard | notify-send "rofi-pass" "Clipboard cleared") & } viewEntry () { checkIfPass showEntry "${selected_password}" } generatePass () { askGen () { askGenMenu=$(echo -e "Yes\nNo" | rofi -dmenu -p "Generate new Password for ${selected_password}? > ") if [[ $askGenMenu == "Yes" ]]; then true elif [[ $askGenMenu == "No" ]]; then generatePass fi } checkIfPass symbols=$(echo -e "0 Cancel\n---\n1 Yes\n2 No" | rofi -dmenu -p "Use Symbols? > ") if [[ $symbols == "0 Cancel" ]]; then mainMenu; elif [[ $symbols == "1 Yes" ]]; then symbols=""; elif [[ $symbols == "2 No" ]]; then symbols="-n"; fi HELP="Enter Number or hit Enter to use default length" length=$(echo -e "" | _rofi -dmenu -mesg "${HELP}" -p "Password length? (Default: ${password_length}) > ") askGen if [[ $length == "" ]]; then pass generate ${symbols} -i "$selected_password" "${password_length}" > /dev/null; else pass generate ${symbols} -i "$selected_password" "${length}" > /dev/null; fi } # main Menu mainMenu () { if [[ $1 == "--bmarks" ]]; then selected_password="$(list_passwords 2>/dev/null \ | _rofi -mesg "Bookmarks Mode. ${switch} to switch" \ -dmenu \ -kb-custom-1 "Alt+x" \ -select "$entry" \ -p "rofi-pass > ")" rofi_exit=$? if [[ $rofi_exit -eq 1 ]]; then exit elif [[ $rofi_exit -eq 10 ]]; then $(${basecommand}) elif [[ $rofi_exit -eq 0 ]]; then openURL fi else unset selected_password HELP="Welcome to rofi-pass. Use ${insert_pass} to create a new pass entry. Run ${default_do} with Enter. For more help hit ${help}." selected_password="$(list_passwords 2>/dev/null \ | _rofi -mesg "${HELP}" \ -dmenu -kb-custom-1 "${autotype}" \ -kb-custom-2 "${type_user}" \ -kb-custom-3 "${type_pass}" \ -kb-custom-4 "${open_url}" \ -kb-custom-5 "${copy_name}" \ -kb-custom-6 "${copy_pass}" \ -kb-custom-7 "${show}" \ -kb-custom-8 "${copy_url}" \ -kb-custom-9 "${type_menu}" \ -kb-custom-14 "${action_menu}" \ -kb-custom-15 "${copy_menu}" \ -kb-custom-16 "${help}" \ -kb-custom-17 "${switch}" \ -kb-custom-18 "${insert_pass}" \ -dmenu \ -select "$entry" \ -p "rofi-pass > ")" rofi_exit=$? if [[ $rofi_exit -eq 1 ]]; then exit fi # generate Array of fields # password_temp=$(PASSWORD_STORE_DIR="${root}" pass "$selected_password") # password="${password_temp%%$'\n'*}" # fields="$(echo "${password_temp}" | tail -n +2)" # pass_key_value=$(echo "${fields}" | awk '$1 ~ /:$/{$1=$1;print}') mapfile -t password_temp < <(PASSWORD_STORE_DIR="${root}" pass "$selected_password") password=${password_temp[0]} fields=$(printf '%s\n' "${password_temp[@]:1}" | awk '$1 ~ /:$/{$1=$1;print}') declare -A stuff stuff["pass"]=${password} if [[ -n $fields ]]; then while read -r LINE; do _id="${LINE%%: *}" _val="${LINE#* }" stuff["${_id}"]=${_val} done < <(echo "${fields}") if test "${stuff['autotype']+autotype}" then : else stuff["autotype"]="${USERNAME_field} :tab pass" fi fi fi pass_content="$(for key in "${!stuff[@]}"; do echo "${key}: ${stuff[$key]}"; done)" # actions based on keypresses if [[ "${rofi_exit}" -eq 0 ]]; then typeMenu; elif [[ "${rofi_exit}" -eq 13 ]]; then openURL; elif [[ "${rofi_exit}" -eq 10 ]]; then sleep 0.2; autopass; elif [[ "${rofi_exit}" -eq 14 ]]; then copyMenu; elif [[ "${rofi_exit}" -eq 11 ]]; then sleep 0.2; typeUser; elif [[ "${rofi_exit}" -eq 12 ]]; then sleep 0.2; typePass; elif [[ "${rofi_exit}" -eq 17 ]]; then copyURL; elif [[ "${rofi_exit}" -eq 16 ]]; then viewEntry; elif [[ "${rofi_exit}" -eq 18 ]]; then export default_do="menu"; typeMenu; elif [[ "${rofi_exit}" -eq 15 ]]; then copyPass; elif [[ "${rofi_exit}" -eq 23 ]]; then actionMenu; elif [[ "${rofi_exit}" -eq 25 ]]; then unset selected_password; helpMenu; elif [[ "${rofi_exit}" -eq 24 ]]; then copyMenu; elif [[ "${rofi_exit}" -eq 26 ]]; then $(${basecommand} --bmarks); elif [[ "${rofi_exit}" -eq 27 ]]; then insertPass; fi clearUp } clearUp () { password='' selected_password='' unset stuff unset password unset selected_password unset password_temp unset stuff } helpMenu () { helptext=$(echo -e "${autotype}: Autotype ${type_user}: Type Username ${type_pass}: Type Password --- ${copy_name}: Copy Username ${copy_pass}: Copy Password ${copy_url}: Copy URL ${open_url}: Open URL ${copy_menu}: Copy Custom Field --- ${action_menu}: Edit, Move, Delete, Re-generate Submenu ${show}: Show Password File ${insert_pass}: Insert new Pass Entry ${switch}: Switch Pass/Bookmark Mode" | _rofi -dmenu -mesg "Hint: All hotkeys are configurable in config file" -p "Help > ") help_val=$? if [[ $help_val -eq 1 ]]; then exit; else unset helptext; mainMenu; fi } typeMenu () { if [[ -n $default_do ]]; then if [[ $default_do == "menu" ]]; then checkIfPass typefield=$(printf '%s\n' "${!stuff[@]}" | sort | _rofi -dmenu -p "Choose Field to type > ") val=$? if [[ $val -eq 1 ]]; then exit fi if [[ $typefield == "" ]]; then exit; elif [[ $typefield == "password" ]]; then typePass elif [[ $typefield == "${AUTOTYPE_field}" ]]; then autopass else typeField fi clearUp elif [[ $default_do == "${AUTOTYPE_field}" ]]; then autopass else $(${default_do}) fi fi } copyMenu () { checkIfPass copyfield=$(printf '%s\n' "${!stuff[@]}" | sort | _rofi -dmenu -p "Choose Field to copy > ") val=$? if [[ $val -eq 1 ]]; then exit; fi if [[ $copyfield == "pass" ]]; then copyPass; else copyField fi clearUp } actionMenu () { checkIfPass action=$(echo -e "< Return\n---\n1 Move Password File\n2 Delete Password File\\n3 Edit Password File\n4 Generate New Password" | _rofi -dmenu -p "Choose Action > ") if [[ ${action} == "1 Move Password File" ]]; then manageEntry move; elif [[ ${action} == "2 Delete Password File" ]]; then manageEntry delete; elif [[ ${action} == "3 Edit Password File" ]]; then manageEntry edit; elif [[ ${action} == "4 Generate New Password" ]]; then generatePass; elif [[ ${action} == "< Return" ]]; then mainMenu; elif [[ ${action} == "" ]]; then exit fi } showEntry () { if [[ -z $pass_content ]]; then password_temp=$(PASSWORD_STORE_DIR="${root}" pass "$selected_password") password="${password_temp%%$'\n'*}" pass_key_value=$(echo "${password_temp}" | tail -n+2 | grep ': ') declare -A stuff while read -r LINE; do _id="${LINE%%: *}" _val="${LINE#* }" stuff["${_id}"]=${_val} done < <(echo "${pass_key_value}") stuff["pass"]=${password} if test "${stuff['autotype']+autotype}" then : else stuff["autotype"]="${USERNAME_field} :tab pass" fi pass_content="$(for key in "${!stuff[@]}"; do echo "${key}: ${stuff[$key]}"; done)" fi HELP="${copy_entry}: Copy Entry" bla=$(echo -e "< Return\n${pass_content}" | _rofi -dmenu -mesg "Enter: Copy entry to clipboard" -p "> ") rofi_exit=$? word=$(echo "$bla" | gawk -F': ' '{print $1}') if [[ ${rofi_exit} -eq 1 ]]; then exit elif [[ ${rofi_exit} -eq 0 ]]; then if [[ ${bla} == "< Return" ]]; then mainMenu else if [[ -z $(echo -n "${stuff[${word}]}") ]]; then echo -n "$word" | doClip else echo -n "${stuff[${word}]}" | doClip fi notify-send "rofi-pass" "Copied Password\nClearing in 45 seconds" $(sleep 45; echo -n "" | xclip; echo "" | xclip -selection clipboard | notify-send "rofi-pass" "Clipboard cleared") & exit fi fi exit unset stuff unset password unset selected_password unset password_temp unset stuff exit } manageEntry () { if [[ "$1" == "edit" ]]; then EDITOR=$EDITOR PASSWORD_STORE_DIR="${root}" pass edit "${selected_password}" mainMenu elif [[ $1 == "move" ]]; then cd "${root}" || exit selected_password2=$(basename "$selected_password" .gpg) group=$(find -type d -not -iwholename '*.git*' -printf '%d\t%P\n' | sort -r -nk1 | cut -f2- | _rofi -dmenu -p "Choose Group > ") if [[ $group == "" ]]; then exit fi PASSWORD_STORE_DIR="${root}" pass mv "$selected_password" "${group}" mainMenu elif [[ "$1" == "delete" ]]; then HELP="Selected entry: ${selected_password}" ask=$(echo -e "Yes\nNo" | _rofi -mesg "${HELP}" -dmenu -p "Are You Sure? > ") if [[ "$ask" == "Yes" ]]; then PASSWORD_STORE_DIR="${root}" pass rm --force "${selected_password}" elif [[ "$ask" == "No" ]]; then mainMenu elif [[ -z "$ask" ]]; then exit fi else mainMenu fi } listgpg () { find . -type f -not -path '*/\.*' | cut -c 3- } insertPass () { url=$(xclip -o) cd "${root}" name="$(listgpg | rofi -dmenu -format 'f' -mesg "Type name, make sure it is unique" -p "> ")" # name="$(echo -e "$(list_passwords 2>/dev/null)" | rofi -dmenu -mesg "Type name, make sure it is unique" -p "> ")" val=$? if [[ $val -eq 1 ]]; then exit fi user=$(echo -e "${default_user2}\n$USER\n${default_user}" | rofi -dmenu -mesg "Chose Username or type" -p "> ") val=$? if [[ $val -eq 1 ]]; then exit fi group=$(echo -e "No Group\n---\n$(find -type d -not -iwholename '*.git*' -printf '%d\t%P\n' | sort -r -nk1 | cut -f2-)" | rofi -dmenu -p "Choose Group > ") val=$? if [[ $val -eq 1 ]]; then exit fi if [[ "$group" == "No Group" ]]; then if [[ $url == http* ]]; then echo -e "PASS\n---\n${USERNAME_field}: $user\n${URL_field}: $url" | PASSWORD_STORE_DIR="${root}" pass insert -m "${name}" > /dev/null && PASSWORD_STORE_DIR="${root}" pass generate -ni "${name}" "${password_length}" >/dev/null && PASSWORD_STORE_DIR="${root}" pass edit "${name}" else echo -e "PASS\n---\n${USERNAME_field}: $user" | PASSWORD_STORE_DIR="${root}" pass insert -m "${name}" > /dev/null && PASSWORD_STORE_DIR="${root}" pass generate -ni "${name}" "${password_length}" >/dev/null && PASSWORD_STORE_DIR="${root}" pass edit "${name}" fi else if [[ $url == http* ]]; then echo -e "PASS\n---\n${USERNAME_field}: $user\n${URL_field}: $url" | PASSWORD_STORE_DIR="${root}" pass insert -m "${group}/${name}" > /dev/null && PASSWORD_STORE_DIR="${root}" pass generate -ni "${group}/${name}" "${password_length}" >/dev/null && PASSWORD_STORE_DIR="${root}" pass edit "${group}/${name}" else echo -e "PASS\n---\n${USERNAME_field}: $user" | PASSWORD_STORE_DIR="${root}" pass insert -m "${group}/${name}" > /dev/null && PASSWORD_STORE_DIR="${root}" pass generate -ni "${group}/${name}" "${password_length}" >/dev/null && PASSWORD_STORE_DIR="${root}" pass edit "${group}/${name}" fi fi } help_msg () { echo "rofi-pass (Version: 1.2)" echo "" echo -e "Usage:\n" echo "--insert insert new entry to password store" echo "--manage edit/move/delete entries" echo -e "--root set custom root directory" echo "--last-used highlight last used item" echo "--show-last show details of last used Entry" echo "--bmarks run bookmarks Mode" echo "" } main () { # enable extended globbing shopt -s nullglob globstar # check if global config exists and load it if [[ -f /etc/rofi-pass.conf ]]; then source /etc/rofi-pass.conf fi # check if local config exists and load it if [[ -f "$HOME/.config/rofi-pass/config" ]]; then source "$HOME/.config/rofi-pass/config" fi # create tmp dir if [[ ! -d "$HOME/.cache/rofi-pass" ]]; then mkdir "$HOME/.cache/rofi-pass" fi if [[ -n $keyboard ]]; then setxkbmap ${keyboard} fi # set help color if [[ $help_color == "" ]]; then help_color=$(rofi -dump-xresources | grep 'rofi.color.normal' | gawk -F ',' '/,/{gsub(/ /, "", $2); print $2}') fi # check for BROWSER variable, use xdg-open as fallback if [[ -z $BROWSER ]]; then export BROWSER=xdg-open fi # check if alternative root directory was given on commandline if [[ -r "$HOME/.cache/rofi-pass/last_used" ]] && [[ $1 == "--last-used" || $1 == "--show-last" ]]; then export root; root=$(awk -F ': ' '{ print $1 }' "$HOME/.cache/rofi-pass/last_used") elif [[ -n "$2" && "$1" == "--root" ]]; then export root="${2}" elif [[ -n $root ]]; then export root="${root}" elif [[ -n ${PASSWORD_STORE_DIR} ]]; then export root=${PASSWORD_STORE_DIR} else export root="$HOME/.password-store" fi export PASSWORD_STORE_DIR="${root}" case $1 in --insert) insertPass ;; --root) mainMenu ;; --manage) manageEntry ;; --help) help_msg ;; --last-used) if [[ -r "$HOME/.cache/rofi-pass/last_used" ]]; then entry="$(awk -F ': ' '{ print $2 }' "$HOME/.cache/rofi-pass/last_used")" fi mainMenu ;; --show-last) if [[ -r "$HOME/.cache/rofi-pass/last_used" ]]; then selected_password="$(awk -F ': ' '{ print $2 }' "$HOME/.cache/rofi-pass/last_used")" viewEntry else mainMenu fi ;; --bmarks) mainMenu --bmarks; ;; *) mainMenu ;; esac } main "$@"