#!/bin/sh
#
# check_debian_iso, copyright 2011 Thomas Schmitt <scdbackup@gmx.net>
# License:   GPLv2 or later
# Tested on: Little-endian GNU/Linux with bash
#            Little-endian FreeBSD-8 with sh and "md5 -q"
#            Little-endian Solaris 5.11 with ksh93
#            Big-endian    GNU/Linux with bash

prog=`basename "$0"`

usage() {
  echo "Usage: $prog Checksum_file [U:]Item [Image_file] [Checksum_command]" >&2
  echo "Reads the checksum of a Debian installation image from Checksum_file" >&2
  echo "and compares it with the ISO 9660 image in Image_file. Suitable" >&2
  echo "for verifying optical media, because trailing garbage is ignored." >&2
  echo "The Item in the Checksum_file is depicted either by its complete" >&2
  echo "file name (e.g. debian-6.0.3-amd64-CD-1.iso) or by a text piece" >&2
  echo "between '-' and '.iso' in the file name. The first match is used." >&2
  echo "Text pieces for debian-update images must be prefixed by 'U:'." >&2
  echo "If no Image_file is given, then the item file name is used instead." >&2
  echo "Checksum_command is normally deduced from Checksum_file name." >&2
  echo "It must read data from standard input and its first word written" >&2
  echo "to standard output must be the checksum. Default commands are" >&2
  echo "md5sum, sha1sum, sha256sum, sha512sum." >&2
  echo "Examples:" >&2
  echo "  $prog  MD5SUMS  debian-6.0.3-amd64-netinst.iso" >&2
  echo "  $prog  MD5SUMS  netinst" >&2
  echo "  $prog  MD5SUMS  debian-6.0.3-amd64-DVD-1.iso  /dev/dvd" >&2
  echo "  $prog  MD5SUMS  1  /dev/dvd" >&2
  echo "  $prog  MD5SUMS  2  /dev/dvd" >&2
  echo "  $prog  MD5SUMS  U:1  /dev/dvd" >&2
  echo "  $prog  MD5SUMS  kde-CD-1  /dev/cdrom" >&2
  echo "  $prog  SHA512SUMS  businesscard  /dev/cdrom" >&2
  echo "  $prog  MD5SUMS  1 /dev/cd0 'md5 -q'" >&2
}

if test -z "$1" -o "$1" = "-h" -o "$1" = "--help" -o -z "$2"
then
  usage
  exit 1
fi
sums="$1"
vol="$2"
file="$3"

checksummer=md5sum
if test -n "$4"
then
  checksummer="$4"
else
  base=`basename "$sums"`
  if test "$base" = "SHA1SUMS"
  then
    checksummer=sha1sum
  elif test "$base" = "SHA256SUMS"
  then
    checksummer=sha256sum
  elif test "$base" = "SHA512SUMS"
  then
    checksummer=sha512sum
  fi
fi

update=""
update_v="-v"
use_fgrep=""
if echo "$vol" | grep '^debian-.*\.iso$' >/dev/null
then
  use_fgrep=1
elif echo "$vol" | grep '^U:' >/dev/null
then
  update=" update"
  update_v=""
  vol=`echo "$vol" | sed -e 's/^U://'`
fi

if test -n "$use_fgrep"
then
  line_from_list=`fgrep "$vol" "$sums" | head -1`
else
  line_from_list=`grep '.*-'"$vol"'\.iso$' "$sums" | grep $update_v "update" | head -1`
fi
sum_from_list=`echo "$line_from_list" | awk '{print $1}'`
name_from_list=`echo "$line_from_list" | awk '{print $2}'`

if test -z "$sum_from_list"
then
  if test -n "$use_fgrep"
  then
    echo "Could not find item '${vol}' in '$sums'" >&2
  else
    echo "Could not find$update item '.*-${vol}.iso' in '$sums'" >&2
  fi
  exit 4
fi

if test -z "$file"
then
  file="$name_from_list"
fi

# Logical block size is assumed with 2048 bytes. Neither genisoimage
# nor xorriso produce other sizes, and even the Linux kernel seems to
# have this size hardcoded.

# At byte 16 * 2048 starts the Primary Volume Descriptor (superblock)
# of the image. The magic number values should be ECMA-119 Volume
# Descriptor Type 0x01 and Standard Identifier "CD001".
# The way how these 6 bytes group to 16-bit words indicates the byte
# sex (endianness) of the local machine. The ECMA-119 Volume Space Size
# is stored as little-endian 32-bit number at PVD byte 80, and as big-endian
# 32-bit number at PVD byte 84.

# od -d is used because it guarantees unsigned integer of predictable
# size. Formats -i and -l depend on sizeof(int).

magic=`(dd if="$file" bs=2048 skip=16 count=1 |
        dd bs=1 count=6 | od -x | head -1 | \
        awk '{print $2 " " $3 " " $4}') 2>/dev/null`
if test "$magic" = "4301 3044 3130"
then
  lo=`(dd if="$file" bs=2048 skip=16 count=1 | \
       dd bs=1 skip=80 count=2 | od -d | head -1 | \
       awk '{print $2}') 2>/dev/null`
  hi=`(dd if="$file" bs=2048 skip=16 count=1 | \
       dd bs=1 skip=82 count=2 | od -d | head -1 | \
       awk '{print $2}') 2>/dev/null`
elif test "$magic" = "0143 4430 3031"
then
  lo=`(dd if="$file" bs=2048 skip=16 count=1 | \
       dd bs=1 skip=86 count=2 | od -d | head -1 | \
       awk '{print $2}') 2>/dev/null`
  hi=`(dd if="$file" bs=2048 skip=16 count=1 | \
       dd bs=1 skip=84 count=2 | od -d | head -1 | \
       awk '{print $2}') 2>/dev/null`
elif test -e "$file"
then
  echo "Does not look like an ISO 9660 filesystem: '$file' magic='$magic'" >&2
  exit 2
else
  echo "File not found: '$file'" >&2
  exit 5
fi

# The two 16 bit numbers, which are of the appropriate byte sex,
# get combined to a 32 bit number.
blocks=`expr $lo + $hi '*' 65536`

echo "Piping $blocks blocks of '$file' through '$checksummer'" >&2
echo "to verify checksum list item '$name_from_list'." >&2

sum_from_file=`dd if=$file bs=2048 count=$blocks | $checksummer | head -1 | awk '{print $1}'`

if test "$sum_from_list" = "$sum_from_file"
then
  echo "Ok: '$file' matches$update '$name_from_list' in '$sums'"
else
  echo "Found:     $sum_from_file" >&2
  echo "Expected:  $sum_from_list" >&2
  echo "MISMATCH: '$file' checksum differs from '$name_from_list' in '$sums'"
  exit 3
fi
exit 0



syntax highlighted by Code2HTML, v. 0.9.1