123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- #!/usr/bin/env bash
- #
- # passrofi is a password-store UI using rofi
- # Copyright (C) 2020 Distopico <distopico@riseup.net>
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- #
- # Description:
- # --------------
- # Simple password-store aka 'pass' UI for rofi or dmenu
- #
- # Usage:
- # ---------------
- # * rofi mode: rofi -modi "pass:passrofi" -show
- # * rofi dmenu: /path/to/script/passrofi
- #
- shopt -s nullglob globstar
- # Options
- COPY_PASS="copy pass"
- COPY_OTP="copy otp"
- COPY_FIELD="copy field"
- SHOW_FIELD="show field"
- COPY_ACTION="copy"
- SHOW_ACTION="copy"
- QUIT_NAME="quit ›"
- RETURN_NAME="‹ return"
- OPTIONS=( "$COPY_PASS|*" "$COPY_OTP|•" "$COPY_FIELD|»" "$SHOW_FIELD|›" )
- ACTIONS=( "$COPY_ACTION|‣" "$SHOW_ACTION|▹" )
- # Setup
- BASE_PATH=$(dirname "$0")
- PASS_CMD=${PASSWORD_STORE_CMD:-"$BASE_PATH/passp"}
- PASS_CLIP_TIME=${PASSWORD_STORE_CLIP_TIME:-45}
- PASS_ROOT_DIR=${PASSWORD_STORE_DIR-~/.password-store}
- PASS_X_SELECTION=${PASSWORD_STORE_X_SELECTION:-clipboard}
- PASS_ROFI_FIELDS_SEPARATOR=${PASSWORD_STORE_ROFI_FIELDS_SEPARATOR:-":"}
- NOTIFY_CMD="notify-send"
- PROMPT="pass"
- BASE64="base64"
- # Check commands
- has_notify=0
- rofi_mode=0
- if [[ -x "$(command -v $NOTIFY_CMD)" ]]; then
- has_notify=1
- fi
- if ! [[ -x "$(command -v pgrep)" ]]; then
- # Set pgrep fallback
- function pgrep() {
- ps axf | grep $1 | grep -v grep | awk '{print $1}'
- }
- fi
- if ! [[ -z "$(pgrep "rofi")" ]]; then
- rofi_mode=1
- fi
- rofi_dmenu () {
- rofi -dmenu -p $PROMPT "$@"
- }
- # main menu
- main_menu () {
- for opt in "${OPTIONS[@]}"
- do
- IFS='|' read -ra val <<< "$opt"
- printf "%s\n" "${val[0]}"
- done
- }
- # Clip with the available command in X or Wayland
- clip() {
- if [[ -n $WAYLAND_DISPLAY ]]; then
- local copy_cmd=( wl-copy )
- local paste_cmd=( wl-paste -n )
- if [[ $PASS_X_SELECTION == primary ]]; then
- copy_cmd+=( --primary )
- paste_cmd+=( --primary )
- fi
- local display_name="$WAYLAND_DISPLAY"
- elif [[ -n $DISPLAY ]]; then
- local copy_cmd=( xclip -selection "$PASS_X_SELECTION" )
- local paste_cmd=( xclip -o -selection "$PASS_X_SELECTION" )
- local display_name="$DISPLAY"
- else
- die "Error: No X11 or Wayland display detected"
- fi
- local sleep_argv0="password store sleep on display $display_name"
- # This base64 is because bash cannot store binary data in a shell variable
- pkill -f "^$sleep_argv0" 2>/dev/null && sleep 0.5
- local before="$("${paste_cmd[@]}" 2>/dev/null | $BASE64)"
- echo -n "$1" | "${copy_cmd[@]}" >/dev/null 2>&1
- (
- ( exec -a "$sleep_argv0" bash <<<"trap 'kill %1' TERM; sleep '$PASS_CLIP_TIME' & wait" )
- local now="$("${paste_cmd[@]}" | $BASE64)"
- [[ $now != $(echo -n "$1" | $BASE64) ]] && before="$now"
- echo "$before" | $BASE64 -d | "${copy_cmd[@]}"
- ) >/dev/null 2>&1 & disown
- }
- # Get identifier type
- get_option_type () {
- local options=("${OPTIONS[@]}", "${ACTIONS[@]}")
- value=""
- for opt in "${options[@]}"
- do
- IFS='|' read -ra val <<< "$opt"
- if [[ "$1" == "${val[0]}" ]] || [[ "$1" == "${val[1]}" ]]; then
- if [[ "$2" == "identifie" ]]; then
- value=${val[1]}
- else
- value=${val[0]}
- fi
- break
- fi
- done
- echo "$value"
- }
- # Get passwords list
- get_pass_list () {
- password_files=( "$PASS_ROOT_DIR"/**/*.gpg )
- password_files=( "${password_files[@]#"$PASS_ROOT_DIR"/}" )
- password_files=( "${password_files[@]%.gpg}" )
- identifier=$(get_option_type "$@" "identifie")
- printf "$RETURN_NAME\n"
- printf "$identifier| %s\n" "${password_files[@]}"
- }
- # Call copy command
- copy_pass () {
- IFS='|' read -ra val <<< "$@"
- action=$(get_option_type "${val[0]}")
- password="$(echo -e "${val[1]}" | sed -e 's/^[[:space:]]*//')"
- should_notify=0
- response=1
- [[ -n $password ]] || exit
- case "${action}" in
- "$COPY_PASS")
- $PASS_CMD -c "$password" >/dev/null 2>&1
- should_notify=1
- response=$?
- ;;
- "$COPY_OTP")
- $PASS_CMD otp -c "$password" >/dev/null 2>&1
- should_notify=1
- response=$?
- ;;
- "$COPY_FIELD")
- pass_field=$($PASS_CMD show "$password")
- response=$?
- pass_key_value=$(printf '%s\n' "${pass_field}" | tail -n+2)
- return_opt="$RETURN_NAME"
- empty_msg="not have values"
- # Check if password not have additional fields
- if [[ -z "$pass_key_value" ]]; then
- if [[ $rofi_mode -eq 1 ]]; then
- echo -en "\0message\x1f<b>$password</b>: $empty_msg\n"
- printf "%s\n" "$return_opt"
- else
- printf "%s\n" "$password: $empty_msg | $RETURN_NAME"
- fi
- exit 0
- fi
- # Return opts
- printf "%s\n" "$return_opt"
- # Show pass fields
- local line=0
- while read -r LINE; do
- line_key="${LINE%%: *}"
- line_val="${LINE#* }"
- content="$line_key: $line_val"
- if [[ $line_key =~ "otpauth://" ]]; then
- # exclude OTP value/secret
- continue
- fi
- ((line++))
- if [[ $line_key = $line_val ]]; then
- content="$line_val"
- fi
- printf "‣| $line) %s [$password]\n" "$content"
- done < <(printf "%s\n" "${pass_key_value}")
- ;;
- "$COPY_ACTION")
- local data=$(echo "$password" | sed 's/\([[:digit:]]\+\))[[:space:]]\+\(.*\)[[:space:]]\+\[\([^]]*\)\]/\1,\2,\3/')
- IFS="," read -ra field_data <<< "$data"
- IFS="$PASS_ROFI_FIELDS_SEPARATOR" read -ra field_values <<< "${field_data[1]}"
- local line=$(("${field_data[0]}" + 1))
- local field_len=${#field_values[@]}
- password="${field_data[2]}"
- # Field name or line to show on notification
- field=" (<b>${field_values[0]}</b>)"
- if [[ $field_len -lt 2 ]]; then
- field=" (<b>line: $line</b>)"
- fi
- # Copy non-password entry without field type
- # e.g. "email: myname@email.com" -> "myname@email.com"
- if [[ $line -gt 1 ]]; then
- # trim leading/trailing spaces
- local copy_value=$(echo "${field_values[1]}" | sed 's,^ *,,; s, *$,,')
- clip echo "$copy_value"
- fi
- should_notify=1
- response=$?
- ;;
- *)
- response=1
- ;;
- esac
- if [[ $response -eq 1 ]]; then
- if [[ $rofi_mode -eq 1 ]]; then
- echo -en "\0message\x1f<b>$action</b>: error copying\n"
- printf "$QUIT_NAME\n"
- else
- printf "$action: error copying | $QUIT_NAME\n"
- fi
- elif [[ $has_notify -eq 1 ]] && [[ $should_notify -eq 1 ]]; then
- $NOTIFY_CMD "pass" "Copied <b>$password</b>$field to clipboard. Will clear in $PASS_CLIP_TIME seconds." >/dev/null 2>&1
- fi
- }
- call_action () {
- if [[ $rofi_mode -eq 1 ]]; then
- if ! [[ -z $(get_option_type "$@") ]]; then
- get_pass_list "$@"
- else
- copy_pass "$@"
- fi
- else
- call_dmenu "$@"
- fi
- }
- call_dmenu () {
- action=$(main_menu | rofi_dmenu "$@")
- [[ -n $action ]] || exit
- password=$(get_pass_list "$action" | rofi_dmenu "$@")
- [[ -n $password ]] || exit
- result=$(copy_pass "$password")
- if [[ "$result" ]]; then
- echo "$result" | rofi_dmenu "$@"
- fi
- }
- call_main () {
- # Show as rofi mode or dmenu
- if [[ $rofi_mode -eq 1 ]]; then
- echo -en "\0message\x1f\n" # reset message
- echo -en "\x00prompt\x1f$PROMPT\n"
- main_menu
- else
- call_dmenu
- fi
- }
- # Inputs
- if [[ x"$@" = x"$QUIT_NAME" ]]; then
- exit 0
- elif [[ x"$@" = x"$RETURN_NAME" ]]; then
- call_main
- elif [[ "$@" ]]; then
- call_action "$@"
- else
- call_main
- fi
|