zpass: full reword

+ Split files
+ Add exec and mv operations
+ Add multi-arg support for operations
~ lxsh to build
This commit is contained in:
zawz 2020-10-23 09:56:27 +02:00
parent 72a1106b4c
commit d326d530f2
11 changed files with 501 additions and 439 deletions

14
zpass/Makefile Normal file
View file

@ -0,0 +1,14 @@
zpass:
lxsh -mo zpass src/main.sh
build: zpass
install: build
cp zpass /usr/local/bin
uninstall:
rm /usr/local/bin/zpass
clear:
rm zpass

101
zpass/src/archive.sh Normal file
View file

@ -0,0 +1,101 @@
#!/bin/sh
# $1 = tmpdir , $2 = keyfile
unpack() {
rm -rf "$1" || return $?
mkdir -p "$1" || return $?
(
set -e
cd "$1"
decrypt "$2" | tar -xf - 2>/dev/null
)
}
# $1 = tmpdir , $2 = keyfile
pack()
{
# clean empty dirs
archive="archive_$(randalnum 5)"
(
cd "$1" || exit $?
if [ -n "$2" ]
then
key=$(cat "$2") && rm "$2" || exit $?
else
key=$(new_key_with_confirm) || exit $?
fi
tar -cf - -- * | encrypt "$key" > "$1/$archive" || exit $?
) || return $?
if [ -n "$ZPASS_REMOTE_ADDR" ]
then
[ -z "$ZPASS_PATH" ] && datapath="~/.local/share/zpass"
if [ -n "$ZPASS_SSH_ID" ]
then scp -i "$ZPASS_SSH_ID" "$1/$archive" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" >/dev/null || return $?
else scp "$1/$archive" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" >/dev/null || return $?
fi
rm -f "$1/$archive" 2>/dev/null
return 0
else
mv -f "$1/$archive" "$file"
fi
}
# $@ = command to execute inside archive
# set env __NOPACK to not repack after command
archive_exec()
{
err=0
# tmp files
tmpdir="$TMPDIR/zpass_$(randalnum 5)"
keyfile="$tmpdir/$(randalnum 5).key"
# operation
(
# unpack
unpack "$tmpdir/archive" "$keyfile" || exit $?
# execute
(cd "$tmpdir/archive" && "$@") || exit $?
# repack
[ -z "$__NOPACK" ] && { pack "$tmpdir/archive" "$keyfile" || exit $?; }
exit 0
) || err=$?
# cleanup
rm -rf "$tmpdir"
return $err
}
# no argument
create() {
if [ -f "$file" ]
then
tmpdir="$TMPDIR/zpass_$(randalnum 5)"
# pack n repack with no tmp key: create new
unpack "$tmpdir" || return $?
pack "$tmpdir" || { echo "Encryption error" >&2 && return 1 ; }
rm -rf "$tmpdir"
else
# if remote: file tmp
[ -n "$ZPASS_REMOTE_ADDR" ] && {
file="$TMPDIR/zpass_$(filehash)$ZPASS_EXTENSION"
[ -z "$ZPASS_PATH" ] && datapath="~/.local/share/zpass"
}
# get key
[ -z "$ZPASS_KEY" ] && {
ZPASS_KEY=$(new_key_with_confirm) || { echo "Cancelled" >&2 && return 100 ; }
}
# create archive
tar -cf - -T /dev/null | encrypt "$ZPASS_KEY" > "$file" || {
echo "Encryption error" >&2
# echo "$tmperr" >&2
rm "$file"
return 1
}
[ -n "$ZPASS_REMOTE_ADDR" ] && {
if [ -n "$ZPASS_SSH_ID" ]
then scp -i "$ZPASS_SSH_ID" "$file" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" >/dev/null || return $?
else scp "$file" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" >/dev/null || return $?
fi
rm -rf "$file" 2>/dev/null
}
fi
return 0
}

38
zpass/src/cache.sh Normal file
View file

@ -0,0 +1,38 @@
#!/bin/sh
## Cache functions
clear_cache() {
rm "$cachepath"/*
}
write_cache() {
echo "$1" > "$cachepath/$(keyfile)"
delete_cache "$ZPASS_KEY_CACHE_TIME"
}
get_key_cached() {
[ ! -f "$file" ] && return 0
cat "$cachepath/$(keyfile)" 2>/dev/null
}
# $1 = delay in sec
delete_cache() {
if [ "$1" -gt 0 ] 2>/dev/null
then
for I in $(screen -ls | grep "zpass_$(keyfile)" | awk '{print $1}')
do
screen -S "$I" -X stuff "^C"
done
screen -dmS "zpass_$(keyfile)" sh -c "sleep $1 ; $0 rmc" # call zpass with cache delete
else
rm -f "$cachepath/$(keyfile)" 2>/dev/null
fi
}
clean_cache() {
# key cache
find "$cachepath" -type f ! -newermt @$(date -d "-$ZPASS_KEY_CACHE_TIME seconds" +%s) -print0 | xargs -0 rm
# tmp folders older than 5 min
rm -rd $(find "$TMPDIR" -maxdepth 1 -type d -name 'zpass_*' ! -mmin 5)
}

39
zpass/src/config.sh Normal file
View file

@ -0,0 +1,39 @@
#!/bin/sh
# XDG config
datapath="~/.local/share/zpass"
cachepath="~/.cache/zpass"
configpath="~/.config/zpass"
[ -n "$XDG_DATA_HOME" ] && datapath="$XDG_DATA_HOME/zpass"
[ -n "$XDG_CONFIG_HOME" ] && configpath="$XDG_CONFIG_HOME/zpass"
[ -n "$XDG_CACHE_HOME" ] && cachepath="$XDG_CACHE_HOME/zpass"
[ -z "$CONFIGFILE" ] && CONFIGFILE="$configpath/default.conf"
[ -z "$TMPDIR" ] && TMPDIR=/tmp
# stash env config
tmpenv="$TMPDIR/zpassenv_$(randalnum 5)"
env | grep '^ZPASS_.*=' | sed "s/'/'\\\''/g;s/=/='/;s/$/'/g" > "$tmpenv"
# load config file
[ -f "$CONFIGFILE" ] && { . "$CONFIGFILE" || exit $? ; }
. "$tmpenv" || exit $?
rm -f "$tmpenv" 2>/dev/null
# resolve zpass_path
[ -n "$ZPASS_PATH" ] && datapath="$ZPASS_PATH"
[ -n "$ZPASS_CACHE_PATH" ] && cachepath="$ZPASS_CACHE_PATH"
# default ZPASS config
[ -z "$ZPASS_FILE" ] && ZPASS_FILE=default
[ -z "$ZPASS_EXTENSION" ] && ZPASS_EXTENSION=.tar.gpg
[ -z "$ZPASS_KEY_CACHE_TIME" ] && ZPASS_KEY_CACHE_TIME=60 # in seconds
[ -z "$ZPASS_CLIPBOARD_TIME" ] && ZPASS_CLIPBOARD_TIME=30 # in seconds
[ -z "$ZPASS_UNK_OP_CALL" ] && ZPASS_UNK_OP_CALL=copy
[ -z "$ZPASS_RAND_LEN" ] && ZPASS_RAND_LEN=20
file="$datapath/$ZPASS_FILE$ZPASS_EXTENSION"
mkdir -p "$datapath" 2>/dev/null || error 1 "Could not create '$datapath'"
mkdir -p "$cachepath" 2>/dev/null && chmod -R go-rwx "$cachepath" 2>/dev/null

56
zpass/src/crypt.sh Normal file
View file

@ -0,0 +1,56 @@
#!/bin/sh
# $1 = key
encrypt() {
gpg --pinentry-mode loopback --batch --passphrase "$1" -o - -c -
}
# $1 = key , $2 = keyfile to write
decrypt_with_key()
{
gpg --pinentry-mode loopback --batch --passphrase "$1" -o - -d "$file" 2>/dev/null && ret=$? && [ -n "$2" ] && echo "$1" > "$2"
return $ret
}
# $1 = keyfile to write
decrypt()
{
# get remote file
[ -n "$ZPASS_REMOTE_ADDR" ] && {
file="$TMPDIR/zpass_$(filehash)$ZPASS_EXTENSION"
[ -z "$ZPASS_PATH" ] && datapath="~/.local/share/zpass"
if [ -n "$ZPASS_SSH_ID" ]
then scp -i "$ZPASS_SSH_ID" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" "$file" >/dev/null || return $?
else scp "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" "$file" >/dev/null || return $?
fi
}
cat "$file" >/dev/null 2>&1 || { echo "File doesn't exist. Use 'zpass create' to create the file" >&2 && return 1; } # no file
if [ -n "$ZPASS_KEY" ]
then # key given already
decrypt_with_key "$ZPASS_KEY" "$1" ; ret=$?
else # prompt for key
# attempt decrypt from cache
key=$(get_key_cached) && decrypt_with_key "$key" "$1"
ret=$?
if [ $ret -ne 0 ]
then
# cache was incorrect: delete
delete_cache >/dev/null 2>&1
# try loop
tries=0
while [ $ret -ne 0 ] && [ $tries -lt 3 ]
do
key=$(ask_key) || { echo "Cancelled" >&2 && return 100 ; }
tries=$((tries+1))
decrypt_with_key "$key" "$1" ; ret=$?
done
fi
fi
# remove temporary file
[ -n "$ZPASS_REMOTE_ADDR" ] && rm -rf "$file" 2>/dev/null
[ $ret -ne 0 ] && { echo "Could not decrypt '$file'" >&2 ; }
return $ret
}

45
zpass/src/help.sh Normal file
View file

@ -0,0 +1,45 @@
#!/bin/sh
fname=$(basename "$0")
usage()
{
echo "$fname [options] <operation>
[Global Operations]:
list-files List eligible files in data path. Shortcut 'lsf'
cache-clear Delete all cached keys. Shortcut 'cc'
help Display help
[File Operations]:
ls [path] List contents at given path
tree List all contents
create Create a file or change key
get <path...> Get values of targets
copy <path> Copy the target value to clipboard. Shortcut 'x'
set <path> <value> Set the value of target
new <path...> Generate a random password at target
rm <path...> Delete targets
mv <path...> Move targets
exec <cmd> Execute the following command inside the archive
rm-file Remove the current file. Shortcut 'rmf'
cached Returns wether or not a key is currently cached. Shortcut 'ch'
rm-cache Delete the cached key for this file. Shortcut 'rmc'
[Config]:
*Variable* *Default value* *Description*
----------------------------------------------------------------------------
CONFIGFILE '\$XDG_CONFIG_HOME/zpass/defaut.conf' Path to the config file to load
ZPASS_PATH '\$XDG_DATA_HOME/zpass' Folder containing password files
ZPASS_CACHE_PATH '\$XDG_CACHE_HOME/zpass' Path used for caching keys
ZPASS_FILE 'default' File to use for operations
ZPASS_KEY Key to use for encrypting/decrypting files
ZPASS_KEY_CACHE_TIME '60' Time a key stays in cache for decrypting, in seconds
ZPASS_CLIPBOARD_TIME '30' Time until clipboard gets cleared after copy, in seconds
ZPASS_UNK_OP_CALL 'copy' Operation to call on unrecognized first argument
ZPASS_RAND_LEN Length of random passwords generated by 'new'
ZPASS_REMOTE_ADDR SSH server the file is on
ZPASS_REMOTE_PORT '22' SSH server port
ZPASS_SSH_ID SSH private key to use
All operations can be shortened to their first char unless specified
Unknown first argument will perform the operation described in 'ZPASS_UNK_OP_CALL' on that argument
"
}

33
zpass/src/main.sh Normal file
View file

@ -0,0 +1,33 @@
#!/bin/lxsh
%include util.sh config.sh *.sh
## pre exec
clean_cache 2>/dev/null
[ $# -lt 1 ] && usage && return 1
arg=$1
shift 1
## exec
case $arg in
-h|h|help) usage && exit 1;;
lsf|list-files) find "$datapath" -type f -maxdepth 1 2>/dev/null | grep "$ZPASS_EXTENSION\$" | sed "s/$(escape_chars "$ZPASS_EXTENSION")\$//g" ;;
rmf|rm-file) rm "$file" ;;
cc|cache-clear) clear_cache 2>/dev/null ;;
ch|cached) get_key_cached >/dev/null 2>&1 ;;
rmc|rm-cache) delete_cache 0 >/dev/null 2>&1 ;;
c|create) create ;;
t|tree) _tree "$@" ;;
s|set) _set "$@" ;;
n|new) new "$@" ;;
g|get) get "$@" ;;
x|copy) copy "$1" ;;
e|exec) archive_exec "$@" ;;
l|ls|list) sanitize_paths "$@" && __NOPACK=y archive_exec ls -Apw1 -- "$@" ;;
r|rm) sanitize_paths "$@" && archive_exec rm -rf -- "$@" ;;
m|mv) sanitize_paths "$@" && archive_exec mv -f -- "$@" ;;
*) [ -n "$ZPASS_UNK_OP_CALL" ] && "$0" $ZPASS_UNK_OP_CALL "$arg" "$@" ;;
esac

61
zpass/src/operation.sh Normal file
View file

@ -0,0 +1,61 @@
#!/bin/sh
# $@ = paths
_tree()
{
if [ $# -gt 0 ]
then
fulltree=$(decrypt | tar -tf - 2>/dev/null) || exit $?;
for N
do
[ $# -gt 1 ] && echo "> $N:"
echo "$fulltree" | grep "^$(escape_chars "$N")" | sed "s|^$N||g ; "' s|^/||g ; /\/$/d ; /^$/d'
done
else
{ decrypt | tar -tf - 2>/dev/null || exit $?; } | sed '/\/$/d ; /^$/d'
fi
}
# $@ = paths
get()
{
__NOPACK=y archive_exec sh -c '
for N
do
(
cat "$N" 2>/dev/null && exit 0
[ -d "$1" ] && cat "$N/default" 2>/dev/null && exit 0
exit 1
) || { echo "$N: not found" >&2 && exit 1; }
done
' sh "$@"
}
# $1 = path
copy()
{
{ get "$1" || return $?; } | tr -d '\n' | xclip -selection clipboard && clipboard_clear "$ZPASS_CLIPBOARD_TIME"
}
# $@ = path
new()
{
archive_exec sh -c "
for N
do
mkdir -p \"\$(dirname \"\$N\")\" || exit $?
{ tr -cd 'a-zA-Z0-9!-.' < /dev/urandom | head -c '$ZPASS_RAND_LEN' && echo; } > \"\$N\" || exit \$?
done
" sh "$@"
}
# $1 = path , $@ = value
_set()
{
[ $# -lt 1 ] && return 1
ref=$1
shift 1
archive_exec sh -c "mkdir -p '$(dirname "$ref")' && printf '%s\n' '$*' > '$ref'"
}

58
zpass/src/prompt.sh Normal file
View file

@ -0,0 +1,58 @@
#!/bin/sh
# $1 = prompt
console_prompt_hidden()
{
local prompt
printf "%s" "$1" >&2
stty -echo
read -r prompt || return $?
stty echo
printf "\n" >&2
echo "$prompt"
}
# $1 = prompt message
prompt_password() {
if [ -n "$DISPLAY" ]
then
if which kdialog >/dev/null 2>&2
then kdialog --title "$fname" --password "$1" 2>/dev/null
else zenity --title "$fname" --password 2>/dev/null
fi
else
console_prompt_hidden "$1: "
fi
}
# $1 = message
error_dialog() {
if which kdialog >/dev/null 2>&2
then kdialog --title "$fname" --error "$1" 2>/dev/null
else zenity --title "$fname" --error --text="$1" 2>/dev/null
fi
}
new_key_with_confirm()
{
[ -n "$ZPASS_KEY" ] && echo "$ZPASS_KEY" && return 0
pass1=1
pass2=2
while [ "$pass1" != "$pass2" ]
do
pass1=$(prompt_password "Enter new key") || error 100 "Cancelled"
pass2=$(prompt_password "Confirm key") || error 100 "Cancelled"
[ "$pass1" != "$pass2" ] && error_dialog "Passwords do not match.\nTry again"
done
write_cache "$pass1" &
echo "$pass1"
}
# $1 = prompt message
ask_key() {
message="Enter key"
[ -n "$1" ] && message="$1"
key=$(prompt_password "$message") || return $?
write_cache "$key" &
echo "$key"
}

56
zpass/src/util.sh Normal file
View file

@ -0,0 +1,56 @@
#!/bin/sh
error(){
ret=$1 && shift 1 && echo "$*" >&2 && exit $ret
}
randalnum() {
tr -cd '[a-zA-Z]' < /dev/urandom | head -c $1
}
# $* = input
escape_chars() {
echo "$*" | sed 's|\.|\\\.|g;s|/|\\/|g'
}
# $@ = paths
sanitize_paths()
{
for N
do
echo "$N" | grep "^/" && echo "Path cannot start with /" >&2 && return 1
echo "$N" | grep -w ".." && echo "Path cannot contain .." >&2 && return 1
done
return 0
}
# $1 = file
getpath() {
if [ -n "$REMOTE_ADDR" ]
then echo "$REMOTE_PORT:$REMOTE_ADDR:$file"
else readlink -f "$file"
fi
}
# $1 = file
filehash(){
getpath "$file" | md5sum | cut -d' ' -f1
}
keyfile(){
printf "%s.key" "$(filehash)"
}
# $1 = delay in sec
clipboard_clear() {
if [ -n "$1" ]
then
for I in $(screen -ls | grep "$fname"_clipboard | awk '{print $1}')
do
screen -S "$I" -X stuff "^C"
done
screen -dmS "$fname"_clipboard sh -c "sleep $1 ; xclip -selection clipboard < /dev/null ; sleep 1" # put empty string into clipboard
else
xclip -selection clipboard < /dev/null
fi
}

View file

@ -1,439 +0,0 @@
#!/bin/sh
fname=$(basename "$0")
usage()
{
echo "$fname <operation>
[Global Operations]:
list-files List eligible files in data path. Shortcut 'lsf'
cache-clear Delete all cached keys. Shortcut 'cc'
help Display help
[File Operations]:
ls [path] List contents at given path
tree List all contents
create Create a file or change key
get <path> Get the value of target
copy <path> Copy the target value to clipboard. Shortcut 'x'
set <path> <value> Set the value of target
new <path> [length] Generate a random password at target
rm <path> Delete the target
rm-file Remove the current file. Shortcut 'rmf'
cached Returns wether or not a key is currently cached. Shortcut 'ch'
rm-cache Delete the cached key for this file. Shortcut 'rmc'
[Config]:
*Variable* *Default value* *Description*
----------------------------------------------------------------------------
CONFIGFILE '\$XDG_CONFIG_HOME/zpass/defaut.conf' Path to the config file to load
ZPASS_PATH '\$XDG_DATA_HOME/zpass' Folder containing password files
ZPASS_CACHE_PATH '\$XDG_CACHE_HOME/zpass' Path used for caching keys
ZPASS_FILE 'default' File to use for operations
ZPASS_KEY Key to use for encrypting/decrypting files
ZPASS_KEY_CACHE_TIME '60' Time a key stays in cache for decrypting, in seconds
ZPASS_CLIPBOARD_TIME '30' Time until clipboard gets cleared after copy, in seconds
ZPASS_UNK_OP_CALL 'copy' Operation to call on unrecognized first argument
ZPASS_REMOTE_ADDR SSH server the file is on
ZPASS_REMOTE_PORT '22' SSH server port
ZPASS_SSH_ID SSH private key to use
All operations can be shortened to their first char unless specified
Unknown first argument will perform the operation described in 'ZPASS_UNK_OP_CALL' on that argument
"
}
# XDG config
datapath="~/.local/share/zpass"
cachepath="~/.cache/zpass"
configpath="~/.config/zpass"
[ -n "$XDG_DATA_HOME" ] && datapath="$XDG_DATA_HOME/zpass"
[ -n "$XDG_CONFIG_HOME" ] && configpath="$XDG_CONFIG_HOME/zpass"
[ -n "$XDG_CACHE_HOME" ] && cachepath="$XDG_CACHE_HOME/zpass"
[ -z "$CONFIGFILE" ] && CONFIGFILE="$configpath/default.conf"
randalnum() {
tr -cd '[a-zA-Z]' < /dev/urandom | head -c $1
}
[ -z "$TMPDIR" ] && TMPDIR=/tmp
# stash env config
tmpenv="$TMPDIR/zpassenv_$(randalnum 5)"
env | grep '^ZPASS_.*=' | sed "s/'/'\\\''/g;s/=/='/;s/$/'/g" > "$tmpenv"
# load config file
[ -f "$CONFIGFILE" ] && { . "$CONFIGFILE" || exit $? ; }
. "$tmpenv" || exit $?
rm -f "$tmpenv" 2>/dev/null
# resolve zpass_path
[ -n "$ZPASS_PATH" ] && datapath="$ZPASS_PATH"
[ -n "$ZPASS_CACHE_PATH" ] && cachepath="$ZPASS_CACHE_PATH"
# default ZPASS config
[ -z "$ZPASS_FILE" ] && ZPASS_FILE=default
[ -z "$ZPASS_EXTENSION" ] && ZPASS_EXTENSION=.tar.gpg
[ -z "$ZPASS_KEY_CACHE_TIME" ] && ZPASS_KEY_CACHE_TIME=60 # in seconds
[ -z "$ZPASS_CLIPBOARD_TIME" ] && ZPASS_CLIPBOARD_TIME=30 # in seconds
[ -z "$ZPASS_UNK_OP_CALL" ] && ZPASS_UNK_OP_CALL=copy
file="$datapath/$ZPASS_FILE$ZPASS_EXTENSION"
error(){
ret=$1 && shift 1 && echo $* >&2 && exit $ret
}
mkdir -p "$datapath" 2>/dev/null || error 1 "Could not create '$datapath'"
mkdir -p "$cachepath" 2>/dev/null && chmod -R go-rwx "$cachepath" 2>/dev/null
escape_chars() {
echo "$*" | sed 's|\.|\\\.|g;s|/|\\/|g'
}
randpass() {
len=$1
[ -z "$len" ] && len=20
tr -cd 'a-zA-Z0-9!-.' < /dev/urandom | head -c $len
}
# $1 = file
getpath() {
if [ -n "$REMOTE_ADDR" ]
then echo "$REMOTE_PORT:$REMOTE_ADDR:$file"
else readlink -f "$file"
fi
}
# $1 = file
filehash(){
getpath "$file" | md5sum | cut -d' ' -f1
}
keyfile(){
printf "%s.key" "$(filehash)"
}
# $1 = delay in sec
clipboard_clear() {
if [ -n "$1" ]
then
for I in $(screen -ls | grep "$fname"_clipboard | awk '{print $1}')
do
screen -S "$I" -X stuff "^C"
done
screen -dmS "$fname"_clipboard sh -c "sleep $1 ; xclip -selection clipboard < /dev/null ; sleep 1" # put empty string into clipboard
else
xclip -selection clipboard < /dev/null
fi
}
# $1 = delay in sec
delete_cache() {
if [ "$1" -gt 0 ] 2>/dev/null
then
for I in $(screen -ls | grep "$fname"_"$(keyfile)" | awk '{print $1}')
do
screen -S "$I" -X stuff "^C"
done
screen -dmS "$fname"_"$(keyfile)" sh -c "sleep $1 ; $0 rmc" # call zpass with cache delete
else
rm -f "$cachepath/$(keyfile)" 2>/dev/null
fi
}
clean_cache() {
# key cache
find "$cachepath" -type f ! -newermt @$(date -d "-$ZPASS_KEY_CACHE_TIME seconds" +%s) -print0 | xargs -0 rm
# tmp folders older than 1 min
rm -rd $(find "$TMPDIR" -maxdepth 1 -type d -name "$fname"'_*' ! -mmin 1)
}
clear_cache() {
rm "$cachepath"/*
}
write_cache() {
echo "$1" > "$cachepath/$(keyfile)"
delete_cache $ZPASS_KEY_CACHE_TIME
}
get_key_cached() {
[ ! -f "$file" ] && return 0
cat "$cachepath/$(keyfile)" 2>/dev/null
}
# $1 = prompt message
prompt_password() {
if [ -n "$DISPLAY" ]
then
if which kdialog >/dev/null 2>&2
then kdialog --title "$fname" --password "$1" 2>/dev/null
else zenity --title "$fname" --password 2>/dev/null
fi
else
printf "%s:" "$1" >&2
stty -echo
read -r PASSWORD || return $?
stty echo
printf "\n" >&2
echo $PASSWORD
fi
}
# $1 = message
error_dialog() {
if which kdialog >/dev/null 2>&2
then kdialog --title "$fname" --error "$1" 2>/dev/null
else zenity --title "$fname" --error --text="$1" 2>/dev/null
fi
}
new_key_with_confirm()
{
[ -n "$ZPASS_KEY" ] && echo "$ZPASS_KEY" && return 0
pass1=1
pass2=2
while [ "$pass1" != "$pass2" ]
do
pass1=$(prompt_password "Enter new key") || error 100 "Cancelled"
pass2=$(prompt_password "Confirm key") || error 100 "Cancelled"
[ "$pass1" != "$pass2" ] && error_dialog "Passwords do not match.\nTry again"
done
write_cache "$pass1" &
echo "$pass1"
}
# $1 = prompt message
get_key() {
message="Enter key"
[ -n "$1" ] && message="$1"
key=$(prompt_password "$message") || return $?
write_cache "$key" &
echo "$key"
}
# $1 = key , $2 = keyfile to write
decrypt_with_key()
{
gpg --pinentry-mode loopback --batch --passphrase "$1" -o - -d "$file" 2>/dev/null && ret=$? && [ -n "$2" ] && echo "$1" > "$2"
return $ret
}
unset REQ_CLEAN
# $1 = keyfile to write
decrypt()
{
[ -n "$ZPASS_REMOTE_ADDR" ] && {
file="$TMPDIR/zpass_$(filehash)$ZPASS_EXTENSION"
[ -z "$ZPASS_PATH" ] && datapath="~/.local/share/zpass"
if [ -n "$ZPASS_SSH_ID" ]
then scp -i "$ZPASS_SSH_ID" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" "$file" >/dev/null || return $?
else scp "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" "$file" >/dev/null || return $?
fi
## TODO ##
}
[ ! -f "$file" ] && return 1 # no file
if [ -n "$ZPASS_KEY" ]
then # key given already
decrypt_with_key "$ZPASS_KEY" "$1" ; ret=$?
else # prompt for key
# attempt decrypt from cache
key=$(get_key_cached) && decrypt_with_key "$key" "$1"
ret=$?
if [ $ret -ne 0 ]
then
# cache was incorrect: delete
delete_cache >/dev/null 2>&1
# try loop
tries=0
while [ $ret -ne 0 ] && [ $tries -lt 3 ]
do
key=$(get_key) || { echo "Cancelled" >&2 && return 100 ; }
tries=$((tries+1))
decrypt_with_key "$key" "$1" ; ret=$?
done
fi
fi
[ -n "$ZPASS_REMOTE_ADDR" ] && rm -rf "$file" 2>/dev/null
[ $ret -ne 0 ] && { echo "Could not decrypt '$file'" >&2 ; }
return $ret
}
# $1 = key
encrypt() {
gpg --pinentry-mode loopback --batch --passphrase "$1" -o - -c -
}
# $1 = tmpdir , $2 = keyfile
unpack(){
rm -rf "$1" || return $?
mkdir -p "$1" || return $?
(
cd "$1"
{ decrypt "$2" || return $?; } | tar -xf - 2>/dev/null || return $?
)
}
# $1 = tmpdir , $2 = keyfile
pack()
{
# clean empty dirs
archive="archive_$(randalnum 5)"
find "$1" -empty -type d -delete
(
cd "$1"
if [ -n "$2" ]
then
key=$(cat "$2") && rm "$2" || return $?
else
key=$(new_key_with_confirm) || return $?
fi
tar -cf - * | encrypt "$key" > "$1/$archive" || return $?
) || return $?
if [ -n "$ZPASS_REMOTE_ADDR" ]
then
[ -z "$ZPASS_PATH" ] && datapath="~/.local/share/zpass"
if [ -n "$ZPASS_SSH_ID" ]
then scp -i "$ZPASS_SSH_ID" "$1/$archive" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" >/dev/null || return $?
else scp "$1/$archive" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" >/dev/null || return $?
fi
rm -f "$1/$archive" 2>/dev/null
return 0
else
mv -f "$1/$archive" "$file"
fi
}
# $1 = file , $2 = content
setval()
{
err=0
# tmp files
tmpdir="$TMPDIR/$fname"_"$(randalnum 5)"
keyfile="$tmpdir/$(randalnum 5).key"
# operation
(
unpack "$tmpdir" "$keyfile" || return $?
mkdir -p "$tmpdir/$(dirname "$1")" || return $? # create dir
echo "$2" > "$tmpdir/$1" || return $? # set target data
pack "$tmpdir" "$keyfile" || return $?
) || err=$?
rm -rf "$tmpdir"
return $err
}
remove()
{
# tmp files
tmpdir="$TMPDIR/$fname"_"$(randalnum 5)"
keyfile="$tmpdir/$(randalnum 5).key"
# operation
unpack "$tmpdir" "$keyfile" || return $?
rm -rf "$tmpdir/$1" # delete target
pack "$tmpdir" "$keyfile" || return $?
rm -rf "$tmpdir"
}
create() {
if [ -f "$file" ]
then
tmpdir="$TMPDIR/$fname"_"$(randalnum 5)"
# pack n repack with no tmp key: create new
unpack "$tmpdir" || return $?
pack "$tmpdir" || { echo "Encryption error" >&2 && return 1 ; }
rm -rf "$tmpdir"
else
# if remote: file tmp
[ -n "$ZPASS_REMOTE_ADDR" ] && {
file="$TMPDIR/zpass_$(filehash)$ZPASS_EXTENSION"
[ -z "$ZPASS_PATH" ] && datapath="~/.local/share/zpass"
}
# get key
[ -z "$ZPASS_KEY" ] && ZPASS_KEY=$(new_key_with_confirm) || { echo "Cancelled" >&2 && return 100 ; }
# create archive
tar -cf - -T /dev/null | encrypt "$ZPASS_KEY" > "$file" || {
echo "Encryption error" >&2
# echo "$tmperr" >&2
rm "$file"
return 1
}
[ -n "$ZPASS_REMOTE_ADDR" ] && {
if [ -n "$ZPASS_SSH_ID" ]
then scp -i "$ZPASS_SSH_ID" "$file" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" >/dev/null || return $?
else scp "$file" "$ZPASS_REMOTE_ADDR:$datapath/$ZPASS_FILE$ZPASS_EXTENSION" >/dev/null || return $?
fi
rm -rf "$file" 2>/dev/null
}
fi
return 0
}
list()
{
tree=$(decrypt | tar -tf - 2>/dev/null) || return $?
ret="$tree"
if [ -n "$1" ]
then
# file
ret=$(echo "$tree" | sed -n "/\/$/d;/^$(escape_chars "$1")\$/p")
# folder
[ -z "$ret" ] && in=$(echo "$1/" | tr -s '/') && ret=$(echo "$tree" | grep "^$in" | sed "s|^$in||g")
fi
echo "$ret" | sed "/\/..*/d;/^\$/d"
}
tree()
{
tree=$(decrypt | tar -tf - 2>/dev/null) || return $?
echo "$tree" | sed '/\/$/d;/^$/d'
}
get(){
err=0
tmpdir="$TMPDIR/$fname"_"$(randalnum 5)" && mkdir -p "$tmpdir"
decrypt | tar -C "$tmpdir" -xf - || err=$?
[ "$err" -eq 0 ] && { (
cd "$tmpdir"
[ -f "$1" ] && cat "$1" && return 0
[ -d "$1" ] && [ -f "$1/default" ] && cat "$1/default" && return 0
return 2
) || err=$? ; }
rm -rf "$tmpdir"
return $err
}
copy()
{
{ get "$1" || return $?; } | tr -d '\n' | xclip -selection clipboard && clipboard_clear "$ZPASS_CLIPBOARD_TIME"
}
clean_cache 2>/dev/null
[ -z "$1" ] && usage && return 1
case $1 in
lsf|list-files) ls "$datapath" 2>/dev/null | grep "$ZPASS_EXTENSION\$" | sed "s/$(escape_chars "$ZPASS_EXTENSION")\$//g" ;;
rmf|rm-file) rm "$file" ;;
cc|cache-clear) clear_cache 2>/dev/null ;;
l|ls|list) list $2 ;;
t|tree) tree ;;
c|create) create ;;
g|get) get "$2" ;;
s|set) setval "$2" "$3" ;;
n|new) setval "$2" "$(randpass $3)" ;;
r|rm) remove "$2" "$3" ;;
x|copy|clipboard) copy "$2" ;;
ch|cached) get_key_cached >/dev/null 2>&1 ;;
rmc|rm-cache) delete_cache 0 >/dev/null 2>&1 ;;
-h|h|help) usage ;;
*) [ -n "$ZPASS_UNK_OP_CALL" ] && "$0" $ZPASS_UNK_OP_CALL "$@" ;;
esac
# ret=$?
# [ -n "$REQ_CLEAN" ] && rm -rf "$REQ_CLEAN" 2>/dev/null
# return $ret