#!/bin/bash

# This script will make a best-effort attempt at showing modifications
# to package-provided config files on a Debian system.
#
# It's subject to some pretty significant limitations: most notably,
# there's no way to identify all such config files. We approximate the
# answer by looking first at dpkg-managed conffiles, and then hoping
# that most of the time, if maintainer scripts are managing files
# themselves, they're using ucf. So, DO NOT TRUST THIS SCRIPT to find
# everything... but it should help to find most customisation.


# Set this non-empty to see a diff against empty for apparently-deleted
# files; leave it empty for a single 'file deleted' note.
diff_empty=

# Space-separated list of directory *trees* to be searched for package
# files. This is the only means of locating packages that can't be
# installed by apt. Note that we do a recursive search in here *before*
# we ask apt to download the package; don't point it at a stupidly-large
# tree.
local_packages="/var/cache/puppet"



package_version() {
   pkg="$1"
   dpkg-query -W -f='${Version}\n' "$pkg"
}

# I've made no attempt to create a sensible overall ordering; we keep
# files grouped by package within a particular section, then hope that
# most packages won't mix config file types.


#############
# conffiles

package_file() {
   pkg="$1"

   exec 3< <(dpkg-query -W -f='${Version} ${Architecture} ${Status}\n' "$pkg")
   read -u3 version arch status

   if [ "$status" != "install ok installed" -o -z "$version" ]; then
      # Package isn't actually installed; ignore it.
      exit 0
   fi

   basename="${pkg}_${version//:/%3a}_${arch}.deb"
   filename="/var/cache/apt/archives/$basename"

   if [ -f "$filename" ]; then
      echo "$filename"
      exit
   fi

   found="$(find $local_packages -name "$basename" -print -quit)"
   if [ -n "$found" ]; then
      echo "$found"
      exit
   fi

   if [ "$UID" -gt 0 ]; then
      echo "Package ${pkg} (${version}, ${arch}) is not available; need to install, but not root" >&2
      exit 1
   fi
   apt-get -qq --download-only --reinstall install "${pkg}=${version}"

   if [ -f "$filename" ]; then
      echo "$filename"
   else
      echo "Failed to download ${pkg} (${version}, ${arch})" >&2
      exit 1
   fi
}

original_content() {
   pkg="$1"
   file="$2"

   deb="$(package_file "$pkg")"
   if [ "$?" -ne 0 -o -z "$deb" ]; then
      exit 1
   fi

   dpkg-deb --fsys-tarfile "$deb" | tar -x -O ".$file"
}


dpkg-query -W -f='${Conffiles}\n' '*' |
   awk 'OFS="  "{print $2,$1}' |
   md5sum -c 2>/dev/null |
   awk -F': ' '$2 !~ /OK/{print $1}' |
   xargs dpkg -S |
   sort -u |
   awk -F ': ' 'OFS=" "{print $1,$2}' |
   while read pkg file; do
      if [ ! -f "$file" -a -z "$diff_empty" ]; then
         echo "Deleted: $file (from $pkg)"
      else
         content="$(original_content "$pkg" "$file")"
         if [ "$?" -eq 0 ]; then
            echo "package $pkg"
            diff -u --new-file --report-identical-files --label "$pkg $(package_version "$pkg")" <(echo "$content") "$file"
         else
            echo "Failed to load original for $file from $pkg"
         fi
      fi
      echo
   done


#######
# ucf

md5sum -c /var/lib/ucf/hashfile 2>/dev/null |
   awk -F': ' '$2 !~ /OK/{print $1}' |
   xargs ucfq -w |
   sort -t ':' -k 2,1 | uniq |
   awk -F: 'OFS=" " {print $1,$2}' |
   while read file pkg; do
      if [ ! -f "$file" -a -z "$diff_empty" ]; then
         echo "Deleted: $file (from ${pkg:-??})"
      else
         cache="/var/lib/ucf/cache/${file//\//:}"
         if [ -f "$cache" ]; then
            if [ -n "$pkg" ]; then
               echo "package $pkg"
               label="$pkg $(package_version "$pkg")"
            else
               label="original"
            fi
            diff -u --new-file --report-identical-files --label "$label" "$cache" "$file"
         else
            echo "Failed to load original for $file from ${pkg:-??}"
         fi
      fi
      echo
   done
