#!/bin/bash PROGNAME=${0##*/} # Notes to users: # # (C) 2021-2024 Alexis Huxley - distributed under GPL3 - absolutely no warranty! # # Notes to self: # # The web page https://www.pasta.freemyip.com/computing/articles/caching-of-plaintext-passwords-by-the-subversion-client-has-been-disabled-by-default/ # expects this script to be accessible at https://svn.pasta.freemyip.com/main/smalltools/trunk/svn-cache-passwd/bin/svn-cache-passwd # So don't move/rename it without updating that page (and this comment) accordingly! # # That webpage advertises this script as being simple. For this # reason I avoid use of miniade, which would require the user # to do more than just download *this* script. main() { # Defaults for options VERBOSELEVEL=2 # Option processing while :; do case $1 in -v) VERBOSELEVEL=3 ;; --) shift; break ;; -*) usage ;; *) break ;; esac shift done # Argument processing [ $# -ge 1 -a $# -le 3 ] || usage REPO_URL=$1 # (further argument analysis is left until after the repo URL is validated) # Sanity checks and derivations [[ $REPO_URL =~ ^(https?)://([^/:]+)(:([^/]+))? ]] || error "$REPO_URL: doesn't look like a repo URL (hint: should look like ://[:][/path] where is 'http' or 'https' and stuff in square brackets is optional)" REPO_PROT=${BASH_REMATCH[1]} REPO_HOST=${BASH_REMATCH[2]} REPO_PORT=${BASH_REMATCH[4]} if [ ${#REPO_PORT} != 0 ]; then : elif [ $REPO_PROT = http ]; then REPO_PORT=80 else REPO_PORT=443 fi for COMMAND in wget openssl; do type -p $COMMAND > /dev/null || error "$COMMAND: could not locate this command (hint: can you install it?)" done # Argument processing if [ $# = 1 ]; then read -p "Username: " REPO_USERNAME read -sp "Password: " REPO_PASSWORD echo elif [ $# = 2 ]; then REPO_USERNAME=$2 read -sp "Password: " REPO_PASSWORD echo else REPO_USERNAME=$2 REPO_PASSWORD=$3 fi # Get realm (ignore authentication errors) info "attempting to get authentication realm ..." WGET_OUTPUT=$(wget -O /dev/null -S $REPO_URL 2>&1) || true if [[ $WGET_OUTPUT =~ Basic\ realm=\"(.*)\" ]]; then REPO_REALM=${BASH_REMATCH[1]} else info "failed to get authentication realm; prompting ..." DFLT_REPO_REALM='Subversion Service' read -p "Realm [$DFLT_REPO_REALM]: " REPO_REALM [ ${#REPO_REALM} != 0 ] || REPO_REALM=$DFLT_REPO_REALM fi # Construct the Subversion realmstring from the protocol, host, port and the actual authenticaton realm. REPO_REALMSTRING="<$REPO_PROT://$REPO_HOST:$REPO_PORT> $REPO_REALM" # Get password cache filename (named after the repo URL and realm name) OPENSSL_OUTPUT=$(echo -n "$REPO_REALMSTRING" | openssl md5 2>&1) [[ $OPENSSL_OUTPUT =~ ^(|MD5)\(stdin\)=\ (.*) ]] || error "failed to parse openssl output (hint: openssl said: $OPENSSL_OUTPUT)" REPO_CACHE_FILENAME=$HOME/.subversion/auth/svn.simple/${BASH_REMATCH[2]} # Write temporary password cache file mkdir -p "${REPO_CACHE_FILENAME%/*}" ( umask 077; touch $REPO_CACHE_FILE.tmp ) { echo "K 8" echo "passtype" echo "V 6" echo "simple" echo "K 15" echo "svn:realmstring" echo "V ${#REPO_REALMSTRING}" echo "$REPO_REALMSTRING" echo "K 8" echo "username" echo "V ${#REPO_USERNAME}" echo "$REPO_USERNAME" echo "K 8" echo "password" echo "V ${#REPO_PASSWORD}" echo "$REPO_PASSWORD" echo "END" } > $REPO_CACHE_FILENAME.tmp # Save cache file to right name (or discard) if [ ! -f $REPO_CACHE_FILENAME ]; then info "creating password cache ..." mv $REPO_CACHE_FILENAME.tmp $REPO_CACHE_FILENAME elif cmp -s $REPO_CACHE_FILENAME.tmp $REPO_CACHE_FILENAME; then info "password cache does not need updating" rm $REPO_CACHE_FILENAME.tmp else warning "password cache will be modified (old version will be saved to /tmp)" mv $REPO_CACHE_FILENAME /tmp mv $REPO_CACHE_FILENAME.tmp $REPO_CACHE_FILENAME fi } usage() { echo "Usage: $PROGNAME [ -v ] [ [ ] ]" >&2 exit 1 } 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 } main "$@"