#!/bin/sh # posix password manager - developed by acidvegas (https://git.acid.vegas/pass) umask 077 export GPG_TTY=$(tty) GPG_ID="acidvegas" # change me GPG_OPTS="-q --yes --compress-algo=none --no-encrypt-to --batch" METHOD="type" PASS_DIR=$HOME/.secrets if [ -z $EDITOR ]; then export EDITOR=nano fi mkdir -p $PASS_DIR edit() { template="pw.XXXXXXXXXXXXX" if [ -d /dev/shm ] && [ -w /dev/shm ] && [ -x /dev/shm ]; then tmp=$(mktemp /dev/shm/$template) elif [ ! -z $PREFIX ] && [ -d $PREFIX/tmp ]; then tmp=$(mktemp $PREFIX/usr/tmp/$template) # For users on Termux else echo "warning: /dev/shm does not exist or is missing permissions required for temporary files (using insecure fallback to /tmp directory)" tmp=$(mktemp /tmp/$template) fi rm_tmp() { rm -rf "$tmp" } trap rm_tmp EXIT if [ -f $PASS_DIR/$1.gpg ]; then gpg -d -o $tmp $GPG_OPTS $PASS_DIR/$1.gpg || exit 1 $EDITOR $tmp if [ ! "$(gpg -d $GPG_OPTS $PASS_DIR/$1.gpg)" = "$(cat $tmp)" ]; then gpg -e -r $GPG_ID -o $PASS_DIR/$1.gpg $GPG_OPTS $tmp fi else $EDITOR $tmp if [ -f $tmp ]; then mkdir -p $(dirname $PASS_DIR/$1) gpg -e -r $GPG_ID -o $PASS_DIR/$1.gpg $GPG_OPTS $tmp fi fi } generate() { case ${1#[-+]} in *[!0-9]* | '') echo "error: invalid number" ;; *) cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $1 | head -n 1 ;; esac } menu() { cwd=$PASS_DIR while : do if [ $cwd = $PASS_DIR ]; then cmd=$(ls -p $cwd | sed 's/\.gpg$//' | dmenu -l 5 "$@") else cmd=$({ echo ".."; ls -p $cwd | sed 's/\.gpg$//'; } | dmenu -l 5 "$@") fi if [ -z $cmd ]; then break elif [ $cmd = '..' ]; then cwd=$(dirname $cwd) elif [ -d $cwd/$cmd ]; then cwd=$cwd/$cmd elif [ -f $cwd/$cmd ]; then if [ $METHOD = "copy" ]; then export PINENTRY_USER_DATA="dmenu" | gpg -d $GPG_OPTS $cwd/$cmd | dmenu "$@" | xclip -selection clipboard sleep 3 && xclip -selection clipboard /dev/nul elif [ $METHOD = "type" ]; then export PINENTRY_USER_DATA="dmenu" | gpg -d $GPG_OPTS $cwd/$cmd | dmenu "$@" | xdotool type --delay 3 "$D" fi break fi done } otp() { if [ -f $PASS_DIR/$1.gpg ]; then otp_uri=$(gpg -d $GPG_OPTS $PASS_DIR/$1.gpg | tail -n 1) || exit 1 if [ "$(echo $otp_uri | head -c 10)" = "otpauth://" ]; then secret=$(echo "$otp_uri" | sed 's/.*secret=//' | cut -f1 -d'&') oathtool -b --totp $secret else echo "error: OTP URI invalid or not found for '$1'" fi else echo "error: '$1' does not exist" fi } show() { if [ -d $PASS_DIR/$1 ]; then echo $1 tree -NCl --noreport $PASS_DIR/$1 3>&- | tail -n +2 | sed -E 's/\.gpg(\x1B\[[0-9]+m)?( ->|$)/\1\2/g' elif [ -f $PASS_DIR/$1.gpg ]; then gpg -d $GPG_OPTS $PASS_DIR/$1.gpg else echo "error: '$1' does not exist" fi } # Main command -v gpg >/dev/null || { echo "error: missing required package 'gpg'"; exit 1; } command -v tree >/dev/null || { echo "error: missing required package 'tree'"; exit 1; } if [ "$#" = '1' ]; then if [ $1 = 'menu' ]; then command -v dmenu >/dev/null || { echo "error: missing required package 'dmenu'"; exit 1; } command -v pinentry >/dev/null || { echo "error: missing required package 'pinentry'"; exit 1; } command -v pinentry-dmenu >/dev/null || { echo "error: missing required package 'pinentry-dmenu'"; exit 1; } if [ $METHOD = "copy" ]; then command -v dmenu >/dev/null || { echo "error: missing required package 'xclip'"; exit 1; } elif [ $METHOD = 'type' ]; then command -v xdotool >/dev/null || { echo "error: missing required package 'xdotool'"; exit 1; } else echo "error: invalid menu method (must be 'copy' or 'type')" exit 1 fi menu else show $1 fi elif [ "$#" = '2' ]; then if [ "$1" = 'edit' ]; then edit $2 elif [ "$1" = 'gen' ]; then generate $2 elif [ "$1" = 'otp' ]; then command -v oathtool >/dev/null || { echo "error: missing required package 'oathtool'"; exit 1; } otp $2 fi else tree -NCl --noreport $PASS_DIR 3>&- | tail -n +2 | sed -E 's/\.gpg(\x1B\[[0-9]+m)?( ->|$)/\1\2/g' fi