#!/bin/sh
#---------------------------------------------------------------------
# Description: Script to Upgrade an Ubuntu Core system via the initramfs.
#---------------------------------------------------------------------
# Copyright © 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#---------------------------------------------------------------------

set -e

if [ ! -e "$1" ]; then
    echo "Command file doesn't exist: $1"
    exit 1
fi

WRITABLE_PARTITION="/root/userdata"
USERDATA="$WRITABLE_PARTITION/cache"

WORKDIR=$(mktemp -dq "/tmp/system-image-XXXXXXXXXX")

COMMAND_FILE="$1.applying"
mv "$1" "$COMMAND_FILE"

REMOVE_LIST="$COMMAND_FILE"

echo "Starting image upgrader: $(date)"

# Functions
verify_signature() {
    # $1 => validation keyring name
    # $2 => path to validate

    if [ ! -e "$2" ]; then
        echo "File doesn't exist: $2"
        return 1
    fi

    # Check against the blacklist
    if [ -e "$WORKDIR/blacklist/pubring.gpg" ]; then
        export GNUPGHOME="$WORKDIR/blacklist/"
        if gpg --ignore-time-conflict --verify "$2" >/dev/null 2>&1; then
            echo "File signed by a blacklisted key: $2"
            return 1
        fi
    fi

    # Check against the keyring
    export GNUPGHOME="$WORKDIR/$1/"
    if [ ! -e "$GNUPGHOME" ]; then
        echo "Keyring doesn't exist: $1"
        return 1
    fi

    if gpg --ignore-time-conflict --verify "$2" >/dev/null 2>&1; then
        return 0
    fi

    return 1
}

install_keyring() {
    # $1 => full path to tarball
    # $2 => full path to signature

    # Some basic checks
    if [ ! -e "$1" ] || [ ! -e "$2" ]; then
        echo "Missing keyring files: $1 => $2"
        return 1
    fi

    # Unpacking
    TMPDIR=$(mktemp -d)
    cd "$TMPDIR"
    cat "$1" | unxz | tar xf -
    if [ ! -e keyring.json ] || [ ! -e keyring.gpg ]; then
        rm -Rf "$TMPDIR"
        echo "Invalid keyring: $1"
        return 1
    fi

    # Extract the expiry
    keyring_expiry=$(grep "^    \"expiry\": " keyring.json | cut -d: -f2 | sed -e "s/[ \",]//g")
    if [ -n "$keyring_expiry" ] && [ "$keyring_expiry" -lt "$(date +%s)" ]; then
        rm -Rf "$TMPDIR"
        echo "Keyring expired: $1"
        return 1
    fi

    # Extract the keyring type
    keyring_type=$(grep "^    \"type\": " keyring.json | cut -d: -f2 | sed -e "s/[, \"]//g")
    if [ -z "$keyring_type" ]; then
        rm -Rf "$TMPDIR"
        echo "Missing keyring type: $1"
        return 1
    fi

    if [ -e "$WORKDIR/$keyring_type" ]; then
        rm -Rf "$TMPDIR"
        echo "Keyring already loaded: $1"
        return 1
    fi

    signer="unknown"
    case "$keyring_type" in
        archive-master)
            signer=""
        ;;

        image-master)
            signer="archive-master"
        ;;

        image-signing|blacklist)
            signer="image-master"
        ;;

        device-signing)
            signer="image-signing"
        ;;
    esac

    if [ -n "$signer" ] && ! verify_signature "$signer" "$2"; then
        rm -Rf "$TMPDIR"
        echo "Invalid signature: $1"
        return 1
    fi

    mkdir "$WORKDIR/$keyring_type"
    chmod 700 "$WORKDIR/$keyring_type"
    mv "$TMPDIR/keyring.gpg" "$WORKDIR/$keyring_type/pubring.gpg"
    chmod 600 "$WORKDIR/$keyring_type/pubring.gpg"
    chown 0:0 "$WORKDIR/$keyring_type/pubring.gpg"
    rm -Rf "$TMPDIR"
    return 0
}

# Initialize GPG
if [ -e /etc/system-image/archive-master.tar.xz ]; then
    echo "Loading keyring: archive-master.tar.xz"
    install_keyring /etc/system-image/archive-master.tar.xz /etc/system-image/archive-master.tar.xz.asc
fi

# Process the command file
FULL_IMAGE=0
echo "Processing the command file"
while read line
do
    set -- $line
    case "$1" in
        format)
            echo "Formating: $2"
            case "$2" in
                system)
                    FULL_IMAGE=1
                    echo "Unsupported at this time"
                    exit 1
                ;;

                data)
                    echo "Unsupported at this time"
                    exit 1
                ;;

                *)
                    echo "Unknown format target: $2"
                ;;
            esac
        ;;

        load_keyring)
            if [ ! -e "$USERDATA/$2" ] || [ ! -e "$USERDATA/$3" ]; then
                echo "Skipping missing file: $2"
                continue
            fi
            REMOVE_LIST="$REMOVE_LIST $USERDATA/$2 $USERDATA/$3"

            echo "Loading keyring: $2"
            install_keyring "$USERDATA/$2" "$USERDATA/$3"

            if [ -e "$WORKDIR/image-master/pubring.gpg" ] && \
               [ ! -e "$WORKDIR/blacklist/pubring.gpg" ] && \
               [ -e /root/var/lib/system-image/blacklist.tar.xz ] && \
               [ -e /root/var/lib/system-image/blacklist.tar.xz.asc ]; then
                echo "Loading blacklist keyring"
                install_keyring /root/var/lib/system-image/blacklist.tar.xz /root/var/lib/system-image/blacklist.tar.xz.asc
            fi
        ;;

        mount)
            case "$2" in
                system)
                    mkdir -p /cache/system
                    mount -o bind /root/ /cache/system/
                ;;

                *)
                    echo "Unknown mount target: $2"
                ;;
            esac
        ;;

        unmount)
            case "$2" in
                system)
                    umount /cache/system
                    rmdir /cache/system
                ;;

                *)
                    echo "Unknown mount target: $2"
                ;;
            esac
        ;;

        update)
            if [ ! -e "$USERDATA/$2" ] || [ ! -e "$USERDATA/$3" ]; then
                echo "Skipping missing file: $2"
                continue
            fi

            REMOVE_LIST="$REMOVE_LIST $USERDATA/$2 $USERDATA/$3"

            if ! verify_signature device-signing "$USERDATA/$3" && \
               ! verify_signature image-signing "$USERDATA/$3"; then
                echo "Invalid signature"
                continue
            fi

            echo "Applying update: $2"
            cd /cache

            # Start by removing any file listed in "removed"
            if [ "$FULL_IMAGE" != "1" ]; then
                cat "$USERDATA/$2" | unxz | tar xf - removed >/dev/null 2>&1 || true
                if [ -e removed ]; then
                    while read file; do
                        rm -Rf "$file"
                    done < removed
                fi
                rm -f removed
            fi

            # Unpack everything else on top of the system partition
            cat "$USERDATA/$2" | unxz | tar xf -
            rm -f removed
        ;;

        *)
            echo "Unknown command: $1"
        ;;
    esac
done < "$COMMAND_FILE"

# Remove the update files
for file in "$REMOVE_LIST"; do
    rm "$file"
done

touch /root/.last_update || true
rm -Rf "$WORKDIR"
sync
echo "Done upgrading: $(date)"
