#!/bin/sh # Copyright (c) 2017, 2021, 2023 Jonas 'Sortie' Termansen. # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # tix-fetch # Download operating system files. set -e boot=false collection="" continue="" execpatch=false initrd=false insecure_downgrade_to_http=false insecure_no_check_certificate=false input_release_file= input_release_sig_file= input_sha256sum= normalize=false output="" outputdir="" output_release_file= # TODO: A better term for this? output_release_sig_file= # TODO: A better term for this? output_sha256sum= patch=false package=false release=false repository_metadata=false sha256=false sha256sum=false source=false source_full=false sysroot="" toolchain=false url=false url_main=false url_main_release=false url_mirror=false url_mirror_release=false url_release_sig=false url_sha256sum=false upgrade=false # TODO: Option to select this default: # TODO: This hides errors. Fix wget so it has a quiet, but errors, mode. wget_options="-q --show-progress" # TODO: Ability to get source code easily for gcc/binutils/libstdc++. dashdash= previous_option= for argument do if test -n "$previous_option"; then eval $previous_option=\$argument previous_option= shift continue fi case $argument in *=?*) parameter=$(expr "X$argument" : '[^=]*=\(.*\)' || true) ;; *=) parameter= ;; *) parameter=yes ;; esac case $dashdash$argument in --) dashdash=yes ;; -c) continue="-c" ;; # TODO: Support -ofoo -o) previous_option=output ;; -O) previous_option=outputdir ;; -q) wget_options="-q" ;; -v) wget_options="-v" ;; --boot) boot=true ;; --collection=*) collection=$parameter ;; --collection) previous_option=collection ;; --continue) continue="--continue" ;; --download-non-verbose) wget_options="-nv" ;; --download-non-verbose) wget_options="-v" ;; --download-quiet) wget_options="-q" ;; --download-verbose) wget_options="-v" ;; --execpatch) execpatch=true ;; --initrd) initrd=true ;; --input-release-file=*) input_release_file=$parameter ;; --insecure-downgrade-to-http) insecure_downgrade_to_http=true ;; --insecure-no-check-certificate) insecure_no_check_certificate=true ;; --input-release-file) previous_option=input_release_file ;; --input-release-sig-file=*) input_release_sig_file=$parameter ;; --input-release-sig-file) previous_option=input_release_sig_file ;; --input-sha256sum=*) input_sha256sum=$parameter ;; --input-sha256sum) previous_option=input_sha256sum ;; --normalize) normalize=true ;; --nv) wget_options="-nv" ;; --outputdir=*) outputdir=$parameter ;; --outputdir) previous_option=outputdir ;; --output=*) output=$parameter ;; --output) previous_option=output ;; --output-release-file=*) output_release_file=$parameter ;; --output-release-file) previous_option=output_release_file ;; --output-release-sig-file=*) output_release_sig_file=$parameter ;; --output-release-sig-file) previous_option=output_release_sig_file ;; --output-sha256sum=*) output_sha256sum=$parameter ;; --output-sha256sum) previous_option=output_sha256sum ;; --output-upgrade-file=*) output_upgrade_file=$parameter ;; --output-upgrade-file) previous_option=output_upgrade_file ;; --package) package=true ;; --patch) patch=true ;; --repository-metadata) repository_metadata=true ;; --sha256) sha256=true ;; --sha256sum) sha256sum=true ;; --source-full) source_full=true ;; --source) source=true ;; --sysroot) previous_option=sysroot ;; --sysroot=*) sysroot=$parameter ;; --toolchain) toolchain=true ;; --upgrade) upgrade=true ;; --url) url=true ;; --url-main) url_main=true ;; --url-mirror) url_mirror=true ;; --url-main-release) url_main_release=true ;; --url-mirror-release) url_mirror_release=true ;; --url-release-sig) url_release_sig=true ;; --url-sha256sum) url_sha256sum=true ;; --wget-options) previous_option=wget_options ;; --wget-options=*) wget_options=$parameter ;; -*) echo "$0: unrecognized option $argument" >&2 exit 1 ;; *) break ;; esac shift done if test -n "$previous_option"; then echo "$0: option '$argument' requires an argument" >&2 exit 1 fi # TODO: Mutually incompatible options. conf() { sed -E -e 's/([a-zA-Z0-9_]+) *? *= */\U\1=/' \ -e 's/=yes$/=true/' -e 's/no$/=false/' "$3" | \ tix-vars -d "$2" - "$4" } tmpdir=$(mktemp -dt tix-fetch.XXXXXX) trap 'rm -rf -- "$tmpdir"' EXIT HUP INT QUIT TERM upgrade_conf="${collection%/}/etc/upgrade.conf" CHANNEL=$(conf -d '' "$upgrade_conf" CHANNEL) RELEASE_KEY=$(conf -d '' "$upgrade_conf" RELEASE_KEY) RELEASE_SIG_URL=$(conf -d '' "$upgrade_conf" RELEASE_SIG_URL) PREFERRED_MIRROR=$(conf -d '' "$upgrade_conf" PREFERRED_MIRROR) FORCE_MIRROR=$(conf -d '' "$upgrade_conf" FORCE_MIRROR) USER_AGENT="$(uname -s)/$(uname -r) ($(uname -m); $(uname -v))" if $insecure_no_check_certificate; then echo "$0: warning: insecurely not checking https certificates" >&2 wget_options="$wget_options --no-check-certificate" fi if $insecure_downgrade_to_http; then echo "$0: warning: insecurely downloading without https" >&2 RELEASE_SIG_URL="$(echo "$RELEASE_SIG_URL" | sed -E 's,^https:,http:,')" fi if $url_release_sig; then printf "%s\n" "$RELEASE_SIG_URL" exit fi # HACK: Provide more useful errors when wget is silent: do_wget() { (set +e wget "$@" status=$? set -e what= case $status in 0) exit 0 ;; 1) what="Generic error" ;; 2) what="Parse error" ;; 3) what="File I/O error" ;; 4) what="Network I/O error" ;; 5) what="Transport Layer Security verification failure" ;; 6) what="Username/password failure" ;; 7) what="Protocol error" ;; 8) what="Error response" ;; *) what="Exit code $status" ;; esac echo "$0: $what when running: wget $@" >&2 exit $status) } # Fetch signed release description. download_release_sh() { (cd "$tmpdir" && do_wget -U "$USER_AGENT" $wget_options -O release.sh.sig \ -- "$RELEASE_SIG_URL") signify -Vq -p "$RELEASE_KEY" -em "$tmpdir/release.sh" } true > "$tmpdir/upgrade.sh" if [ -z "$input_release_file" -a -z "$input_release_sig_file" ]; then download_release_sh # TODO: tix-vars's output is not quoted so it can be input again. tix-vars "$tmpdir/release.sh" | \ grep -E '^UPGRADE_=' | \ cat > "$tmpdir/upgrade.sh" UPGRADE_SIG_URL=$(tix-vars -d '' "$tmpdir/upgrade.sh" UPGRADE_SIG_URL) if $upgrade && [ -n "$UPGRADE_SIG_URL" ]; then RELEASE_SIG_URL="$UPGRADE_SIG_URL" RELEASE_KEY=$(tix-vars "$tmpdir/upgrade.sh" UPGRADE_KEY) download_release_sh fi fi if [ -n "$input_release_file" ]; then cp -T -- "$input_release_file" "$tmpdir/release.sh" elif [ -n "$input_release_sig_file" ]; then signify -Vq -p "$RELEASE_KEY" -em "$tmpdir/release.sh" fi # Store the signed release file if requested. if [ -n "$output_release_sig_file" ]; then cp -T -- "$tmpdir/release.sh.sig" "$output_release_sig_file" fi # Store the release file (without signature) if requested. if [ -n "$output_release_file" ]; then cp -T -- "$tmpdir/release.sh" "$output_release_file" fi # Store the upgrade file if requested. if [ -n "$output_upgrade_file" ]; then cp -T -- "$tmpdir/upgrade.sh" "$output_upgrade_file" fi # Load the release description. # TODO: SECURITY: Protect against responding with older release.sh. # TODO: DO NOT SUBMIT: Temporary compatibility. MAIN=$(tix-vars -d '' "$tmpdir/release.sh" MAIN) MASTER=$(tix-vars -d '' "$tmpdir/release.sh" MASTER) if [ -z "$MAIN" ]; then MAIN="$MASTER" fi RELEASE=$(tix-vars "$tmpdir/release.sh" RELEASE) MACHINE=$(tix-vars "$tmpdir/release.sh" MACHINE) SHA256SUM_FILE=$(tix-vars -d sha256sum "$tmpdir/release.sh" SHA256SUM_FILE) SHA256SUM_SHA256SUM=$(tix-vars "$tmpdir/release.sh" SHA256SUM_SHA256SUM) MIRRORS=$(tix-vars -d '' "$tmpdir/release.sh" MIRRORS) if $url_main; then printf "%s\n" "$MAIN" exit elif $url_main_release; then printf "%s\n" "$MAIN/$RELEASE" exit fi # Default to the main mirror but switch to the preferred mirror if the release # description knows about the mirror and believes it to be trustworthy. MIRROR="$MAIN" for POTENTIAL_MIRROR in $MIRRORS; do if [ "$POTENTIAL_MIRROR" = "$PREFERRED_MIRROR" ]; then MIRROR="$PREFERRED_MIRROR" fi done if [ -n "$PREFERRED_MIRROR" ] && [ "$MIRROR" != "$PREFERRED_MIRROR" ]; then if [ "$FORCE_MIRROR" = true ]; then MIRROR="$PREFERRED_MIRROR" else echo "$0: warning: ignoring unsupported mirror $PREFERRED_MIRROR" >&2 fi fi # TODO: Make sure the distant future http downgrade is supported. if $insecure_downgrade_to_http; then MIRROR="$(echo "$MIRROR" | sed -E 's,^https:,http:,')" fi if $url_mirror; then printf "%s\n" "$MIRROR" exit elif $url_mirror_release; then printf "%s\n" "$MIRROR/$RELEASE" exit fi RELEASE_URL="$MIRROR/$RELEASE" # Fetch sha256sum file and check its SHA256 hash with the release description. if $url_sha256sum; then printf "%s\n" "$RELEASE_URL/$SHA256SUM_FILE" exit fi if [ -z "$input_sha256sum" ]; then # TODO: If the mirror doesn't work, try the main. (cd "$tmpdir" && do_wget -U "$USER_AGENT" $wget_options -O sha256sum \ -- "$RELEASE_URL/$SHA256SUM_FILE") else cp -T -- "$input_sha256sum" "$tmpdir/sha256sum" fi # TODO: Check if upstream release description changed, if so, start over. echo "$SHA256SUM_SHA256SUM $tmpdir/sha256sum" | sha256sum -cq # Store the sha256sum file if requested. if [ -n "$output_sha256sum" ]; then cp -T -- "$tmpdir/sha256sum" "$output_sha256sum" fi escape_extended_regex() { printf "%s\n" "$1" | sed -E -e 's/[[$()*?\+.^{|}]/\\\0/g' } request() { REQUEST="$1" REQUESTDIR="$2" REQUESTFINAL="${3-$1}" FULLREQUEST="$REQUESTDIR$REQUEST" if $url; then printf '%s\n' "$RELEASE_URL/$FULLREQUEST" return fi if $sha256 || $sha256sum; then set +e # Don't fail if grep exits 1 (no match). # TODO: Should this be a checksum(1) feature to look up a hash? grep -E "^[0-9a-fA-F]{64} $(escape_extended_regex "$FULLREQUEST")$" \ "$tmpdir/sha256sum" > "$tmpdir/match" EXITCODE=$? set -e if [ 2 -le "$EXITCODE" ]; then (exit $EXITCODE); fi if $sha256 && [ -s "$tmpdir/match" ]; then grep -Eo '^[0-9a-fA-F]{64}' "$tmpdir/match" fi if $sha256sum && [ -s "$tmpdir/match" ]; then cat "$tmpdir/match" fi return fi # Decide the final location the file will end up. if [ -n "$output" ]; then FINAL="$output" OUTPUTDIR=$(dirname -- "$output") elif [ -n "$outputdir" ]; then FINAL="$outputdir/$REQUESTFINAL" OUTPUTDIR="$outputdir" else FINAL="$REQUESTFINAL" OUTPUTDIR=. fi # If a resumable download, store the file directly to the destination path. # Otherwise download to a temporary directory and move only to the final # location if the cryptographic check is passed. if [ -n "$continue" ]; then DOWNLOADDIR="$OUTPUTDIR" OUTPUT="$FINAL" else DOWNLOADDIR="$tmpdir/download" OUTPUT="$DOWNLOADDIR/$REQUEST" mkdir -p -- "$DOWNLOADDIR" fi # Fetch the file. # TODO: If the mirror doesn't work, try the main. (cd "$DOWNLOADDIR" && mkdir -p -- "$(dirname -- "$REQUEST")" && do_wget -U "$USER_AGENT" $wget_options $continue -O "$REQUEST" \ -- "$RELEASE_URL/$FULLREQUEST") # Verify the cryptographic integrity of the fetched file. ABSOLUTE_OUTPUT=$(realpath -- "$OUTPUT") mkdir -p -- "$tmpdir/check" (cd "$tmpdir/check" && mkdir -p -- "$(dirname -- "$FULLREQUEST")" ln -s -- "$ABSOLUTE_OUTPUT" "$FULLREQUEST" if ! sha256sum -q -C "$tmpdir/sha256sum" -- "$FULLREQUEST"; then # Don't leave behind a file that didn't pass a cryptographic check. if [ -n "$continue" ]; then # TODO: Check if upstream release description changed, if so, start over. echo "error: Deleting corrupted output file: $OUTPUT" 2>&1 rm -f -- "$OUTPUT" fi exit 1 fi) rm -rf -- "$tmpdir/check" # Move the file to the final destination if not already. if [ -z "$continue" ]; then if [ -z "$output" ]; then (cd "$OUTPUTDIR" && mkdir -p -- "$(dirname -- "$REQUESTFINAL")") fi cp -T -- "$OUTPUT" "$FINAL" rm -rf -- "$tmpdir/download" fi } if $release; then MIRRORS=$(tix-vars "$tmpdir/release.sh" BUILD_FILE) request "$BUILD_FILE" "" "$(basename -- "$BUILD_FILE")" fi # TODO: --source, --source-full # TODO: --binutils, --gcc, --libstdc++ # Fetch each of the specified signed files from the mirror. for REQUEST; do if $package; then REQUEST="$REQUEST.tix.tar.xz" REQUESTDIR="repository/$MACHINE-sortix/" elif $repository_metadata; then REQUESTDIR="repository/$MACHINE-sortix/" elif $boot; then REQUEST="$REQUEST" REQUESTDIR="$MACHINE/boot/" elif $initrd; then REQUEST="$REQUEST.tar.xz" REQUESTDIR="$MACHINE/boot/" elif $patch; then REQUEST="$REQUEST.patch" REQUESTDIR="patches/" elif $normalize; then REQUEST="$REQUEST.normalize" REQUESTDIR="patches/" elif $toolchain; then REQUESTDIR="toolchain/" else REQUESTDIR="" fi request "$REQUEST" "$REQUESTDIR" done