#!/bin/sh

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

set -e

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

VERSION='11+2025.01.30'

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

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
    >&2 printf "%s: " "$NAME"
    >&2 eval_gettext "Warning: unknown DEBIAN_VERSION \$DEBIAN_VERSION. Valid values are from \$DEB_LOWEST_VER_ID to \$DEB_NEXT_VER_ID, inclusive."
    >&2 echo
fi

LIST=
NOHEADING=
STATUSDB_FILE=
EXCEPT=
TYPE=

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

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

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

FOLD="fold -s -w 72"

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
  --except PACKAGES             exempt given binary packages (comma-separated list)
  --type SECURITY_SUPPORT_TYPE  earlyend, ended or limited
  -V, --version                 display version and exit"; echo
}

if [ $? != 0 ] ; then
    >&2 printf "%s: " "$NAME"
    >&2 gettext "Failed to parse the command line parameters"
    >&2 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
            ;;
        --except)
            EXCEPT="$2"
            case "$EXCEPT" in
                *:*)
                    >&2 printf "%s: " "$NAME"
                    >&2 gettext 'E: --except=<package name> does not allow :<arch> suffixes'
                    >&2 echo
                    exit 1
                    ;;
            esac
            shift 2
            ;;
        --type)
            TYPE="$2"
            shift 2
            ;;
        --)
            shift ;
            break
            ;;
        *)
            >&2 printf "%s: " "$NAME"
            >&2 gettext 'E: Internal error'
            >&2 echo
            exit 1
            ;;
    esac
done

case "$TYPE" in
'')
    if [ -z "$LIST" ] ; then
        REPORT="$($0 --type ended --list /usr/share/debian-security-support/security-support-ended.deb"$DEBIAN_VERSION" --status-db "$STATUSDB_FILE" --except "$EXCEPT" $NOHEADING)"
        if [ -n "$REPORT" ]  ; then
            echo "$REPORT"
            echo
        fi
        REPORT="$($0 --type limited --list /usr/share/debian-security-support/security-support-limited --status-db "$STATUSDB_FILE" --except "$EXCEPT" $NOHEADING)"
        if [ -n "$REPORT" ] ; then
            echo "$REPORT"
            echo
        fi
        $0 --type earlyend --list /usr/share/debian-security-support/security-support-ended.deb"$DEBIAN_VERSION" --status-db "$STATUSDB_FILE" --except "$EXCEPT" $NOHEADING
        exit 0
    fi
    >&2 printf "%s: " "$NAME"
    >&2 gettext 'E: Need a --type if --list is given'
    >&2 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
    ;;
*)
    >&2 printf "%s: " "$NAME"
    >&2 eval_gettext "E: Unknown --type '\$TYPE'"
    >&2 echo
    exit 1
    ;;
esac

# exit silently if there's no list
if [ ! -f "$LIST" ] ; then
    exit 0
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 '${Status}\t${binary:Package}\t${Version}\t${Source}\n' |
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"
INTERSECTION_LIST="$TEMPDIR/intersection"
awk -F'\t' '{print $3}' "$INSTALLED_LIST" | LC_ALL=C sort -u >"$LEFT"
PATTERNS=$(grep -vP '^(#|$)' "$LIST" | awk '{print $1}' | paste -sd'|')

LC_ALL=C grep -P -x -e "$PATTERNS" "$LEFT" >"$INTERSECTION_LIST" || true
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
    LINE=$(grep -vP '^(#|$)' "$LIST" | while read pattern rest ; do
            if echo $SRC_NAME | grep -q -P -x -e "$pattern" ; then
                echo "$pattern $rest"
                break
            fi
        done)
    IFS="$(printf '\nx')"
    IFS="${IFS%x}"
    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
        # skip packages that were listed in --except
        case ",$EXCEPT," in
            *,"$BIN_NAME",*) # plain match (e.g., "binutils")
                continue
                ;;
            *,"${BIN_NAME%:*}",*) # match with arch suffix (e.g., "libbinutils:amd64")
                continue
                ;;
        esac
        # for earlyend and ended, check packages actually affected (if TMP_WHEN not null)
        if [ -n "$TMP_WHEN" ] || [ "$TYPE" = limited ] ; 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
    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"
            echo; echo
            gettext \
"Unfortunately, it will be necessary to end security support for some packages before the end of the regular security maintenance life cycle." | $FOLD
            echo; echo
            gettext \
"The following packages found on this system are affected by this:" ; echo
            ;;
        ended)
            gettext \
"Ended security support for one or more packages"
            echo; echo
            gettext \
"Unfortunately, it has been necessary to end security support for some packages before the end of the regular security maintenance life cycle." | $FOLD
            echo; echo
            gettext \
"The following packages found on this system are affected by this:" ; echo
            ;;
        limited)
            gettext \
"Limited security support for one or more packages"
            echo; echo
            gettext \
"Unfortunately, it has been necessary to limit security support for some packages." | $FOLD
            echo; echo
            gettext \
"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
