#!/bin/sh set -e PROGNAME=$(basename $0) KEY_ID=$LOGNAME CONFIG_FILE=$HOME/.dbs.conf # $HeadURL$ $LastChangedRevision$ main() { # Defaults for options MODE=normal VERBOSELEVEL=2 # Process options while [ "X$1" != X ]; do case $1 in --init|-i) MODE=init ;; --verbose|-v) VERBOSELEVEL=3 ;; -d) VERBOSELEVEL=$2; shift ;; --debug=*) VERBOSELEVEL=${1#*=} ;; --help|-h) usage 0 ;; --) shift; break ;; -*) usage ;; *) break ;; esac shift done # Set up traps trap "warning \"cleaning up; please wait ...\"; rm -f /tmp/$PROGNAME.$$.*; exit 1" 1 2 15 # Create config file if non-existent. if [ ! -f $CONFIG_FILE ]; then { echo "# This file details the various wallets this program can query or edit." echo "#" echo "# Each line consists of three fields separated by colons, like this:" echo "#" echo "# ::" echo "#" echo "# where:" echo "#" echo "# is the name of the program that will search this" echo "# file (e.g. if you name the program 'pin', which is the default," echo "# then it will search this file for the entry ID 'pin', but if" echo "# you copy or rename that same program 'phone' then it will search" echo "# this file for the entry ID 'phone')" echo "#" echo "# is either 'true' or 'false'; it indicates" echo "# whether the wallet in which entries are stored will be" echo "# encrypted or not." echo "#" echo "# indicates the path of the wallet which will store" echo "# the entries (e.g. '~/.phone.db' or '~/.pin.db')." echo "#" echo "# So complete entries might look like this:" echo "#" echo "# phone:false:~/.phone.db" echo "# pin:true:~/.pin.db" echo "#" echo "# Finally, note that blank lines and lines which begin with '#' (like" echo "# these) are ignored and that entries may be intended or not, it makes" echo "# no difference." } > $CONFIG_FILE warning "wallet list created (you will need to define some wallets in $CONFIG_FILE before adding/search entries)" return 0 fi # Find name of wallet and if it is encrypted or not. KEYID=$PROGNAME ENCRYPTED_FLAG=$(sed -n "s/^[ \\t]*$KEYID[ \\t]*:[ \\t]*\\([^:]*\\)[ \\t]*:[ \\t]*[^:]*[ \\t]*\$/\\1/p" $CONFIG_FILE) WALLET_FILE=$(sed -n "s/^[ \\t]*$KEYID[ \\t]*:[ \\t]*[^:]*[ \\t]*:[ \\t]*\\([^:]*\\)[ \\t]*\$/\\1/p" $CONFIG_FILE) debug 10 "main: KEYID=$KEYID, ENCRYPTED_FLAG=$ENCRYPTED_FLAG, WALLET_FILE=$WALLET_FILE" [ "X$ENCRYPTED_FLAG" != X ] || error "$KEYID: missing or malformed record (do you need to add an entry for '$KEYID' to $CONFIG_FILE?)" [ "X$ENCRYPTED_FLAG" = Xtrue -o "X$ENCRYPTED_FLAG" = Xfalse ] || error "$ENCRYPTED_FLAG: invalid value for " # Perform tilde expansion WALLET_FILE="$(eval echo "$WALLET_FILE")" if $ENCRYPTED_FLAG; then ENCRYPTER_CMD="gpg --encrypt --recipient $KEY_ID --output -" DECRYPTER_CMD="gpg --batch --quiet --use-agent --decrypt" debug 10 "main: checking agent running ..." [ "X$GPG_AGENT_INFO" != X ] || error "GPG_AGENT_INFO: not set (do you need to run 'eval \`gpg-agent --daemon\`'?)" debug 10 "main: checking encryption possible ..." # We do this by encrypting some text and decrypting it again. Unfortunately, we # can just pipe the crypter's output to the decrypter because the decrypter # sees stdin isn't a tty and so won't ask the use for their GPG password (if # it has not yet been cached). So we need to do this in two steps. Note also that # encrypting does not require any password, it is decrypting that requires it. TEST_ENCRYPTED_FILE=$(mktemp /tmp/$PROGNAME.$$.test-encrypted.XXXXXXXXXX) TEST_PLAINTEXT_FILE=$(mktemp /tmp/$PROGNAME.$$.test-plaintext.XXXXXXXXXX) echo non-empty-string-to-avoid-gpg-warning | $ENCRYPTER_CMD > $TEST_ENCRYPTED_FILE || error "encryption test failed (do you need to generate a GPG key with 'gpg --gen-key'?)" # Note that there is no '<' here. debug 10 "main: checking decryption possible ..." $DECRYPTER_CMD $TEST_ENCRYPTED_FILE > $TEST_PLAINTEXT_FILE || error "decryption test failed (case #1)" rm -f $TEST_ENCRYPTED_FILE [ "X$(cat $TEST_PLAINTEXT_FILE 2>&1)" = Xnon-empty-string-to-avoid-gpg-warning ] || error "decryption test failed (case #2)" rm -f $TEST_PLAINTEXT_FILE else ENCRYPTER_CMD="cat" DECRYPTER_CMD="cat" fi if [ ! -f $WALLET_FILE ]; then { echo "This file is a wallet; it can contain entries to query or search." echo echo "The format of each entry is up to you! The only restrictions are that" echo "entries should be separated by a blank line and that the first line" echo "of an entry should not be indented." echo echo "So complete entries might look like this:" echo echo " My Yahoo Email Account" echo " login: fredbloggs@yahoo.com" echo " password: opensesame" echo echo " My Gmail Email Account:" echo " login: fredbloggs@gmail.com" echo " password: eggsbutnobacon" echo echo "Or like this:" echo echo " My Yahoo Email Account" echo " login: fredbloggs@yahoo.com" echo " password: opensesame" echo echo " My Gmail Email Account:" echo " login: fredbloggs@gmail.com" echo " password: eggsbutnobacon" echo echo "Or like this:" echo echo " My Yahoo Email Account: login: fredbloggs@yahoo.com, password: opensesame" echo echo " My Gmail Email Account: login: fredbloggs@gmail.com, password: eggsbutnobacon" echo echo "Or even like this:" echo echo " My Yahoo Email Account:fredbloggs@gmail.com:eggsbutnobacon" echo echo " My Gmail Email Account:fredbloggs@gmail.com:eggsbutnobacon" echo echo "And of course, you don't need to use it only for online accounts and" echo "passwords; you could use it as a phone book, birthday list, bank" echo "account details, for code snippets you want to remember ... for" echo "whatever you like!" echo echo "Finally, note that all the above text should be removed once you" echo "add your own entries, and that all indentation shown above is for" echo "cosmetic purposes only; your own entries should not have their first" echo "line indented (as described above)." } | $ENCRYPTER_CMD > $WALLET_FILE fi # Check wallet file accessible. debug 10 "main: checking wallet accessible ..." [ -f $WALLET_FILE -a -r $WALLET_FILE -a -w $WALLET_FILE ] || error "$WALLET_FILE: access problems (did you run '$PROGNAME --init'? is the file readable and writable by you?)" # No args means search. if [ "X$1" != X ]; then debug 10 "main: searching ..." cat $WALLET_FILE | $DECRYPTER_CMD | perl -e '$/ = ""; $f=1; while () { if (/$ARGV[0]/i) { print; $f=0; } } exit $f' -- "$@" return $? fi # Otherwise we edit. # Create and name temp files PLAINTEXT_PREEDIT_FILE=$(mktemp /tmp/$PROGNAME.$$.plainpre.XXXXXXXXXX) PLAINTEXT_POSTEDIT_FILE=$(mktemp /tmp/$PROGNAME.$$.plainpost.XXXXXXXXXX) ENCRYPTEDTEXT_PREEDIT_FILE=$(mktemp /tmp/$PROGNAME.$$.encryptpre.XXXXXXXXXX) ENCRYPTEDTEXT_POSTEDIT_FILE=$(mktemp /tmp/$PROGNAME.$$.encryptpost.XXXXXXXXXX) # Start transaction (meaning nothing is actually done until the 'Commit transaction' below). cat $WALLET_FILE > $ENCRYPTEDTEXT_PREEDIT_FILE # Decrypt to temp file. Note that we must already be sure that the private key # has been cached because we cannot ask for it here because the decrypter's # stdin is not a terminal, so it has no capability to ask. debug 10 "main: decrypting ..." cat $ENCRYPTEDTEXT_PREEDIT_FILE | $DECRYPTER_CMD > $PLAINTEXT_PREEDIT_FILE rm -f $ENCRYPTEDTEXT_PREEDIT_FILE # Edit, and if no changes clean up and exit. debug 10 "main: editing ..." cat $PLAINTEXT_PREEDIT_FILE > $PLAINTEXT_POSTEDIT_FILE if ! ${EDITOR:-vi} $PLAINTEXT_POSTEDIT_FILE; then rm -f $PLAINTEXT_PREEDIT_FILE $PLAINTEXT_POSTEDIT_FILE $ENCRYPTEDTEXT_POSTEDIT_FILE error "$EDITOR: failed" fi if cmp $PLAINTEXT_PREEDIT_FILE $PLAINTEXT_POSTEDIT_FILE > /dev/null; then debug 10 "main: no changes; exiting ..." shred -zu $PLAINTEXT_PREEDIT_FILE $PLAINTEXT_POSTEDIT_FILE rm -f $ENCRYPTEDTEXT_POSTEDIT_FILE # is empty; don't shred exit $? fi shred -zu $PLAINTEXT_PREEDIT_FILE # Encrypt debug 10 "main: encrypting ..." cat $PLAINTEXT_POSTEDIT_FILE | $ENCRYPTER_CMD > $ENCRYPTEDTEXT_POSTEDIT_FILE shred -zu $PLAINTEXT_POSTEDIT_FILE # Commit transaction debug 10 "main: committing ..." cat $ENCRYPTEDTEXT_POSTEDIT_FILE > $WALLET_FILE rm -f $ENCRYPTEDTEXT_POSTEDIT_FILE # Clear traps trap - 1 2 15 } usage() { local RC FMT RC=${1:-1} FMT="%-9s%-32s%-39s\\n" { printf "$FMT" "Usage:" "$PROGNAME " "to search for " printf "$FMT" "" "$PROGNAME" "to edit entries" echo printf "$FMT" "Options:" "-i | --init" "initialise the password wallet" printf "$FMT" "" "-v | --verbose" "be verbose" printf "$FMT" "" "-d | --debug=" "be very verbose" echo printf "$FMT" "Notes:" "gpg-agent should probably be running in daemon mode (you'll be told if it isn't and is needed)" "" } | if [ $RC = 0 ]; then cat else cat >&2 fi exit $RC } debug() { [ $VERBOSELEVEL -lt $1 ] || echo "$PROGNAME: DEBUG[$1]: $2" >&2; } info() { [ $VERBOSELEVEL -lt 3 ] || echo "$PROGNAME: INFO: $1" >&2; } warning() { [ $VERBOSELEVEL -lt 2 ] || echo "$PROGNAME: WARNING: $1" >&2; } error() { [ $VERBOSELEVEL -lt 1 ] || echo "$PROGNAME: ERROR: $1" >&2; exit 1; } internal() { echo "$PROGNAME: INTERNAL ERROR: $1" >&2; exit 2; } main "$@"