#!/bin/sh

#%# Copyright (C) 2014 Christoph Biedl <debian.axhn@manchmal.in-ulm.de>
#%# License: GPL-2.0-only

set -e

. gettext.sh
export TEXTDOMAIN=debian-security-support

VERSION='2016.05.24'

# Oldest Debian version included in debian-security-support
DEB_LOWEST_VER_ID=7
# Version ID for next Debian stable
DEB_NEXT_VER_ID=9

if [ -z "$DEBIAN_VERSION" ] ; then
    DEBIAN_VERSION="$(cat /etc/debian_version | grep '[0-9.]' | cut -d. -f1)"
    [ "$DEBIAN_VERSION" ] || DEBIAN_VERSION="$DEB_NEXT_VER_ID"
fi

if [ "$DEBIAN_VERSION" -lt "$DEB_LOWEST_VER_ID" ] || [ "$DEBIAN_VERSION" -gt "$DEB_NEXT_VER_ID" ] ; then
    eval_gettext "Unknown DEBIAN_VERSION \$DEBIAN_VERSION. Valid values from \$DEB_LOWEST_VER_ID and \$DEB_NEXT_VER_ID"; echo
    exit 1
fi

LIST=
NOHEADING=
STATUSDB_FILE=
TYPE=

NAME="$(basename "$0")"

TODAY="$(date +"%Y%m%d")"

TEMP=$( \
    getopt \
    --options h,V \
    --long help,list:,no-heading,semaphore:,status-db:,type:,version,Version \
    -n "$NAME" \
    -- "$@"
)

usage()
{
gettext "Usage: $NAME [OPTION]
Options:
  -h, --help                    display this help and exit
  --list FILE                   database of packages under specific support conditions
  --no-heading                  skips printing headlines
  --status-db FILE              database about already reported packages
  --type SECURITY_SUPPORT_TYPE  earlyend, ended or limited
  -V, --version                 display version and exit"; echo
}

if [ $? != 0 ] ; then
    gettext "Failed to parse the command line parameters"; echo
    exit 1
fi

eval set -- "$TEMP"

while true ; do
    case "$1" in
        -V|--version|--Version)
            eval_gettext "\$name version \$VERSION"; echo
            exit 0
            ;;
        -h|--help)
            usage;
            exit 0
            ;;
        --list)
            LIST="$2"
            shift 2
            ;;
        --no-heading)
            NOHEADING='--no-heading'
            shift
            ;;
        --semaphore|--status-db)
            # --semaphore is supported for a transitional period
            STATUSDB_FILE="$2"
            shift 2
            ;;
        --type)
            TYPE="$2"
            shift 2
            ;;
        --)
            shift ;
            break
            ;;
        *)
            gettext 'E: Internal error'; echo
            exit 1
            ;;
    esac
done

case "$TYPE" in
'')
    if [ -z "$LIST" ] ; then
        $0 --type ended --list /usr/share/debian-security-support/security-support-ended.deb"$DEBIAN_VERSION" --status-db "$STATUSDB_FILE" $NOHEADING
        echo
        $0 --type limited --list /usr/share/debian-security-support/security-support-limited --status-db "$STATUSDB_FILE" $NOHEADING
        echo
        $0 --type earlyend --list /usr/share/debian-security-support/security-support-ended.deb"$DEBIAN_VERSION" --status-db "$STATUSDB_FILE" $NOHEADING
        exit 0
    fi
    gettext 'E: Need a --type if --list is given'; echo
    exit 1
    ;;
earlyend)
    [ "$LIST" ] || LIST=/usr/share/debian-security-support/security-support-ended.deb"$DEBIAN_VERSION"
    ;;
ended)
    [ "$LIST" ] || LIST=/usr/share/debian-security-support/security-support-ended.deb"$DEBIAN_VERSION"
    ;;
limited)
    [ "$LIST" ] || LIST=/usr/share/debian-security-support/security-support-limited
    ;;
*)
    eval_gettext "E: Unknown --type '\$TYPE'"; echo
    exit 1
    ;;
esac

# exit silently if there's no list
if [ ! -f "$LIST" ] ; then
    exit 0
fi

# Get dpkg version for dpkg-query --showformat string

DPKG_VERSION="$(
    dpkg-query -f '${Version}' -W dpkg
)"

SHOWFORMAT=
if [ "$DPKG_VERSION" ] && dpkg --compare-versions "$DPKG_VERSION" '<<' 1.16 ; then
    # squeeze, does not support binary:Package
    SHOWFORMAT='${Status}\t${Package}\t${Version}\t${Source}\n'
else
    if [ -z "$DPKG_VERSION" ] ; then
        gettext "E: Cannot detect dpkg version, assuming wheezy or newer"; echo
    fi
    SHOWFORMAT='${Status}\t${binary:Package}\t${Version}\t${Source}\n'
fi


TEMPDIR="$(mktemp --tmpdir --directory debian-security-support.XXXXX)"
trap "rm -rf '$TEMPDIR'" 0

# Get list of installed packages
INSTALLED_LIST="$TEMPDIR/installed"

LC_ALL=C dpkg-query --show --showformat "$SHOWFORMAT" |
awk '($1=="install"){print}' |
awk -F'\t' '{if($4==""){print $2"\t"$3"\t"$2}else{print $2"\t"$3"\t"$4}}' >"$INSTALLED_LIST"

# Create intersection
LEFT="$TEMPDIR/left"
RIGHT="$TEMPDIR/right"
INTERSECTION_LIST="$TEMPDIR/intersection"
awk -F'\t' '{print $3}' "$INSTALLED_LIST" | LC_ALL=C sort -u >"$LEFT"
grep -v '^#' "$LIST" | LC_ALL=C sort | awk '{print $1}' >"$RIGHT"

LC_ALL=C comm -12 "$LEFT" "$RIGHT" >"$INTERSECTION_LIST"
if [ ! -s "$INTERSECTION_LIST" ] ; then
    # nothing to do
    exit 0
fi

TD="$TEMPDIR/$TYPE"
mkdir -p "$TD"

cat "$INTERSECTION_LIST" | while read SRC_NAME ; do
    IFS="$(printf '\nx')"
    IFS="${IFS%x}"
    LINE="$(awk '($1=="'"$SRC_NAME"'"){print}' "$LIST" | head -1)"
    case "$TYPE" in
        earlyend)
            TMP_WHEN="$(echo "$LINE" | awk '{print $3}')"
            if [ $(date -d $TMP_WHEN +"%Y%m%d") -gt $TODAY ] ; then
                ALERT_WHEN=$TMP_WHEN
                ALERT_VERSION="$(echo "$LINE" | awk '{print $2}')"
                ALERT_WHY="$(
                    echo "$LINE" |
                    awk '{
                        $0 = substr ($0, index ($0, $3) + length ($3) + 1);
                        gsub (/^[ \t]+/,"");
                        print
                    }'
                )"
            else
                unset TMP_WHEN
            fi
            ;;
        ended)
            TMP_WHEN="$(echo "$LINE" | awk '{print $3}')"
            if [ $(date -d $TMP_WHEN +"%Y%m%d") -le $TODAY ] ; then
                ALERT_VERSION="$(echo "$LINE" | awk '{print $2}')"
                ALERT_WHEN=$TMP_WHEN
                ALERT_WHY="$(
                    echo "$LINE" |
                    awk '{
                        $0 = substr ($0, index ($0, $3) + length ($3) + 1);
                        gsub (/^[ \t]+/,"");
                        print
                    }'
                )"
            else
                unset TMP_WHEN
            fi
            ;;
        limited)
            ALERT_VERSION=
            ALERT_WHEN=
            ALERT_WHY="$(
                echo "$LINE" |
                awk '{
                    $0 = substr ($0, index ($0, $1) + length ($1) + 1);
                    gsub (/^[ \t]+/,"");
                    print
                }'
            )"
            ;;
    esac
    unset IFS

    awk '($3=="'"$SRC_NAME"'"){print $1" "$2}' "$INSTALLED_LIST" | \
    while read BIN_NAME BIN_VERSION ; do
        # for earlyend and ended, check packages actually affected (if TMP_WHEN not null)
        if [ -n "$TMP_WHEN" ] || [ "$TYPE" = limited ] ; then
            if \
                [ -z "$ALERT_VERSION" ] ||
                [ "$BIN_VERSION" = "$ALERT_VERSION" ] ||
                dpkg --compare-versions "$BIN_VERSION" '<=' "$ALERT_VERSION"
            then
                # need to alert, but check status db first
                TOKEN="$BIN_NAME/$BIN_VERSION"
                if [ "$STATUSDB_FILE" ] && [ -f "$STATUSDB_FILE" ]; then
                    if grep -qFx "$TOKEN" "$STATUSDB_FILE" ; then
                        continue
                    fi
                fi
                echo "$BIN_NAME $BIN_VERSION" >>"$TD/$SRC_NAME.bin"
                echo "$ALERT_VERSION" >"$TD/$SRC_NAME.version"
                echo "$ALERT_WHEN" >"$TD/$SRC_NAME.when"
                echo "$ALERT_WHY" >"$TD/$SRC_NAME.why"
                if [ "$STATUSDB_FILE" ] ; then
                    # add to status db, remove any older entries
                    if [ -f "$STATUSDB_FILE" ]; then
                        TEMPFILE="$(mktemp --tmpdir="$(dirname "$STATUSDB_FILE")")"
                        awk -F/ '($1!="'"$BIN_NAME"'"){print}' \
                            <"$STATUSDB_FILE" >"$TEMPFILE"
                        mv "$TEMPFILE" "$STATUSDB_FILE"
                    fi
                    echo "$TOKEN" >>"$STATUSDB_FILE"
                fi  # maintain status db
            fi # package BIN_NAME's version is not supported
        fi
    done # read binary name and version for matching source name
done # each source package from intersection

if [ $(find "$TD" -type f | wc -l) -eq 0 ] ; then
    # nothing to do
    exit 0
fi

if [ -z "$NOHEADING" ] ; then
    case "$TYPE" in
        earlyend)
            gettext \
"Future end of support for one or more packages

Unfortunately, it will be necessary to end security support for some
packages before the end of the regular security maintenance life cycle.

The following packages found on this system are affected by this:"
            echo
            ;;
        ended)
            gettext \
"Ended security support for one or more packages

Unfortunately, it has been necessary to end security support for some
packages before the end of the regular security maintenance life cycle.

The following packages found on this system are affected by this:"
            echo
            ;;
        limited)
            gettext \
"Limited security support for one or more packages

Unfortunately, it has been necessary to limit security support for some
packages.

The following packages found on this system are affected by this:"
            echo
            ;;
    esac
fi
for f in $(find "$TD" -type f -name '*.bin' | sort) ; do
    SRC_NAME="$(basename "$f" | sed -e 's/\.bin$//')"
    ALERT_VERSION="$(cat "$TD/$SRC_NAME.version")"
    ALERT_WHEN="$(cat "$TD/$SRC_NAME.when")"
    ALERT_WHY="$(cat "$TD/$SRC_NAME.why")"
    echo
    case "$TYPE" in
        earlyend)
            eval_gettext "* Source:\$SRC_NAME, will end on \$ALERT_WHEN"; echo
            ;;
        ended)
            eval_gettext "* Source:\$SRC_NAME, ended on \$ALERT_WHEN at version \$ALERT_VERSION"; echo
            ;;
        limited)
            eval_gettext "* Source:\$SRC_NAME"; echo
            ;;
    esac
    if [ "$ALERT_WHY" ] ; then
        eval_gettext "  Details: \$ALERT_WHY"; echo
    fi
    if [ $(wc -l <"$f") -eq 1 ] ; then
        gettext "  Affected binary package:"; echo
    else
        gettext "  Affected binary packages:"; echo
    fi
    cat "$f" | while read BIN_NAME BIN_VERSION ; do
        eval_gettext "  - \$BIN_NAME (installed version: \$BIN_VERSION)"; echo
    done
done

exit 0
