#!/bin/bash
# Copyright 2010 Novell, Inc.
# Author: Peter Bowen <pzb@novell.com> as a work made for hire.
#
# This work is licensed under the 
# Creative Commons Attribution-ShareAlike 3.0 Unported License. 
# To view a copy of this license, visit 
# http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to 
# Creative Commons, 171 Second Street, Suite 300, 
# San Francisco, California, 94105, USA.
#

REQFILE=/etc/sces/instance-requirements

##private function: cleanup
function cleanup()
{
    if [ -n "$PDIR" -a -d "$PDIR" ]; then
        umount "$PDIR" &>/dev/null
        rm -rf "$PDIR" &>/dev/null
    fi
}
trap cleanup EXIT

##function: ec2-instance-data
function ec2-instance-data() {
  local ver="latest"
  [ "$2" ] && ver="$2"
  curl --retry 3 --retry-delay 0 --silent --fail "http://169.254.169.254/$ver/$1
"
  [ $? -eq 0 ] && echo
}

##function: ec2-meta-data
function ec2-meta-data() {
  ec2-instance-data "meta-data/$1" "$2"
}

##function: ec2-b64
function ec2-b64() {
  T=$(echo "$1" | $base64 -d)
  ec2-instance-data "$T" "$2"
}

##function: ec2-xd
function ec2-xd()
{
    if [ -n "$XD_FILE" -a -s "$XD_FILE" ]; then
        cat "$XD_FILE"
    fi
    XD_FILE=$(mktemp)
    JSP=$(type -p sces-json)
    if [ -z "$JSP" ]; then
        return 1
    fi
    XD=$(ec2-b64 'ZHluYW1pYy9pbnN0YW5jZS1pZGVudGl0eS9kb2N1bWVudAo=')
    [ $? -ne 0 ] && return 1
    echo "$XD" | sces-json | tee "$XD_FILE"
}

##function: xd-key()
function xd-key() {
    TK="$1"
    if [ -z "$TK" ]; then
        return 1
    fi
    TKL=${#TK}
    ec2-xd | while read -r LINE; do
        KEY="${LINE% : *}"
        VALUE="${LINE#* : }"
        # Note: null != "null"; we are checking for the version without quotes
        if [ "$VALUE" == "null" ]; then
            continue
        fi
        if [ "${KEY:0:$TKL}" != "$TK" ]; then
            continue
        fi
        KEYR="${KEY:$TKL}"
        if [ -z "$KEYR" ]; then
            echo "$VALUE" | sed -e 's/^"//;s/"$//'
        else
            # This handles array matches
            echo "$KEYR" | grep -E -q '^/[[:digit:]]+$'
            [ $? -eq 0 ] && echo "$VALUE" | sed -e 's/^"//;s/"$//'
        fi
    done
}

##function: ec2-region
function ec2-region() {
    xd-key region
}

##function: ec2-instance-type
function ec2-instance-type() {
    xd-key instanceType
}

##function: ec2-devpay-codes
function ec2-devpay-codes() {
    xd-key devpayProductCodes
}

##function: ec2-account-id
function ec2-account-id() {
    xd-key accountId
}

##function ec2-arch
function ec2-arch() {
    xd-key architecture

}

##function: ec2-machine-id
function ec2-machine-id() {
    local 1=$(xd-key imageId)
    local r=$(ec2-region)
    echo "$r/$l"
}

##function: ec2-kernel-id
function ec2-kernel-id() {
    local l=$(xd-key kernelId)
    if [ -z "$l" ]; then
        echo "none"
        return
    fi
    local r=$(ec2-region)
    echo "$r/$l"
}

##function: ec2-ramdisk-id
function ec2-ramdisk-id() {
    local l=$(xd ramdiskId)
    if [ -z "$l" ]; then
        echo "none"
        return
    fi
    local r=$(ec2-region)
    echo "$r/$l"
}

##function: ec2-billing-codes
function ec2-billing-codes() {
    xd-key billingProducts
}

##function: ec2-instance-id
function ec2-instance-id() {
    local l=$(xd-key instanceId)
    local r=$(ec2-region)
    echo "$r/$l"
}

##function: getkey
function getkey() {
    local s='[[:space:]]*'
    sed -n -r -e "s/^${s}$1${s}=${s}(.*)/\1/pi" "$2"
}

##function: getsig
function getsig() {
    awk 'f == 1 { print  }; $0 == "---- BEGIN SIGNATURE ----" { f = 1 }' "$1" | $base64 -d
}

##function: getcontent
function getcontent() {
    awk '$0 == "---- BEGIN SIGNATURE ----" { exit } { print }' "$1"
}

##function: entitlements 
function entitlements() {
    local tf=/etc/sces/validation.token
    local pk=/etc/sces/validation.pubkey
    local private_tf=$(mktemp)
    local private_pk=$(mktemp)
    if [ ! -f $tf -o ! -s $tf ]; then
        return 1
    fi
    cat "$tf" > "$private_tf"
    if [ ! -f $pk -o ! -s $pk ]; then
        return 2
    fi
    cat "$pk" > "$private_pk"

    local h=$(head -n 1 "$private_tf")
    if [ "$h" != "[SUSE Cloud Validated Entitlements]" ]; then
        return 10
    fi
    
    local INI=$(mktemp)
    local SIG=$(mktemp)
    getsig $private_tf > $SIG
    getcontent $private_tf > $INI
    if [ ! -f $INI -o ! -s $INI ]; then
        return 3
    fi
    if [ ! -f $SIG -o ! -s $SIG ]; then
        return 5
    fi
    local VERSTATE=$(openssl dgst -sha1 -verify $private_pk -signature $SIG $INI 2>&1)
    if [ "$VERSTATE" != "Verified OK" ]; then
        return 6
    fi
    if [ $(getkey version $INI) != "2010-08-29" ]; then
        return 4
    fi
    if [ $(getkey instance $INI) != $(ec2-instance-id) ]; then
        return 7
    fi

    # We have a signed license token that is for our instance!!!

    getkey entitlements $INI | sed -e 's/ /\n/g'
    return 0
}

function product_cert() {
    local pc="$1"
    if [ -z "$products_pubkey" -o ! -s "$products_pubkey" ]; then
        return 1
    fi
    if [ ! -f "$pc" -o ! -s "$pc" ]; then
        return 2
    fi
    local private_pc=$(mktemp)
    cat "$pc" > "$private_pc"

    local h=$(head -n 1 "$private_pc")
    if [ "$h" != '[SUSE Cloud Product Token]' ]; then
        return 3
    fi

    local INI=$(mktemp)
    local SIG=$(mktemp)
    getsig $private_pc > $SIG
    getcontent $private_pc > $INI
    if [ ! -f $INI -o ! -s $INI ]; then
        return 3
    fi
    if [ ! -f $SIG -o ! -s $SIG ]; then
        return 5
    fi
    local VERSTATE=$(openssl dgst -sha1 -verify $products_pubkey -signature $SIG $INI 2>&1)
    if [ "$VERSTATE" != "Verified OK" ]; then
        return 6
    fi
    if [ $(getkey version $INI) != "2010-08-29" ]; then
        return 4
    fi

    # For now, we only check that the signature is valid
    # In the future, we will implement rules that allow for restricting the product certificate

    getkey products $INI | sed -e 's/ /\n/g'

    return 0
}



function itemmatch() {
    local type="$1"
    local value="$2"

    case $type in
        product)
            [ -e "$PR/$value" ]
            return
            ;;
        entitlement)
            [ -e "$VP/$value" ]
            return
            ;;
        billingproduct)
            [ -e "$BP/$value" ]
            return
            ;;
        devpayproduct)
            [ -e "$DP/$value" ]
            return
            ;;
        certproduct)
            [ -e "$CP/$value" ]
            return
            ;;
        accountid)
            [ "$(ec2-account-id)" == "$value" ]
            return
            ;;
        machine)
            [ "$(ec2-machine-id)" == "$value" ]
            return
            ;;
        ramdisk)
            [ "$(ec2-ramdisk-id)" == "$value" ]
            return
            ;;
        kernel)
            [ "$(ec2-kernel-id)" == "$value" ]
            return
            ;;
        instancetype)
            [ "$(ec2-instance-type)" == "$value" ]
            return
            ;;
        region)
            [ "$(ec2-region)" == "$value" ]
            return
            ;;
        arch)
            [ "$(ec2-arch)" == "$value" ]
            return
            ;;
        *)
            return 1
            ;;
    esac
}

function queue_delayed() {
    echo "$1" >> $DELAYED_COMMANDS
}

function do_action_print() {
    printf "$1\n" "$2"
}

function do_action_log() {
    printf "$1" "$2" | logger -t sces
}

function do_action_shutdown() {
    queue_delayed "shutdown -r now"
}

# No config, do nothing
if [ ! -e "$REQFILE" ]; then
    exit 0
fi

# This tries to get our own namespace and then rexecs us in our own namespace
if [ "$1" == "--postexec" ]; then
    [ -f "$2" ] && rm -f "$2"
else
    unshare=$(type -p unshare)
    scesunshare=$(type -p sces-unshare)
    perl=$(type -p perl)
    # Try to get our own namespace
    EXECCHK=$(mktemp /dev/shm/.sces-execchk.XXXXXXXXXX)
    [ -f "$EXECCHK" ] || EXECCHK=$(mktemp)
    [ -f "$EXECCHK" ] || unset EXECCHK
    if [ "$unshare" ]; then
        unshare -m -- $0 --postexec "$EXECCHK"
        [ -n "$EXECCHK" -a ! -f "$EXECCHK" ] && exit 0
    fi
    if [ "$scesunshare" ]; then
        sces-unshare -m -- $0 --postexec "$EXECCHK"
        [ -n "$EXECCHK" -a ! -f "$EXECCHK" ] && exit 0
    fi
    if [ "$perl" ]; then
        perl -e 'require "syscall.ph"; require "sched.ph"; $r=syscall(&SYS_unshare, &CLONE_NEWNS);exec(join(" ",@ARGV)) if ($r == 0);' "$0" --postexec "$EXECCHK"
        [ -n "EXECCHK" -a ! -f "$EXECCHK" ] && exit 0
    fi
    [ -f "$EXECCHK" ] && rm -f "$EXECCHK"
fi

PDIR=$(mktemp -d /dev/shm/.mnt.XXXXXXXXXX)
if [ ! -d "$PDIR" ]; then
    exit 1
fi
chmod 0700 "$PDIR"

# Try to mount a tmpfs over our tmpdir
# If unshare worked and we can mount this, then everything in here 
# will be hidden
mount -n -t tmpfs tmpfs "$PDIR" 2>/dev/null

# Use future mktemp calls use our private directory
export TMPDIR="$PDIR"

base64=$(type -p base64)
if [ -z "$base64" ]; then
    base64="openssl base64"
fi

IR=$(mktemp)
cat "$REQFILE" > "$IR"

S='[[:space:]]+'

# DevPay products
DEVPAY=$(mktemp -d)
# Billing products
BILLING=$(mktemp -d)

# Build our define tree
grep -E -i "^DEFINE$S" "$IR" | while read token type code product; do
    ltype=$(echo "$type" | tr '[:upper:]' '[:lower:]')
    lcode=$(echo "$code" | tr '[:upper:]' '[:lower:]')
    case "$ltype" in
        devpay) 
            echo "$product" >> "$DEVPAY/$lcode"
            ;;
        billing)
            echo "$product" >> "$BILLING/$lcode"
            ;;
        *)
            echo "Unknown code type: $ltype" >&2
            ;;
    esac
done

# Build our action tree
ACTIONS=$(mktemp -d)
grep -E -i "^ACTION$S" "$IR" | while read token name args; do
    echo "$args" >> "$ACTIONS/$name"
done

PR=$(mktemp -d) # All products
VP=$(mktemp -d) # Entitlements
DP=$(mktemp -d) # DevPay products
BP=$(mktemp -d) # Product code products
CP=$(mktemp -d) # Local token products
# Get our list of verified products
entitlements | while read V; do
    touch "$VP/$V"
    touch "$PR/$V"
done

# Build our local (unverified) products
ec2-devpay-codes | while read code; do
    lcode=$(echo "$code" | tr '[:upper:]' '[:lower:]')
    if [ -f "$DEVPAY/$lcode" ]; then
        cat "$DEVPAY/$lcode" | while read prod; do
            touch "$DP/$prod"
            touch "$PR/$prod"
        done
    fi
done

ec2-billing-codes | while read code; do
    lcode=$(echo "$code" | tr '[:upper:]' '[:lower:]')
    if [ -f "$BILLING/$lcode" ]; then
        cat "$BILLING/$lcode" | while read prod; do
            touch "$BR/$prod"
            touch "$PR/$prod"
        done
    fi
done

if [ -d /etc/sces/products -a -s /etc/sces/product-token.pubkey ]; then
    products_pubkey=$(mktemp)
    cat /etc/sces/product-token.pubkey > $products_pubkey
    ls /etc/sces/products | while read prodcert; do
        product_cert "/etc/sces/products/$prodcert" | while read product; do
            touch "$CP/$prod"
            touch "$PR/$prod"
        done
    done
fi

ACTIONLIST=$(mktemp -d)

grep -E -i "^(REQUIRE|CONFLICT)$S" "$IR" | while read token type value action param; do
    declare -a values
    values=($(echo "$value" | sed -e 's/,/ /g'))
    alen=${#values[@]}
    ltoken=$(echo "$token" | tr '[:upper:]' '[:lower:]')
    ltype=$(echo "$type" | tr '[:upper:]' '[:lower:]')
    doaction=1

    # First figure out if the rule matches
    case $ltoken in
        require)
            for (( i=0; i < alen; i++ )); do
                itemmatch $ltype "${values[$i]}"
                if [ $? -eq 0 ];then
                    doaction=0
                    break
                fi
            done
            ;;
        conflicts)
            for (( i=0; i < alen; i++ )); do
                itemmatch $ltype "${values[$i]}"
                if [ $? -ne 0 ];then
                    doaction=0
                    break
                fi
            done
            ;;
        *)
            doaction=0
            ;;
    esac

    if [ $doaction -eq 1 ]; then
        echo "$param" >> "$ACTIONLIST/$action"
    fi 

done

DELAYED_COMMANDS=$(mktemp)
ls "$ACTIONLIST/" | while read act; do
    if [ ! -f "$ACTIONS/$act" ]; then
        echo "Invalid action $act"
        continue
    fi 
    cat "$ACTIONLIST/$act" | while read param2; do
        cat "$ACTIONS/$act" | while read cmd param1; do
            if [ "$(type -t do_action_$cmd)" != "function" ]; then
                echo "Invalid command $cmd in action $act"
                continue
            fi
            do_action_$cmd "$param1" "$param2"
        done
    done
done

if [ -s "$DELAYED_COMMANDS" ]; then
    echo "Need to run the following commands:"
    cat "$DELAYED_COMMANDS"
fi
exit 0
