#!/bin/sh - STOREDIR="$HOME/.snapaid" setdefaults() { GPG=$(which gpg2) WGET=$(which wget) SHASUM=$(which shasum) } setdefaults if [ ! -x "$GPG" ]; then echo 'Failed to find gpg2 executable' exit 1 fi if [ ! -x "$WGET" ]; then echo 'Failed to find wget executable' exit 1 fi if [ ! -x "$SHASUM" ]; then echo 'Failed to find shasum executable' exit 1 fi #wget: # -N for timestamps # --backups=x for backing up completeurl="https://www.funkthat.com/~jmg/FreeBSD-snap/snapshot.complete.idx.xz" currenturl="https://www.funkthat.com/~jmg/FreeBSD-snap/snapshot.idx.xz" # type release arch platform date svnrev xxx fname url mid # 1 2 3 4 5 6 7 8 9 10 # iso 11.1-STABLE arm-armv6 BEAGLEBONE 20180315 r330998 xxx FreeBSD-11.1-STABLE-arm-armv6-BEAGLEBONE-20180315-r330998.img.xz https://download.freebsd.org/ftp/snapshots/ISO-IMAGES/11.1/FreeBSD-11.1-STABLE-arm-armv6-BEAGLEBONE-20180315-r330998.img.xz 20180316000842.GA7399@FreeBSD.org set -e # This is used for some testing functions copy_function() { declare -F "$1" > /dev/null || return 1 local func="$(declare -f "$1")" eval "${2}(${func#*\(}" } # Test function to cause a bad input cmd_failure() { exit 1 } # First time fails, second time run real command gpg_first_fails() { copy_function verifygpg_orig verifygpg return 1 } # Make sure that the storage directory is present mkstore() { mkdir "$STOREDIR" 2>/dev/null || : } # Given a message id, get the raw body and store it. get_raw() { mkstore mid="$1" midfile="$STOREDIR/$mid".raw if [ ! -e "$midfile" ]; then # get the location, it's a database lookup loc=$($WGET --max-redirect=0 --method=HEAD -S -o - -O - 'https://docs.freebsd.org/cgi/mid.cgi?'"$mid" 2>/dev/null | awk 'tolower($1) == "location:" { print $2; exit }') # if it's relative, add https if [ x"$loc" != x"${loc#//}" ]; then # add https loc="https:$loc" fi # get the raw part tmpfile="$STOREDIR/.tmp.$$.$mid".raw # strip out everything but message id and first signed part $WGET -O - "$loc"+raw 2>/dev/null | awk ' tolower($1) == "message-id:" && check == 0 { print } $0 == "-----BEGIN PGP SIGNED MESSAGE-----" { sigbody = 1 } sigbody { print } $0 == "-----END PGP SIGNATURE-----" { sigbody = 0 }' > "$tmpfile" if verifygpg "$tmpfile"; then mv "$tmpfile" "$STOREDIR/$mid.raw" else rm "$tmpfile" echo Bad signature from mail archive. return 1 fi else if ! verifygpg "$midfile"; then rm "$midfile" get_raw "$mid" return $? fi fi } fetch() { mkstore (cd "$STOREDIR" && $WGET -N "$1" >/dev/null 2>&1) } getvermid() { xzcat "$STOREDIR"/snapshot.complete.idx.xz | awk '$8 == fname { print $10 }' fname="$i" } # takes basename of arg, which much exist in STOREDIR, and verifies # that the signature is valid. verifygpg() { local fname fname=$(basename "$1") if ! (cd "$STOREDIR" && $GPG --verify "$fname" 2> /dev/null); then echo 'ERROR: PGP signature verification failed!' return 1 fi } # Verifies the file verifyfile() { local fname local hashinfo local algo hash fname="$STOREDIR/${1}.raw" hashinfo=$(awk ' check && $2 == "('"$2"')" { hashes[$1] = $4 } $0 == "-----BEGIN PGP SIGNED MESSAGE-----" { check = 1 } $0 == "-----BEGIN PGP SIGNATURE-----" { check = 0 } END { if ("SHA512" in hashes) algo = "SHA512" else if ("SHA256" in hashes) algo = "SHA256" else { print "unkn BADHASH" exit 1 } print algo " " hashes[algo] } ' "$fname") read algo hash <<-EOF ${hashinfo} EOF if [ x"$algo" == x"unkn" -o x"$algo" = x"" ]; then echo 'Unable to find hash for file.' exit 1 fi echo "$hash $2" | $SHASUM -a "${algo#SHA}" -c - } if [ x"$1" = x"verify" ]; then shift fetch "$completeurl" for i in "$@"; do vermid=$(getvermid "$i") if [ x"$vermid" = x"" ]; then echo "Unable to find entry for: $i" continue fi get_raw "$vermid" if ! verifygpg "$vermid".raw; then echo "Unable to verify: $i" fi verifyfile "$verurl" "$i" done elif [ x"$1" = x"find" ]; then fetch "$currenturl" tmpdir=$(mktemp -d -t snapaid) trap "rm -rf $tmpdir" 0 ( cd "$tmpdir"; xzcat "$STOREDIR"/snapshot.idx.xz | sort -r -k 5 > selection; while :; do # display current list cnt=$(wc -l < selection) awk ' BEGIN { fmtstr = "%2s %-3s %-15s %-18s %-18s %-8s %-7s\n" printf(fmtstr, "#", "TYP", "RELEASE", "ARCH", "PLATFORM/TYPE", "DATE", "SVNREV") cnt = 1 } { if ($4 == "xxx") plt=$7 else plt=$4 printf(fmtstr, cnt, $1, $2, $3, plt, $5, $6) if (cnt >= 20) exit 0 cnt += 1 } ' selection read -p 'Select image, enter search term, reset, or quit: ' sel if [ x"$sel" = x"reset" ]; then xzcat "$STOREDIR"/snapshot.idx.xz | sort -r -k 5 > selection; continue elif [ x"$sel" = x"quit" ]; then echo "$sel" > sel break fi if [ "$cnt" -gt 20 ]; then cnt=20 fi if [ "$sel" -ge 1 -a "$sel" -le "$cnt" ] 2>/dev/null; then echo selected image $sel echo $(tail -n +"$sel" selection | head -n 1) > sel break else # restrict if ! grep -- "$sel" selection > selection.new; then echo WARNING: Ignoring selection, no results. else mv selection.new selection fi fi done ) sel=$(cat "$tmpdir"/sel) if [ x"$sel" = x"quit" ]; then exit 0 fi echo $sel fname=$(cut -f 8 -d ' ' "$tmpdir"/sel) dlurl=$(cut -f 9 -d ' ' "$tmpdir"/sel) verurl=$(cut -f 10 -d ' ' "$tmpdir"/sel) # fetch link $WGET -- "$dlurl" # verify image fetch "$verurl" if ! verifygpg "$verurl"; then echo "Unable to verify: $fname" fi if ! verifyfile "$verurl" "$fname"; then rm "$fname" fi elif [ x"$1" = x"test" ]; then # Run various tests # Test getting the raw file echo 'Testing get_raw success...' mid='20160122055622.GA87581@FreeBSD.org' get_raw "$mid" # Verify resulsts (cd "$STOREDIR" && echo '6e53df5995b6cc423c7f2d63b6df52d5d7f70e8586c25f91433fd8a1a2466e77be6a38884bde8bedd9ff6e7deb0215a66e1c2a16e4955503c20445e649a5fb47 20160122055622.GA87581@FreeBSD.org.raw' | $SHASUM -a 512 -c) echo passed # If the file already exists, but fails verification, that # it will refetch and be correct echo 'Testing get_raw with file already present that fails verification...' copy_function verifygpg verifygpg_orig copy_function gpg_first_fails verifygpg get_raw "$mid" (cd "$STOREDIR" && echo '6e53df5995b6cc423c7f2d63b6df52d5d7f70e8586c25f91433fd8a1a2466e77be6a38884bde8bedd9ff6e7deb0215a66e1c2a16e4955503c20445e649a5fb47 20160122055622.GA87581@FreeBSD.org.raw' | $SHASUM -a 512 -c) echo passed # If the file already exists, a "broken" wget won't cause # a problem echo 'Testing get_raw with file already present...' WGET=cmd_failure get_raw "$mid" echo passed # Test failure echo 'Testing get_raw fails w/ bad data...' WGET=cmd_failure rm "$STOREDIR/$mid.raw" # it should fail ! get_raw "$mid" # and the desired file should not exist if [ -e "$STOREDIR/$mid.raw" ]; then echo 'Test failed!' exit 1; fi echo passed setdefaults else echo "Unknown verb: $1" echo "Usage:" echo " $0 verify file ..." echo " $0 find" echo "" echo "The verify option will attempt to verify each file specified." echo "" echo "The find option will start up an interactive session to find" echo "and select the snapshot to download and verify." fi