Files
browserpass-native/placeholder.sh
Erayd 60a4a1ddaa Update placeholder (#10)
* Put error message inside params
* Rename .response to .data
2018-04-14 21:39:02 +12:00

185 lines
5.1 KiB
Bash
Executable File

#!/bin/bash
set -e
LENGTH=$(head -c 4 | perl -ne 'print unpack("L", $_)')
declare -gr REQUEST=$(head -c $LENGTH)
VERSION=3000000
# Write a number as little-endian binary
function writelen()
{
printf
}
# Require a command to be present, and quit if it's not
function require()
{
if ! `command -v "$1" >/dev/null`; then
OUTPUT="{\n \"status\": \"error\"\n "version": $VERSION,\n \"params\": {\n \"message\": \"Required dependency '$1' is missing\"\n }, \"code\": 1\n}"
LANG=C LC_ALL=C LENGTH=${#OUTPUT}
echo -n
exit 1
fi
}
# Echo to stderr with failure status code
function fail()
{
echo "$@" >&2
return 1
}
# trim leading and trailing whitespace
function trim()
{
echo "$1" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//'
}
# Build an error response message
function error()
{
if [ -n "$1" ]; then
ERROR="$1"
else
ERROR=$(cat)
fi
[ -n "$2" ] && CODE="$2" || CODE=1
OUTPUT="$(jq -n '.status = "error"' | jq --arg message "$ERROR" --arg code "$CODE" '.params.message = $message | .code = ($code|tonumber)')"
perl -e "print pack('L', ${#OUTPUT})"
echo -n "$OUTPUT"
}
# Wrap a shell command and bail with a valid response message on error
function wrap()
{
. <({ STDERR=$({ STDOUT=$("$@"); EXIT=$?; } 2>&1; declare -p EXIT >&2; declare -p STDOUT >&2); declare -p STDERR; } 2>&1; )
if [ $EXIT -gt 0 ] || [ -n "$STDERR" ]; then
if [ -n "$STDERR" ]; then
echo -n "$STDERR" | error
elif [ -n "$STDOUT" ]; then
echo -n "$STDOUT" | error
else
error "$@" $EXIT
fi
else
OUTPUT="$(jq -n --arg version $VERSION --arg response "$STDOUT" '.status = "ok" | .version = $version | .data = ($response | if .[:1] == "{" then ( . | fromjson) else . end)')"
perl -e "print pack('L', ${#OUTPUT})"
echo -n "$OUTPUT"
fi
return $EXIT
}
# jq wrapper around the request
function rq()
{
jq -r "$@" <<< "$REQUEST"
}
# Supply per-store configuration
function configure()
{
set -e
declare -A STORES
. <(rq '.settings.stores | to_entries | map("STORES[\(.key|@sh)]=\(.value.path|@sh)")[]')
for STORE in "${!STORES[@]}"; do
STOREPATH=$(echo "${STORES["$STORE"]}" | sed 's/^~/$HOME/' | envsubst)
[ -d "$STOREPATH" ] || fail "Store directory for '$STORE' does not exist: $STOREPATH"
if [ -f "$STOREPATH/.browserpass.json" ]; then
STORES["$STORE"]=$(cat "$STOREPATH/.browserpass.json")
else
STORES["$STORE"]=
fi
done
[ -n "$PASSWORD_STORE_DIR" ] || PASSWORD_STORE_DIR="~/.password-store"
OUTPUT="$(jq -n --arg defaultPath "$PASSWORD_STORE_DIR" '.defaultPath = $defaultPath')"
STOREPATH=$(echo "$PASSWORD_STORE_DIR" | sed 's/^~/$HOME/' | envsubst)
if [ -f "$STOREPATH/.browserpass.json" ]; then
OUTPUT=$(jq --arg settings "$(cat "$STOREPATH/.browserpass.json")" '.defaultSettings = $settings' <<< "$OUTPUT")
else
OUTPUT=$(jq '.defaultSettings = ""' <<< "$OUTPUT")
fi
for STORE in "${!STORES[@]}"; do
OUTPUT=$(jq --arg store "$STORE" --arg settings "${STORES[$STORE]}" '.storeSettings[$store] = $settings' <<< "$OUTPUT")
done
echo "$OUTPUT"
}
# List all available logins by store
function list()
{
set -e
declare -A STORES
. <(rq '.settings.stores | to_entries | map("STORES[\(.key|@sh)]=\(.value.path|@sh)")[]')
for STORE in "${!STORES[@]}"; do
STOREPATH=$(echo "${STORES["$STORE"]}" | sed 's/^~/$HOME/' | envsubst)
[ -d "$STOREPATH" ] || fail "Store directory for '$STORE' does not exist: $STOREPATH"
STORES[$STORE]="$(find -L "$STOREPATH" -type f -name '*.gpg' -printf '%P\n' | jq -R -s 'split("\n") | map(select(length > 0)) ')"
done
OUTPUT='{}'
for STORE in "${!STORES[@]}"; do
OUTPUT=$(jq --arg store "$STORE" --arg files "${STORES[$STORE]}" '.files[$store] = ($files|fromjson)' <<< "$OUTPUT")
done
echo "$OUTPUT"
}
# Fetch the specified fields from a single login
function fetch()
{
set -e
STORE="$(rq .store)"
FILE="$(rq .file)"
# sanity-check variables
[ -n "$STORE" ] || fail ".store is not set"
[ -n "$FILE" ] || fail ".file is not set"
# get file path
STOREPATH="$(rq --arg store "$STORE" '.settings.stores[$store].path//empty' | sed 's/^~/$HOME/' | envsubst)"
[ -n "$STOREPATH" ] || fail "Store path is empty"
[ -d "$STOREPATH" ] || fail "Store directory for '$STORE' does not exist: $STOREPATH"
FILEPATH="$STOREPATH/$FILE"
# get file contents
[ -f "$FILEPATH" ] || fail "Requested file does not exist: $STORE:$FILE"
DATA="$(gpg -q --decrypt "$FILEPATH")"
# build output
echo "$(jq -n --arg data "$DATA" '.data = $data')"
}
function run()
{
case "$(rq .action)" in
configure) configure; return $?;;
list) list; return $?;;
fetch) fetch; return $?;;
*) echo "Unknown action: $(rq .action)" >&2; return 1;;
esac
}
# Ensure dependencies are present
require cat
require envsubst
require find
require gpg
require grep
require head
require jq
require perl
require sed
require tac
# Run the client
wrap run