A utility for downloading and verifying FreeBSD releases and snapshots
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

347 lines
7.2 KiB

  1. #!/bin/sh -
  2. STOREDIR="$HOME/.snapaid"
  3. setdefaults() {
  4. GPG=$(which gpg2)
  5. WGET=$(which wget)
  6. SHASUM=$(which shasum)
  7. }
  8. setdefaults
  9. if [ ! -x "$GPG" ]; then
  10. echo 'Failed to find gpg2 executable'
  11. exit 1
  12. fi
  13. if [ ! -x "$WGET" ]; then
  14. echo 'Failed to find wget executable'
  15. exit 1
  16. fi
  17. if [ ! -x "$SHASUM" ]; then
  18. echo 'Failed to find shasum executable'
  19. exit 1
  20. fi
  21. #wget:
  22. # -N for timestamps
  23. # --backups=x for backing up
  24. completeurl="https://www.funkthat.com/~jmg/FreeBSD-snap/snapshot.complete.idx.xz"
  25. currenturl="https://www.funkthat.com/~jmg/FreeBSD-snap/snapshot.idx.xz"
  26. # type release arch platform date svnrev xxx fname url mid
  27. # 1 2 3 4 5 6 7 8 9 10
  28. # 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
  29. set -e
  30. # This is used for some testing functions
  31. copy_function() {
  32. declare -F "$1" > /dev/null || return 1
  33. local func="$(declare -f "$1")"
  34. eval "${2}(${func#*\(}"
  35. }
  36. # Test function to cause a bad input
  37. cmd_failure() {
  38. exit 1
  39. }
  40. # First time fails, second time run real command
  41. gpg_first_fails() {
  42. copy_function verifygpg_orig verifygpg
  43. return 1
  44. }
  45. # Make sure that the storage directory is present
  46. mkstore() {
  47. mkdir "$STOREDIR" 2>/dev/null || :
  48. }
  49. # Given a message id, get the raw body and store it.
  50. get_raw() {
  51. mkstore
  52. mid="$1"
  53. midfile="$STOREDIR/$mid".raw
  54. if [ ! -e "$midfile" ]; then
  55. # get the location, it's a database lookup
  56. 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 }')
  57. # if it's relative, add https
  58. if [ x"$loc" != x"${loc#//}" ]; then
  59. # add https
  60. loc="https:$loc"
  61. fi
  62. # get the raw part
  63. tmpfile="$STOREDIR/.tmp.$$.$mid".raw
  64. # strip out everything but message id and first signed part
  65. $WGET -O - "$loc"+raw 2>/dev/null | awk '
  66. tolower($1) == "message-id:" && check == 0 {
  67. print
  68. }
  69. $0 == "-----BEGIN PGP SIGNED MESSAGE-----" {
  70. sigbody = 1
  71. }
  72. sigbody {
  73. print
  74. }
  75. $0 == "-----END PGP SIGNATURE-----" {
  76. sigbody = 0
  77. }' > "$tmpfile"
  78. if verifygpg "$tmpfile"; then
  79. mv "$tmpfile" "$STOREDIR/$mid.raw"
  80. else
  81. rm "$tmpfile"
  82. echo Bad signature from mail archive.
  83. return 1
  84. fi
  85. else
  86. if ! verifygpg "$midfile"; then
  87. rm "$midfile"
  88. get_raw "$mid"
  89. return $?
  90. fi
  91. fi
  92. }
  93. fetch() {
  94. mkstore
  95. (cd "$STOREDIR" && $WGET -N "$1" >/dev/null 2>&1)
  96. }
  97. getvermid() {
  98. xzcat "$STOREDIR"/snapshot.complete.idx.xz | awk '$8 == fname {
  99. print $10
  100. }' fname="$i"
  101. }
  102. # takes basename of arg, which much exist in STOREDIR, and verifies
  103. # that the signature is valid.
  104. verifygpg() {
  105. local fname
  106. fname=$(basename "$1")
  107. if ! (cd "$STOREDIR" && $GPG --verify "$fname" 2> /dev/null); then
  108. echo 'ERROR: PGP signature verification failed!'
  109. return 1
  110. fi
  111. }
  112. # Verifies the file
  113. verifyfile() {
  114. local fname
  115. local hashinfo
  116. local algo hash
  117. fname="$STOREDIR/${1}.raw"
  118. hashinfo=$(awk '
  119. check && $2 == "('"$2"')" {
  120. hashes[$1] = $4
  121. }
  122. $0 == "-----BEGIN PGP SIGNED MESSAGE-----" {
  123. check = 1
  124. }
  125. $0 == "-----BEGIN PGP SIGNATURE-----" {
  126. check = 0
  127. }
  128. END {
  129. if ("SHA512" in hashes)
  130. algo = "SHA512"
  131. else if ("SHA256" in hashes)
  132. algo = "SHA256"
  133. else {
  134. print "unkn BADHASH"
  135. exit 1
  136. }
  137. print algo " " hashes[algo]
  138. }
  139. ' "$fname")
  140. read algo hash <<-EOF
  141. ${hashinfo}
  142. EOF
  143. if [ x"$algo" == x"unkn" -o x"$algo" = x"" ]; then
  144. echo 'Unable to find hash for file.'
  145. exit 1
  146. fi
  147. echo "$hash $2" | $SHASUM -a "${algo#SHA}" -c -
  148. }
  149. if [ x"$1" = x"verify" ]; then
  150. shift
  151. fetch "$completeurl"
  152. for i in "$@"; do
  153. vermid=$(getvermid "$i")
  154. if [ x"$vermid" = x"" ]; then
  155. echo "Unable to find entry for: $i"
  156. continue
  157. fi
  158. get_raw "$vermid"
  159. if ! verifygpg "$vermid".raw; then
  160. echo "Unable to verify: $i"
  161. fi
  162. verifyfile "$verurl" "$i"
  163. done
  164. elif [ x"$1" = x"find" ]; then
  165. fetch "$currenturl"
  166. tmpdir=$(mktemp -d -t snapaid)
  167. trap "rm -rf $tmpdir" 0
  168. ( cd "$tmpdir";
  169. xzcat "$STOREDIR"/snapshot.idx.xz | sort -r -k 5 > selection;
  170. while :; do
  171. # display current list
  172. cnt=$(wc -l < selection)
  173. awk '
  174. BEGIN {
  175. fmtstr = "%2s %-3s %-15s %-18s %-18s %-8s %-7s\n"
  176. printf(fmtstr, "#", "TYP", "RELEASE", "ARCH", "PLATFORM/TYPE", "DATE", "SVNREV")
  177. cnt = 1
  178. }
  179. {
  180. if ($4 == "xxx")
  181. plt=$7
  182. else
  183. plt=$4
  184. printf(fmtstr, cnt, $1, $2, $3, plt, $5, $6)
  185. if (cnt >= 20)
  186. exit 0
  187. cnt += 1
  188. }
  189. ' selection
  190. read -p 'Select image, enter search term, reset, or quit: ' sel
  191. if [ x"$sel" = x"reset" ]; then
  192. xzcat "$STOREDIR"/snapshot.idx.xz | sort -r -k 5 > selection;
  193. continue
  194. elif [ x"$sel" = x"quit" ]; then
  195. echo "$sel" > sel
  196. break
  197. fi
  198. if [ "$cnt" -gt 20 ]; then
  199. cnt=20
  200. fi
  201. if [ "$sel" -ge 1 -a "$sel" -le "$cnt" ] 2>/dev/null; then
  202. echo selected image $sel
  203. echo $(tail -n +"$sel" selection | head -n 1) > sel
  204. break
  205. else
  206. # restrict
  207. if ! grep -- "$sel" selection > selection.new; then
  208. echo WARNING: Ignoring selection, no results.
  209. else
  210. mv selection.new selection
  211. fi
  212. fi
  213. done
  214. )
  215. sel=$(cat "$tmpdir"/sel)
  216. if [ x"$sel" = x"quit" ]; then
  217. exit 0
  218. fi
  219. echo $sel
  220. fname=$(cut -f 8 -d ' ' "$tmpdir"/sel)
  221. dlurl=$(cut -f 9 -d ' ' "$tmpdir"/sel)
  222. verurl=$(cut -f 10 -d ' ' "$tmpdir"/sel)
  223. # fetch link
  224. $WGET -- "$dlurl"
  225. # verify image
  226. fetch "$verurl"
  227. if ! verifygpg "$verurl"; then
  228. echo "Unable to verify: $fname"
  229. fi
  230. if ! verifyfile "$verurl" "$fname"; then
  231. rm "$fname"
  232. fi
  233. elif [ x"$1" = x"test" ]; then
  234. # Run various tests
  235. # Test getting the raw file
  236. echo 'Testing get_raw success...'
  237. mid='20160122055622.GA87581@FreeBSD.org'
  238. get_raw "$mid"
  239. # Verify resulsts
  240. (cd "$STOREDIR" && echo '6e53df5995b6cc423c7f2d63b6df52d5d7f70e8586c25f91433fd8a1a2466e77be6a38884bde8bedd9ff6e7deb0215a66e1c2a16e4955503c20445e649a5fb47 20160122055622.GA87581@FreeBSD.org.raw' | $SHASUM -a 512 -c)
  241. echo passed
  242. # If the file already exists, but fails verification, that
  243. # it will refetch and be correct
  244. echo 'Testing get_raw with file already present that fails verification...'
  245. copy_function verifygpg verifygpg_orig
  246. copy_function gpg_first_fails verifygpg
  247. get_raw "$mid"
  248. (cd "$STOREDIR" && echo '6e53df5995b6cc423c7f2d63b6df52d5d7f70e8586c25f91433fd8a1a2466e77be6a38884bde8bedd9ff6e7deb0215a66e1c2a16e4955503c20445e649a5fb47 20160122055622.GA87581@FreeBSD.org.raw' | $SHASUM -a 512 -c)
  249. echo passed
  250. # If the file already exists, a "broken" wget won't cause
  251. # a problem
  252. echo 'Testing get_raw with file already present...'
  253. WGET=cmd_failure
  254. get_raw "$mid"
  255. echo passed
  256. # Test failure
  257. echo 'Testing get_raw fails w/ bad data...'
  258. WGET=cmd_failure
  259. rm "$STOREDIR/$mid.raw"
  260. # it should fail
  261. ! get_raw "$mid"
  262. # and the desired file should not exist
  263. if [ -e "$STOREDIR/$mid.raw" ]; then
  264. echo 'Test failed!'
  265. exit 1;
  266. fi
  267. echo passed
  268. setdefaults
  269. else
  270. echo "Unknown verb: $1"
  271. echo "Usage:"
  272. echo " $0 verify file ..."
  273. echo " $0 find"
  274. echo ""
  275. echo "The verify option will attempt to verify each file specified."
  276. echo ""
  277. echo "The find option will start up an interactive session to find"
  278. echo "and select the snapshot to download and verify."
  279. fi