diff --git a/zpass/Makefile b/zpass/Makefile new file mode 100644 index 0000000..ecdbf7e --- /dev/null +++ b/zpass/Makefile @@ -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 diff --git a/zpass/src/archive.sh b/zpass/src/archive.sh new file mode 100644 index 0000000..a4e3262 --- /dev/null +++ b/zpass/src/archive.sh @@ -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 +} diff --git a/zpass/src/cache.sh b/zpass/src/cache.sh new file mode 100644 index 0000000..bf67c77 --- /dev/null +++ b/zpass/src/cache.sh @@ -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) +} diff --git a/zpass/src/config.sh b/zpass/src/config.sh new file mode 100644 index 0000000..f543050 --- /dev/null +++ b/zpass/src/config.sh @@ -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 diff --git a/zpass/src/crypt.sh b/zpass/src/crypt.sh new file mode 100644 index 0000000..92f7f20 --- /dev/null +++ b/zpass/src/crypt.sh @@ -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 +} diff --git a/zpass/src/help.sh b/zpass/src/help.sh new file mode 100644 index 0000000..0a62e9f --- /dev/null +++ b/zpass/src/help.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +fname=$(basename "$0") +usage() +{ + echo "$fname [options] +[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 Get values of targets + copy Copy the target value to clipboard. Shortcut 'x' + set Set the value of target + new Generate a random password at target + rm Delete targets + mv Move targets + exec 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 +" +} diff --git a/zpass/src/main.sh b/zpass/src/main.sh new file mode 100644 index 0000000..895c0c7 --- /dev/null +++ b/zpass/src/main.sh @@ -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 diff --git a/zpass/src/operation.sh b/zpass/src/operation.sh new file mode 100644 index 0000000..02ed0d8 --- /dev/null +++ b/zpass/src/operation.sh @@ -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'" +} diff --git a/zpass/src/prompt.sh b/zpass/src/prompt.sh new file mode 100644 index 0000000..6bb7d6e --- /dev/null +++ b/zpass/src/prompt.sh @@ -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" +} diff --git a/zpass/src/util.sh b/zpass/src/util.sh new file mode 100644 index 0000000..2eedebc --- /dev/null +++ b/zpass/src/util.sh @@ -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 +} diff --git a/zpass/zpass b/zpass/zpass deleted file mode 100644 index b250e1c..0000000 --- a/zpass/zpass +++ /dev/null @@ -1,439 +0,0 @@ -#!/bin/sh - -fname=$(basename "$0") -usage() -{ - echo "$fname -[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 Get the value of target - copy Copy the target value to clipboard. Shortcut 'x' - set Set the value of target - new [length] Generate a random password at target - rm 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