You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

720 lines
12 KiB

#!/usr/bin/env sh
#
##
### _ __ __ _ ___ ___ _ __
### | '_ \ / _` / __/ __| '__|
### | |_) | (_| \__ \__ \ |
### | .__/ \__,_|___/___/_|
### |_|
### _ _|_ _ ._ _ _
### (_\/|_(_)\/|_)(_|(/_
### / / | _|
###
### passr
### fzf cli wrapper for pass
### copyright (c) 2020 - 2022 | cytopyge
###
### GNU GPLv3 GENERAL PUBLIC LICENSE
### 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 <https://www.gnu.org/licenses/>.
### https://www.gnu.org/licenses/gpl-3.0.txt
###
### y3l0b3b5z2u=:matrix.org @cytopyge@mastodon.social
###
##
#
: '
## dependencies
pass, gpg, cryptsetup, fzf
pwgn, vault, source functions
## usage
### status management
% passr open
% passr o
% passr login
% passr i
% passr status
% passr s
% passr logout
% passr u
% passr close
% passr c
### operational modes
#### object
% passr
#### element
# passr $object
% passr email_john
#### direct
# passr $object $element
% passr email_john password
# '
# initial definitions
## script
script_name='passr'
developer='cytopyge'
license='gplv3'
initial_release='2020'
## arguments
args=$@
object=$1
element=$2
arg_object="$object"
arg_element="$element"
## .password-store
## $crypto-source is the vault in which $store is saved
crypto_source="$XDG_DATA_HOME/c/keys/pass/vlt_pass"
## $store_l is the symlink location to the pass password store
## recommended is to mount an encrypted vault container
## then symlink .password-store to a container mountpoint
## default symlink: $HOME/.password-store
## default target: $HOME/dock/vlt/pass
store_l="$HOME/.password-store"
## $store is the pass password store location
## the symlinks target
store=$(readlink $store_l)
## source functions
source_dir="$XDG_DATA_HOME/c/git/code/source/function"
source $source_dir/text_appearance
source $source_dir/reply_functions
source $source_dir/get_sudo
elements=(\
"1 url" \
"2 userid" \
"" \
"3 password" \
"4 otp" \
"5 unlock" \
"6 peacto" \
"" \
"A autoentry" \
"" \
"E edit" \
"S show" \
"C copy" \
"" \
"ESC cancel")
: '
$object definition
^^^^^^^^^^^^^^^^^^
an object refers to a gpg encrypted file (*.gpg)
the pass man page refers to objects by naming them 'passwords'
so insert a new password means actually making a new .gpg file
a gpg file can contain anything, also passwords
pass expects the first line of the gpg file to be the password
this script makes extensive use of that
pass names gpg files from the password_store_root without extension
example:
$password_store_root/subdir/object.gpg <=> subdir/object
$object contains in this script the value: 'subdir/object'
$element contains the value of an element in the $object
i.e. this can be a password, url or userid (among more)
$element definition
^^^^^^^^^^^^^^^^^^^
1. an element can be retrieved from an object
regular elements are i.e.: url, userid, password
2. an element can be an action applied to an object
retrievals are numbered and actions are lettered in the elements menu
gpg file contents
^^^^^^^^^^^^^^^^^
example gpg file (mail_john.gpg) for passr to work properly:
123456
userid: john
url: https://login.mail.com
unlock: password
peacto: iloveyou
comments: important
otp-strings can be stored into otp_mail_john.gpg
'
#--------------------------------
get_object() {
object=$arg_object
element=$arg_element
case $object in
open | o)
object='open'
vault_open
;;
login | i)
object='login'
log_in
;;
status | s)
object='status'
show_status
exit 0
;;
logout | u)
object='logout'
log_out
;;
close | c)
object='close'
log_out
close
;;
*)
get_mountpoint_status
case $mp_status in
0)
object='login'
pass_mount
;;
$store)
get_cache_status
case $cache_status in
0)
log_in
get_object
;;
1)
case $object in
'')
object=$(find -L $store_l -type f -iname '*.gpg' | \
# with sed we remove the leading path of $store_l
# internal field separator is '|'
# because the path itself contains '/'
sed "s|$store_l/||g" | \
# with sed we remove the trailing .gpg extension
sed "s/.gpg//g" | \
sort | \
fzf --prompt='select object ')
;;
esac
;;
esac
;;
esac
;;
esac
}
vault_open() {
pass_mount
}
log_in() {
get_mountpoint_status
case $mp_status in
$store)
from_log_in=1
element='login'
gpg_decrypt
pass_element
;;
*)
vault_open
log_in
;;
esac
}
log_out() {
gpg-connect-agent -q reloadagent /bye >/dev/null
printf "${BLUE}logged out, bye!${NOC}\n"
[[ "$object" == 'close' ]] || exit 0
}
close() {
sh vault close $store
exit 0
}
show_status() {
get_mountpoint_status
get_cache_status
printf "$mp_status $cache_status\n"
}
get_mountpoint_status() {
mountpoint -q $store_l && mp_status=$store || mp_status=0
}
get_cache_status() {
# 1 | 0 (no cache)
cache_status=$(gpg-connect-agent 'keyinfo --list' /bye 2>/dev/null | \
awk 'BEGIN{CACHED=0} /^S/ {if($7==1){CACHED=1}} END\
{if($0!=""){print CACHED} else {print "none"}}')
}
pass_mount() {
# pass crypto source location
# pass mount destination
## via $LS_COLORS symlink color:
## vlt_pass not mounted => $HOME/.password-store is red
## vlt_pass mounted => $HOME/.password-store becomes pale
pass_mount="$HOME/dock/vlt/pass"
[[ -d $pass_mount ]] || mkdir -p $pass_mount
get_sudo
# pass luksuuid
luksuuid=$(sudo cryptsetup luksUUID $crypto_source)
printf "$luksuuid" | wl-copy -n -o
## check for mountpoint
lsblk_rpaf=$(lsblk -rpaf --noheadings)
loopdevice=$(printf $lsblk_rpaf | grep $luksuuid | awk '{print $1}')
mapper_data=$(lsblk -rpaf --noheadings $loopdevice)
mountpoint=$(printf $mapper_data | awk '{print $8}')
if [[ -z "$mountpoint" ]]; then
vault open $crypto_source $pass_mount
elif [[ -n "$mountpoint" ]]; then
wl-copy --clear
fi
case $object in
open)
# only mount vault, no login
exit 0
;;
esac
}
gpg_decrypt() {
# always reset cached gpg password
gpg-connect-agent -q reloadagent /bye >/dev/null
## for aesthetic reasons: first sudo then O--,
get_sudo
erase_line
printf "${YELLOW}${BOLD} O--, ${NORMAL}${NOC}"
echo
generate_pw
echo
## pwgn has at this point generated its hex string
}
erase_line() {
printf "\r"
tput el
}
process_reply() {
action_reply='^a'
answer=$(printf "$1" | grep -iq "$action_reply")
case $reply in
$1)
continue
;;
*)
pwgn
;;
esac
}
generate_pw() {
printf "starting pwgn... ${YELLOW}A${NOC}bort "
reply_read_single_hidden_timer
erase_line
#[DEV] process_reply $reply
if printf "$reply" | grep -iq "^a"; then
continue
else
pwgn
fi
}
get_element() {
if [[ -z $element ]]; then
element=$(printf '%s\n' "${elements[@]}" | \
fzf --prompt='select element ' | \
awk '{print $2}')
fi
}
process_object() {
# empty object selection
[[ -z $object ]] && exit 0
# existing object
check_obj_exist
if [[ $obj_exist == 1 ]]; then
:
else
printf "${MAGENTA}no object${NOC}\n" && exit 70
fi
# otp object
if [[ -n $(printf "$object" | grep ^otp) ]]; then
get_otp
exit 0
fi
# TODO if only passr is entered when status is 0 0
get_cache_status
case $cache_status in
0)
log_in
;;
esac
}
get_element_value() {
element_value=$(pass show $object | grep ^$element: | awk '{print $2}')
check_element_value
## '%s' prevents 'invalid format character' error
printf '%s' "$element_value" | wl-copy -n -o
}
get_password() {
pass $object | head -n 1 | wl-copy -n -o
}
check_obj_exist() {
[[ -f "$store/$object.gpg" ]] && obj_exist=1
}
get_otp() {
# redirection to the separate otp file
if [[ "$(printf $object | head -c 4)" != "otp_" ]]; then
object="otp_$object"
fi
#TODO
#check_obj_exist && pass otp $object | wl-copy -n -o
if [[ -f $store/$object.gpg ]]; then
pass otp $object | wl-copy -n -o
fi
# reset object value for human info
object=$(printf $object | cut -c 5-)
}
pass_element() {
# run pass natively with $element
case $from_log_in in
1)
# silent (when logging in)
## this triggers the gpg passphrase question
## to unlock the openpgp secret key
pass $element
#pass $element $object /dev/null 2>&1
;;
*)
# normal (not silent)
human_info
pass $element $object
;;
esac
}
check_element_value() {
## no autoentry
if [[ -z $ae ]]; then
if [[ -z $element ]]; then
element="${MAGENTA}element${NOC} no selection"
elif [[ -z $element_value ]]; then
element="${MAGENTA}$element${NOC} no value"
fi
fi
## autoentry
if [[ -n $ae ]]; then
[[ -z $element_value ]] && notify-send "$element no value" && exit
fi
}
error_message() {
## selected empty line in element menu
if [[ -z $element ]]; then
error='no valid selection'
fi
}
human_info() {
# get error message if applicable
error_message
if [[ -z $ae ]]; then
if [[ "$object" == 'login' ]]; then
if [[ -n $error ]]; then
## print error message
printf "${MAGENTA}$object${NOC} $error\n"
else
printf "$object\n"
fi
else
if [[ -n $error ]]; then
## print error message
printf "${MAGENTA}$object $element${NOC} $error\n"
else
printf "$object $element\n"
fi
fi
elif [[ -n $ae ]]; then
notify-send "$element"
sleep $t
fi
}
autoentry() {
#TODO autoentry when not logged in
## now results in show being executed
ae=1
## sleep time
t=5
element="url"
get_element_value
qutebrowser --target tab $element_value &
t=$((t * 2))
human_info
#notify-send "$element"
#sleep $((t * 2))
element="userid"
get_element_value
t=$t
human_info
#notify-send "$element"
#sleep $t
element="password"
get_password 1> /dev/null #no stdout
human_info
#notify-send "$element"
#sleep $t
element="otp"
get_otp
if [[ -z $otp ]]; then
human_info
#notify-send "$element"
#sleep $t
fi
element="unlock"
get_element_value
if [[ -z $otp ]]; then
human_info
#notify-send "$element"
#sleep $t
fi
}
get_object_name() {
printf "$object" | wl-copy -n -o
}
process_element() {
case $element in
password)
get_password
human_info
;;
otp)
get_otp
human_info
;;
autoentry)
autoentry
;;
show)
element='show'
pass_element
;;
edit)
element='edit'
pass_element
;;
copy)
get_object_name
human_info
;;
log_out)
log_out
;;
"")
human_info
;;
*)
get_element_value
human_info
;;
esac
}
main() {
get_object
process_object
get_element
process_element
}
main