|
- #!/bin/sh -
- #
- # Copyright 2018 John-Mark Gurney.
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions
- # are met:
- # 1. Redistributions of source code must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- # 2. Redistributions in binary form must reproduce the above copyright
- # notice, this list of conditions and the following disclaimer in the
- # documentation and/or other materials provided with the distribution.
- #
- # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- # SUCH DAMAGE.
- #
- # $Id$
- #
-
- STOREDIR="$HOME/.snapaid"
- KEYS="0x524F0C37A0B946A3"
- KEY_URLS='https://svnweb.freebsd.org/doc/head/share/pgpkeys/gjb.key?view=co'
-
- 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
- hostname=people.FreeBSD.org
- completeurl="https://${hostname}/~jmg/FreeBSD-snap/snapshot.complete.idx.xz"
- currenturl="https://${hostname}/~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
- }
-
- # When first arg is --, just touch the file to fake a bad d/l
- bad_file_dl() {
- if [ x"$1" = x"--" ]; then
- touch $(basename "$2")
- else
- $WGET_orig "$@"
- fi
- }
-
- # Make sure that the storage directory is present
- mkstore() {
- mkdir "$STOREDIR" 2>/dev/null || :
- if ! [ -x "$STOREDIR" -a -w "$STOREDIR" -a -d "$STOREDIR" ]; then
- echo "$STOREDIR is not a writable directory."
- fi
- }
-
- check_keys() {
- for i in $KEYS; do
- if ! $GPG --list-keys "$i" >/dev/null 2>&1; then
- return 1
- fi
- done
-
- return 0
- }
-
- # 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 }')
-
- # Some emails are sent to both -current and -snapshot,
- # we can't handle them for now
- # such as 20160529215940.GA11785@FreeBSD.org
- if [ x"$loc" = x"" ]; then
- return 1
- fi
-
- # 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
-
- if ! (cd "$STOREDIR" && $WGET -N "$1" >/dev/null 2>&1); then
- return 1
- fi
- }
-
- 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 -
- }
-
- dlverify() {
- fname="$8"
- dlurl="$9"
- vermid="${10}"
-
- # verify snap email
- if ! get_raw "$vermid"; then
- echo "Unable to fetch/verify snapshot email for: $fname"
- return 1
- fi
-
- # fetch link
- $WGET -- "$dlurl"
-
- if ! verifyfile "$vermid" "$fname"; then
- echo 'Removing bad file.'
- rm "$fname"
- return 1
- fi
- }
-
- if ! check_keys; then
- echo 'Necessary keys have not been imported into key ring.'
- echo "Please obtain they following keyid(s):"
- echo $KEYS
- echo ""
- echo "The keys may be obtained from the following URLs:"
- for i in $KEY_URLS; do
- echo "$i"
- done
- echo ""
- echo "and imported into GPG w/ the --import option."
- echo ""
- echo "For extra security, additional verification should be done, such"
- echo "as manually verifying finger prints."
-
- exit 3
- fi
-
- if [ x"$1" = x"verify" ]; then
- shift
-
- if ! fetch "$completeurl"; then
- echo Failed to fetch the complete index.
- exit 1
- fi
-
- for i in "$@"; do
- vermid=$(getvermid "$i")
- if [ x"$vermid" = x"" ]; then
- echo "Unable to find entry for: $i"
- continue
- fi
-
- if ! get_raw "$vermid"; then
- echo "Unable to fetch snapshot email for: $i"
- continue
- fi
-
- verifyfile "$vermid" "$i"
- done
- elif [ x"$1" = x"find" ]; then
- fetch "$currenturl"
-
- tmpdir=$(mktemp -d -t snapaid)
-
- trap "rm -r $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 $(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
-
- set -- ${sel}
-
- echo selected image "$8"
-
- dlverify ${sel}
- elif [ x"$1" = x"test" ]; then
- # Setup test environment
- tmpdir=$(mktemp -d -t snapaid)
-
- trap "rm -r $tmpdir" 0
-
- cd "$tmpdir"
-
- STOREDIR="$tmpdir"/snapaid
-
- # Make sure that the check keys function works.
- echo 'Testing check_keys works...'
-
- # Prime the custom keyring
- GPG="gpg2 --no-default-keyring --keyring pubring.gpg"
- for i in $KEY_URLS; do
- $WGET -O - -- "$i" 2>/dev/null | $GPG --import 2>/dev/null
- done
-
- if ! check_keys; then
- echo failed
- exit 1
- fi
-
- KEYS_orig="$KEYS"
- KEYS="0x1384923867573928" # bogus key
-
- if check_keys; then
- echo failed
- exit 1
- fi
-
- echo passed
-
- # Test a bad download fails
- echo 'Testing dlverify...'
- WGET_orig="$WGET"
- WGET=bad_file_dl
-
- # if dlverify is successsful, then it's a failure
- if dlverify iso 13.0-CURRENT sparc64 xxx 20181026 r339752 bootonly FreeBSD-13.0-CURRENT-sparc64-20181026-r339752-bootonly.iso.xz https://download.freebsd.org/ftp/snapshots/ISO-IMAGES/13.0/FreeBSD-13.0-CURRENT-sparc64-20181026-r339752-bootonly.iso.xz 20181026184443.GD75350@FreeBSD.org; then
- echo 'failed'
- exit 1
- fi
-
- # Make sure that a bad d/l was not left behind
- if [ -e FreeBSD-13.0-CURRENT-sparc64-20181026-r339752-bootonly.iso.xz ]; then
- echo failed
- exit 1
- fi
- echo passed
-
- # 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
-
- echo tests completed!!!
- 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
|