#!/bin/sh

# make sure we exit on error
set -e

# Sanitise environment
while read -r env ; do
    case "$env" in
	'') continue ;;
	OPTIND) ;; # Dash croaks when unsetting OPTIND. See #985478
	PATH|PWD|TERM) ;;
	LANGUAGE|LANG|LC_*) ;;
	DEB_*|DEBIAN_*|DEBCONF_*) ;;
	DPKG_FORCE) ;;
	UCF_*) ;;
	*) unset "$env" ;;
    esac
done<<EOF
$(awk 'BEGIN{for(v in ENVIRON) print v}')
EOF

# set the version and revision
progname="$(basename "$0")"
pversion='3.0052'

######################################################################
########                                                     #########
########              Utility functions                      #########
########                                                     #########
######################################################################
for libdir in "$(dirname "$0")" /usr/share/ucf ; do
    [ -f "${libdir}/ucf_library.sh" ] && \
	. "${libdir}/ucf_library.sh" && \
	break
done

# Usage: get_file_metadate file_name
get_file_metadata()
{
    if [ -e "$1" ]; then
        # get file modification date without the nanoseconds and timezone info
        moddate="$(date +"%F %T" --date "$(stat --format '@%Y' "$1")")"
        # print file_name user.group permissions above_date
        stat --format "%n %U.%G 0%a $moddate" "$1"
    else
        echo "/dev/null"
    fi
}

# Runs the diff command with approrpiate arguments
# Usage run_diff diff|sdiff diff_opts old_file new_file
run_diff()
{
    local diff_cmd diff_opt old_file new_file old_file_label new_file_label out
    diff_cmd="$1"
    diff_opt="$2"
    old_file="$3"
    new_file="$4"

    old_file_label="$(get_file_metadata "$old_file")"
    new_file_label="$(get_file_metadata "$new_file")"

    [ -e "$old_file" ] || old_file=/dev/null
    [ -e "$new_file" ] || new_file=/dev/null

    if [ "$diff_cmd" = "diff" ] ; then
      diff "$diff_opt" --label "$old_file_label" "$old_file" \
            --label "$new_file_label" "$new_file" || true
    elif [ "$diff_cmd" = "sdiff" ] ; then
      # unfortunatelly the sdiff command does not support --label option
      out="$(sdiff "$diff_opt" "$old_file" "$new_file")" || true
      [ -z "$out" ] || printf "Old file: %s\nNew file: %s\n\n%s" \
                               "$old_file_label" "$new_file_label" "$out"
    else
      echo "Unknown diff command: $diff_cmd" >&2
      exit 1
    fi
}

# Use debconf to show the differences
# Usage: show_diff actual_file_differences file_stat_differences
show_diff() {
    if [ -z "$1" ]; then
	DIFF="There are no non-white space differences in the files."
    else
        if  [ 99999 -lt "$(echo "$1" | wc -c | awk '{print $1; }')" ]; then
            DIFF="The differences between the files are too large to display."
        else
	    # Try to convert non-breaking space to current locale
	    nbsp=$(printf '\302\240' | iconv -c --from-code=UTF-8 --to-code=//TRANSLIT)
	    DIFF="$(printf "%s" "$1" | sed "s/ /${nbsp:- }/g" | debconf-escape -e )"
        fi
    fi
    if [ "$DEBCONF_OK" = "YES" ] && [ "$DEBIAN_HAS_FRONTEND" ]; then
	templ=ucf/show_diff
	db_capb escape
	db_subst $templ DIFF "$DIFF"
	db_fset $templ seen false
	db_input critical $templ || true
	db_go || true
	db_get $templ
	# may contain sensitive information, so clear
	# immediatly after use so it is never written
	# to disk
	db_subst $templ DIFF ""
	db_reset $templ
	db_capb
    else
        if [ -z "$my_pager" ]; then
	    echo "$DIFF" | sensible-pager
        else
	    echo "$DIFF" | $my_pager
        fi
    fi
}

usageversion () {
        cat >&2 <<END
Debian GNU/Linux $progname $pversion.
           Copyright (C) 2002-2020 Manoj Srivastava.
	   Copyright (C) 2024- Mark Hindley.
This is free software; see the GNU General Public Licence for copying
conditions.  There is NO warranty.

Usage: $progname  [options] new_file  destination
Options:
     -h,     --help          print this message
     -s foo, --src-dir  foo  Set the src dir (historical md5sums live here)
             --sum-file bar  Force the historical md5sums to be read from
                             this file.  Overrides any setting of --src-dir.
     -d[n], --debug=[n]      Set the Debug level to N. Please note there must
                             be no spaces before the debug level
     -n,     --no-action     Dry run. No action is actually taken.
     -P foo, --package foo   Don't follow dpkg-divert diversions by package foo.
     -v,     --verbose       Make the script verbose
             --three-way     Register this file in the cache, and turn on the
                             diff3 option allowing the merging of maintainer
                             changes into a (potentially modified) local
                             configuration file. )
             --state-dir bar Set the state directory to bar instead of the
                             default '/var/lib/ucf'. Used mostly for testing.
             --debconf-ok    Indicate that it is ok for ucf to use an already
                             running debconf instance for prompting.
             --debconf-template bar
                             Specify an alternate, caller-provided debconf
                             template to use for prompting.
Usage: $progname  -p  destination
     -p,     --purge         Remove any reference to destination from records

By default, the directory the new_file lives in is assumed to be the src-dir,
which is where we look for any historical md5sums.

END

}

######################################################################
########                                                     #########
########        file and hash save/restore functions         #########
########                                                     #########
######################################################################
purge_md5sum () {
    for i in $(/usr/bin/seq 6 -1 0); do
	if [ -e "${statedir}/hashfile.${i}" ]; then
	    if [ "$docmd" = "YES" ]; then
		cp -pf "${statedir}/hashfile.${i}" \
		    "${statedir}/hashfile.$(($i+1))"
	    else
		echo cp -pf "${statedir}/hashfile.${i}" \
                          "${statedir}/hashfile.$(($i+1))"
	    fi
	fi
    done
    if [ -e "$statedir/hashfile" ]; then
	if [ "$docmd" = "YES" ]; then
	    cp -pf "$statedir/hashfile"  "$statedir/hashfile.0"
	else
	    echo cp -pf "$statedir/hashfile"  "$statedir/hashfile.0"
	fi
	if [ "$docmd" = "YES" ]; then
	    if [ "$VERBOSE" ]; then
		echo >&2 "grep -v ${dest_file_bre} $statedir/hashfile"
		grep -v "${dest_file_bre}"  "$statedir/hashfile" >&2 \
		    || true
	    fi
	    grep -v "${dest_file_bre}" "$statedir/hashfile" > \
		"$statedir/hashfile.tmp" || true
	    mv -f "$statedir/hashfile.tmp"  "$statedir/hashfile"
	fi
    fi
    [ "$VERBOSE" ] && echo >&2 "The cache file is $cached_file"
    if [ "$cached_file" ] && [ -f "$statedir/cache/$cached_file" ]; then
	$action rm -f "$statedir/cache/$cached_file"
    fi
}

replace_md5sum () {
    for i in $(/usr/bin/seq 6 -1 0); do
	if [ -e "${statedir}/hashfile.${i}" ]; then
	    if [ "$docmd" = "YES" ]; then
		cp -pf "${statedir}/hashfile.${i}" \
		    "${statedir}/hashfile.$(($i+1))"
	    else
		echo cp -pf "${statedir}/hashfile.${i}" \
		    "${statedir}/hashfile.$(($i+1))"
	    fi
	fi
    done
    if [ -e "$statedir/hashfile" ]; then
	if [ "$docmd" = "YES" ]; then
	    cp -pf "$statedir/hashfile"  "$statedir/hashfile.0"
	else
	    echo cp -pf "$statedir/hashfile"  "$statedir/hashfile.0"
	fi
	if [ "$docmd" = "YES" ]; then
	    if [ "$VERBOSE" ]; then
		echo >&2 "grep -v \"${dest_file_bre}\" \"$statedir/hashfile\""
		grep -v "${dest_file_bre}" "$statedir/hashfile" >&2 || true
		md5sum "$orig_new_file" | sed "s|$orig_new_file|$dest_file|" >&2
	    fi
	    grep -v "${dest_file_bre}" "$statedir/hashfile" > \
		"$statedir/hashfile.tmp" || true
	    md5sum "$orig_new_file" | sed "s|$orig_new_file|$dest_file|" >> \
		"$statedir/hashfile.tmp"
	    mv -f "$statedir/hashfile.tmp"  "$statedir/hashfile"
	else
	    echo "(grep -v \"${dest_file_bre}\" \"$statedir/hashfile\""
	    echo " md5sum \"$orig_new_file\" | sed \"s|$orig_new_file|$dest_file|\"; "
	    echo ") | sort > \"$statedir/hashfile\""
	fi
    else
	if [ "$docmd" = "YES" ]; then
	    md5sum "$orig_new_file" | sed "s|$orig_new_file|$dest_file|"  > \
		"$statedir/hashfile"
	else
	    echo " md5sum \"$orig_new_file\" | sed \"s|$orig_new_file|$dest_file|\" >" \
		"\"$statedir/hashfile\""
	fi
    fi
    file_size=$(stat -c '%s' "$orig_new_file")
    if [ "$THREEWAY" ] || [ "$file_size" -lt 25600 ]; then
	$action cp -pf "$orig_new_file" "$statedir/cache/$cached_file"
    fi
    # cp -pf "$orig_new_file" "$dest_file.${DIST_SUFFIX}"
}

replace_conf_file () {
    # do not mangle $dest_file since it's the one registered in the hashfile
    # or we have been ask to register
    real_file="$dest_file"
    if [ -L "$dest_file" ]; then
	real_file="$(readlink -nf "$dest_file" || :)"
	if [ ! "$real_file" ]; then
	    echo >&2 "$dest_file is a broken symlink!"
	    $action rm -f "$dest_file"
	    real_file="$dest_file"
	fi
    fi
    if [ -e "$real_file" ]; then
	if [ -z "$RETAIN_OLD" ]; then
	    #echo "Saving  ${real_file}.${OLD_SUFFIX},  in case."
	    [ "$VERBOSE" ] &&
		echo >&2 "Not saving ${real_file}, since it was unmodified"
	else
	    $action cp -pf $selinux "${real_file}" "${real_file}.${OLD_SUFFIX}"
	fi
    fi
    if [ -e "${real_file}" ]; then
        # Do not change the permissions and attributes of the destination
        $action cp -f $selinux "$new_file" "${real_file}"
    else
        # No destination file exists
        $action cp -pf $selinux "$new_file" "${real_file}"
    fi
    replace_md5sum
}

######################################################################
########                                                     #########
########              Command line args                      #########
########                                                     #########
######################################################################
#
# Long term variables
#
docmd='YES'
# action='withecho'
action=
selinux=''
DEBUG=0
VERBOSE=''
statedir='/var/lib/ucf'
THREEWAY=
DIST_SUFFIX="ucf-dist"
NEW_SUFFIX="ucf-new"
OLD_SUFFIX="ucf-old"
ERR_SUFFIX="merge-error"

handle_file_args() {
    if [ "$PURGE" = "YES" ]; then
	if [ $# -ne 1 ]; then
	    echo >&2 "*** ERROR: Need exactly one file argument when purging, got $#"
	    echo >&2 ""
	    usageversion
	    exit 2
	fi
	temp_dest_file="$1"
	if [ -e "$temp_dest_file" ]; then
            dest_file=$(vset "$(readlink -q -m "$temp_dest_file")" "The Destination file")
	else
            dest_file=$(vset "$temp_dest_file" "The Destination file")
	fi
    else
	if [ $# -ne 2 ]; then
	    echo >&2 "*** ERROR: Need exactly two file arguments, got $#"
	    echo >&2 ""
	    usageversion
	    exit 2
	fi
	temp_new_file="$1"
	temp_dest_file="$2"

	if [ ! -e "${temp_new_file}" ]; then
	    echo >&2 "Error: The new file ${temp_new_file} does not exist!"
	    exit 1
	fi
	new_file=$(vset "$(readlink -q -m "$temp_new_file")"  "The new file")
	if [ -e "$temp_dest_file" ]; then
            dest_file=$(vset "$(readlink -q -m "$temp_dest_file")" "The Destination file")
	else
            dest_file=$(vset "$temp_dest_file" "The Destination file")
	fi
    fi
}

handle_opts() {
    # Arguments are from getopt(1) in quoted mode.
    eval set --  "$*"
    while [ $# -gt 0 ] ; do
	case "$1" in
	    -h|--help) usageversion;                        exit 0 ;;
	    -n|--no-action) action='echo'; docmd='NO';      shift  ;;
	    -v|--verbose) VERBOSE=1;                        shift  ;;
	    -P|--package)
		opt_package="$2";			       shift 2 ;;
	    -s|--src-dir)
		opt_source_dir="$2";                       shift 2 ;;
	    --sum-file)
		opt_old_mdsum_file="$2";		  shift 2 ;;
	    --state-dir)
		opt_state_dir="$2";                        shift 2 ;;
	    --debconf-template)
		override_template="$2";                    shift 2 ;;
	    -D|-d|--debug|--DEBUG)
		# d has an optional argument. As we are in quoted mode,
		# an empty parameter will be generated if its optional
		# argument is not found.
		case "$2" in
		    "") DEBUG=$(vset 1 "The Debug value");	shift 2 ;;
		    *)  DEBUG=$(vset "$2" "The Debug value"); shift 2 ;;
		esac ;;
            -p|--purge) PURGE=YES;                         shift   ;;
	    --three-way) THREEWAY=YES;                       shift   ;;
	    --debconf-ok) DEBCONF_OK=YES;                    shift   ;;
	    -Z) selinux='-Z';                                shift   ;;
	    --)  shift ; handle_file_args "$@";            return   ;;
	    *) echo >&2 "Internal error!" ; exit 1 ;;
	esac
    done
}

# A separate assignment is essential to ensure getopt(1) error status isn't
# lost.
UCF_OPTS="$(getopt -a -o hs:d::D::npP:Zv -n "$progname" \
		     --long help,src-dir:,sum-file:,dest-dir:,debug::,DEBUG::,no-action,package:,purge,verbose,three-way,debconf-ok,debconf-template:,state-dir: \
		     -- "$@")"

handle_opts "$UCF_OPTS"

######################################################################
########                                                     #########
########              Sanity checking                        #########
########                                                     #########
######################################################################
# Need to run as root, or else the
if test "$(id -u)" != 0; then
    if [ "$docmd" = "YES" ]; then
        echo "$progname: Need to be run as root." >&2
        echo "$progname: Setting up no action mode." >&2
        action='echo'; docmd='NO'
    fi
fi

# Follow dpkg-divert as though we are installed as part of $opt_package
divert_line=$(dpkg-divert --listpackage "$dest_file")
if [ -n "$divert_line" ]; then
   # name of the package or 'LOCAL' for a local diversion
   divert_package="$divert_line"

   if [ "$divert_package" != "$opt_package" ]; then
       dest_file=$(dpkg-divert --truename "$dest_file")
   fi
fi
dest_file_bre="[[:space:]]$(escape_bre "$dest_file")"'$'

######################################################################
########                                                     #########
########              Set Default Values                     #########
########                                                     #########
######################################################################
# Load site defaults and overrides.
if [ -f /etc/ucf.conf ]; then
    . /etc/ucf.conf
fi

# Command line, env variable, config file, or default
if [ "$opt_source_dir" ]; then
    source_dir=$(vset "$opt_source_dir" "The Source directory")
elif [ "$UCF_SOURCE_DIR" ]; then
    source_dir=$(vset "$UCF_SOURCE_DIR" "The Source directory")
elif [ "$conf_source_dir" ]; then
    source_dir=$(vset "$conf_source_dir" "The Source directory")
else
    if [ "$new_file" ]; then
	source_dir=$(vset "$(dirname "$new_file")" "The Source directory")
    else
	source_dir=$(vset "/tmp" "The Source directory")
    fi

fi

if [ "$PAGER" ] && which "$PAGER" >/dev/null 2>&1 ; then
    my_pager="$(which "$PAGER")"
elif [ -s /usr/bin/pager ] &&
     [ "$(readlink -e /usr/bin/pager || :)" ]; then
    my_pager=/usr/bin/pager
elif [ -x /usr/bin/sensible-pager ]; then
    my_pager=/usr/bin/sensible-pager
elif [ -x /bin/more ]; then
    my_pager=/bin/more
else
    my_pager=
fi

# Command line, env variable, config file, or default
if [ "$opt_state_dir" ]; then
    statedir=$(vset "$opt_state_dir" "The State directory")
elif [ "$UCF_STATE_DIR" ]; then
    statedir=$(vset "$UCF_STATE_DIR" "The State directory")
elif [ "$conf_state_dir" ]; then
    statedir=$(vset "$conf_state_dir" "The State directory")
else
    statedir=$(vset '/var/lib/ucf'  "The State directory")
fi

# Handle conffold and conffnew together. See #980996
# Default
force_conff=

# Config file
if [ "$conf_force_conffold" ]; then
    if [ "$conf_force_conffnew" ]; then
	echo >&2 "Error: Only one of conf_force_conffold and conf_force_conffnew should"
	echo >&2 "       be set in the config file"
	exit 1
    fi
    force_conff=$(vset "old" "Force keep file")
elif [ "$conf_force_conffnew" ]; then
    force_conff=$(vset "new" "Force keep file")
fi

# Environment
if [ "$UCF_FORCE_CONFFOLD" ]; then
    if [ "$UCF_FORCE_CONFFNEW" ]; then
	echo >&2 "Error: Only one of UCF_FORCE_CONFFOLD and UCF_FORCE_CONFFNEW should"
	echo >&2 "       be set in the environment"
	exit 1
    fi
    force_conff=$(vset "old" "Force keep file")
elif [ "$UCF_FORCE_CONFFNEW" ]; then
    force_conff=$(vset "new" "Force keep file")
fi

# Env variable, config file, or default
if [ "$UCF_FORCE_CONFFMISS" ]; then
    force_conffmiss=$(vset "$UCF_FORCE_CONFFMISS" "Replace any missing files")
elif [ "$conf_force_conffmiss" ]; then
    force_conffmiss=$(vset "$conf_force_conffmiss" "Replace any missing files")
else
    force_conffmiss=''
fi

# DPKG_FORCE overrides all others: see #925375.
IFS=,
for f in $DPKG_FORCE ; do
    case $f in
	confmiss) force_conffmiss=$(vset 1 "DPKG_FORCE replace missing files") ;;
	confold) force_conff=$(vset "old" "DPKG_FORCE keep file") ;;
	confnew) force_conff=$(vset "new" "DPKG_FORCE keep file") ;;
	# The following override other settings, so break out of loop.
	confdef) unset force_conff
		 [ "$VERBOSE" ] && echo >&2 "DPKG_FORCE keep file default"
		 break ;;
	confask) force_conff=$(vset "ask" "DPKG_FORCE keep file"); break ;;
    esac
done
unset IFS

if [ -n "$opt_old_mdsum_file" ]; then
    old_mdsum_file=$(vset "$opt_old_mdsum_file" "The md5sum is found here")
elif [ "$UCF_OLD_MDSUM_FILE" ]; then
    old_mdsum_file=$(vset "$UCF_OLD_MDSUM_FILE" "The md5sum is found here")
elif [ "$conf_old_mdsum_file" ]; then
    old_mdsum_file=$(vset "$conf_old_mdsum_file" "Replace the old file")
elif [ "${new_file}" ]; then
    old_mdsum_file="$source_dir/$(basename "${new_file}").md5sum"
else
    old_mdsum_file=""
fi

######################################################################
########                                                     #########
########               More Sanity checking                  #########
########                                                     #########
######################################################################

# VERBOSE of 0 is supposed to be the same as not setting VERBOSE
if [ "$VERBOSE" = "0" ]; then
    VERBOSE=''
fi

if [ -e "$statedir/hashfile" ] && [ ! -w "$statedir/hashfile" ]; then
    echo >&2 "ucf: do not have write privilege to the state data"
    if [ "$docmd" = "YES" ]; then
	exit 1
    fi
fi

if [ ! -d "$statedir/cache" ]; then
    $action mkdir -p "$statedir/cache"
fi

# test and see if this file exists in the database
if [ -e "$statedir/hashfile" ]; then
    if [ "$VERBOSE" ]; then
	echo >&2 "The hash file exists"
	echo >&2 "grep ${dest_file_bre} $statedir/hashfile"
	grep "${dest_file_bre}" "$statedir/hashfile" >&2 || true
    fi
    lastsum=$(grep "${dest_file_bre}" "$statedir/hashfile" | \
                   awk '{print $1;}' )
fi

if [ "${new_file}" ]; then
    old_mdsum_dir="$source_dir/$(basename "${new_file}").md5sum.d"
else
    old_mdsum_dir=""
fi

cached_file="$(echo "$dest_file" | tr / :)"
######################################################################
########                                                     #########
########                  Debugging dump                     #########
########                                                     #########
######################################################################

if [ "$DEBUG" -gt 0 ]; then
    cat >&2 <<EOF
The new start file is      \`$new_file\'
The destination is         \`$dest_file\'  (\`$dest_file_bre\')
The history is kept under  \'$source_dir\'
The file may be cached at \'$statedir/cache/$cached_file\'
EOF
    if [ -s "$dest_file" ]; then
	echo "The destination file exists, and has md5sum:"
	md5sum "$dest_file"
    else
	echo "The destination file does not exist."
    fi
    if [ "$lastsum" ]; then
	echo "The old md5sum exists, and is:"
	echo "$lastsum"
    else
	echo "The old md5sum does not exist."
        if [ -d "$old_mdsum_dir" ] || [ -f "$old_mdsum_file" ]; then
            echo "However, there are historical md5sums around."
        fi
    fi
    if [ -e "$new_file" ]; then
	echo "The new file exists, and has md5sum:"
	md5sum "$new_file"
    else
	echo "The new file does not exist."
    fi
    if [ -d "$old_mdsum_dir" ]; then
	echo "The historical md5sum dir $old_mdsum_dir exists"
    elif [ -f "$old_mdsum_file" ]; then
	echo "The historical md5sum file $old_mdsum_file exists"
    else
	echo "Historical md5sums are not available"
    fi
fi

######################################################################
########                                                     #########
########        Short circuit if we are purging              #########
########                                                     #########
######################################################################

if [ "$PURGE" = "YES" ]; then
    [ "$VERBOSE" ] &&
	echo >&2 "Preparing to purge ${dest_file}"
    purge_md5sum
    exit 0
fi

######################################################################
########                                                     #########
########                  DebConf stuff                      #########
########                                                     #########
######################################################################

# Is debconf already running? Kinda tricky, because it will be after the
# confmodule is sourced, so only test before that.
if [ -z "$DEBCONF_ALREADY_RUNNING" ]; then
    if [ "$DEBIAN_HAS_FRONTEND" ]; then
	DEBCONF_ALREADY_RUNNING='YES'
    else
	DEBCONF_ALREADY_RUNNING='NO'
    fi
fi

export DEBCONF_ALREADY_RUNNING

if [ -z "$DEBCONF_OK" ]; then
    if [ "$DEBCONF_ALREADY_RUNNING" = 'YES' ]; then
	DEBCONF_OK='NO'
    else
	DEBCONF_OK='YES'
    fi
fi

# Time to start nagging the users who call ucf without debconf-ok
if [ "$DEBCONF_ALREADY_RUNNING"  = 'YES' ] && [ "$DEBCONF_OK" = NO ]; then
	# Commented out for now, uncomment after a while to begin nagging
	# maintainers to fix their scripts.
	cat \
<<END
*** WARNING: ucf was run from a maintainer script that uses debconf, but
             the script did not pass --debconf-ok to ucf. The maintainer
             script should be fixed to not stop debconf before calling ucf,
             and pass it this parameter. For now, ucf will revert to using
             old-style, non-debconf prompting. Ugh!

             Please inform the package maintainer about this problem.
END
fi

# Start up debconf or at least get the db_* commands available
# Don't check debconf with shellcheck.
# shellcheck source=/dev/null
if [ -e /usr/share/debconf/confmodule ]; then
    if test "$(id -u)" = 0; then
	. /usr/share/debconf/confmodule

	# Load our templates, just in case our template has
	# not been loaded or the Debconf DB lost or corrupted
	# since then, but only if it is OK to use debconf.
        if [ "$DEBCONF_OK" = 'YES' ]; then
            db_x_loadtemplatefile "$(dpkg-query --control-path ucf templates)" ucf
        fi
    else
        echo >&2 "$progname: Not loading confmodule, since we are not running as root."
    fi
    # Only set the title if debconf was not already running.
    # If it was running, then we do not want to clobber the
    # title used for configuring the whole package with debconf.
    if [ "$DEBCONF_ALREADY_RUNNING" = 'NO' ]; then
	if ! db_settitle ucf/title 2>/dev/null; then
      	    # Older debconf that does not support that command.
            if test "$(id -u)" = 0; then
		db_title "Modified configuration file"
            else
                echo >&2 "$progname: Not changing title, since we are not running as root."
            fi
	fi
    fi
fi

######################################################################
########                                                     #########
########                Start Processing                     #########
########                                                     #########
######################################################################

orig_new_file="$new_file"	# Since sometimes we replace the newfile below
newsum=$(md5sum "$new_file" | awk '{print $1}')

######################################################################
########                                                     #########
########               Do the replacement                    #########
########                                                     #########
######################################################################
# Step 1: If we have no record of this file, and dest file
#         does, We need to determine how to initialize the
#         ${old_mdsum_prefix}.old file..
if [ -e "$dest_file" ]; then
    destsum=$(md5sum "$dest_file"  | awk '{print $1}')
    if [ ! "$lastsum" ]; then
#      a: If we have a directory containing historical md5sums of this
#         file in question, we should look and see if the currently
#         installed file matches any of the old md5sums; in which case
#         it can be silently replaced.
	if [ -d "$old_mdsum_dir" ] || [ -f "$old_mdsum_file" ]; then
	    if [ -d "$old_mdsum_dir"  ]; then
		for file in "${old_mdsum_dir}/"*; do
		    oldsum="$(awk '{print $1}' "$file")"
		    if [ "$oldsum" = "$destsum"  ]; then
			if [ "$force_conff" != "old" ]; then
#                           Bingo! replace, set the md5sum, and we are done
			    [ "$VERBOSE" ] &&
				echo >&2 \
				    "Replacing config file $dest_file with new version"
			    replace_conf_file
			    exit 0
			else
			    replace_md5sum
			    cp -pf "$orig_new_file" "$dest_file.${DIST_SUFFIX}"
			    exit 0
			fi
		    fi
		done
	    elif [ -f "$old_mdsum_file" ]; then
		oldsum=$(grep -E "^${destsum}" "$old_mdsum_file" || true)
		if [ "$oldsum" ]; then
#                    Bingo
		    if [ "$force_conff" != old ]; then
			[ "$VERBOSE" ] &&
			    echo >&2 \
				"Replacing config file $dest_file with new version"
			replace_conf_file
			exit 0
		    else
			replace_md5sum
			cp -pf "$orig_new_file" "$dest_file.${DIST_SUFFIX}"
			exit 0
		    fi
		fi
	    fi
#	   Well, nothing matched. We now check to see if the
#	   maintainer has an opinion on how to set the ``md5sum of the
#	   previously installed version'', since we have no way of
#	   determining that automatically. Please note that unless
#	   there are limited number of previously released packages
#	   (like just one), the maintainer is also making a guess at
#	   this point by supplying a historical md5sum default file.
	    [ "$VERBOSE" ] &&
		echo >&2 "Historical md5sums did not match."
	    if [ -d "$old_mdsum_dir"  ]; then
		if [ -e "${old_mdsum_dir}/default" ]; then
		    [ "$VERBOSE" ] &&
			echo >&2 "However, a default entry exists, using it."
		    lastsum="$(awk '{print $1;}' "${old_mdsum_dir}"/default)"
		    do_replace_md5sum=1
		fi
	    elif [ -f "$old_mdsum_file" ]; then
		oldsum=$(grep -E "[[:space:]]default$" "$old_mdsum_file" | \
		    awk '{print $1;}')
		if [ "$oldsum" ]; then
#                   Bingo
		    lastsum=$oldsum
		    do_replace_md5sum=1
		fi
	    fi
	fi

#       At this point, we are almost certain that either the
#       historical record of md5sums is not complete, or the user has
#       changed the configuration file. Rather than guessing and
#       chosing one of the historical md5sums, we fall through to the
#       solution used if there had been no historical md5sums
#       directory/file.
	if [ ! "$lastsum" ]; then
#       b: We do not have a historical list of md5sums, or none
#          matched, and we still need to initialize the
#          ${old_mdsum_prefix}.old file. We can't determine whther or
#          not they made any changes, so we err on the side of caution
#          and ask.
	    [ "$VERBOSE" ] &&
		echo >&2 "No match found, we shall ask."
	    lastsum='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
	fi # the old md5sum file does not exist, and the historical
	   # record failed
    fi # the old md5sum file does not exist (bug))
else  # "$dest_file" does not exist
# Step 2: If destfile does not exist, create it, set the file
#         "${old_mdsum_prefix}.old" to the md5sum of the new file, and we
#         are done
    if [ ! "$lastsum" ]; then
        # Ok, so there is no indication that the package was ever
        # installed on this machine.
	echo >&2 "Creating config file $dest_file with new version"
	replace_conf_file
	exit 0
    elif [ "$lastsum" = "$newsum" ]; then
        # OK, new version of the file is the same as the last version
        # we saw. Since the user apparently has deleted the file,
        # nothing needs be done, unless we have been told differently
        if [ "$force_conffmiss" ]; then
	    echo >&2 "Recreating deleted config file $dest_file with new version, as asked"
	    replace_conf_file
	    exit 0
        else
            echo >&2 "Not replacing deleted config file $dest_file"
        fi

    else
        # OK. New upstream version.
        if [ "$force_conffmiss" ]; then
            # User has said to replace missing files, so we do so, no
            # questions asked.
	    echo >&2 "Recreating deleted config file $dest_file with new version, as asked"
	    replace_conf_file
	    exit 0
        else
            # Even though the user has deleted this file, they should
            # be asked now, unless specified otherwise.
            if [ "$force_conff" != "old" ]; then
                destsum='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
            else
                exit 0
	    fi
        fi
    fi
fi

# Here, the destfile exists.

# step 3: If the old md5sum and the md5sum of the new file
#         do not match, we need to take action.
if [ "$lastsum" = "$newsum" ]; then
    if [ "$force_conff" = 'ask' ]; then
	[ "$VERBOSE" ] &&
	    echo >&2 "Forced to ask even though md5sums match."
	destsum='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    else
	[ "$VERBOSE" ] &&
	    echo >&2 "md5sums match, nothing needs be done."
	if [ "$do_replace_md5sum" ]; then
	    replace_md5sum
	fi
	exit 0;			# Hah. Match. We are done.
    fi
fi
#      a: If the md5sum of the dest file is the same as lastsum, replace the
#         destfile, saying we are replacing old config files
if [ "$destsum" = "$lastsum" ]; then
    if [ "$force_conff" != old ]; then
	echo >&2 "Replacing config file $dest_file with new version"
	replace_conf_file
	exit 0
    else
	replace_md5sum
	cp -pf "$orig_new_file" "$dest_file.${DIST_SUFFIX}"
	exit 0
    fi
else
#      b: If the md5sum of the dest file differs from lastsum, we need to ask
#         the user what action to take.
    if [ "$force_conff" = "new" ]; then
	echo >&2 "Replacing config file $dest_file with new version"
	echo >&2 "since you asked for it."
        if [ "$destsum" = "$newsum" ]; then
            echo >&2 "The new and the old files are identical, AFAICS"
        else
            echo >&2 "The new and the old files are different"
        fi
	replace_conf_file
	exit 0
    fi
    if [ "$force_conff" = "old" ]; then
	replace_md5sum
	cp -pf "$orig_new_file" "$dest_file.${DIST_SUFFIX}"
	exit 0
    fi
#      c: If the destination file is the same as the new maintianer provided one,
#         we need do nothing.
    if [ "$newsum" = "$destsum" ]; then
	[ "$VERBOSE" ] &&
	    echo >&2 "md5sums of the file in place matches, nothing needs be done."
	replace_md5sum
	exit 0;			# Hah. Match. We are done.
    fi

    done='NO'
    while [ "$done" = "NO" ]; do
	if [ "$DEBCONF_OK" = "YES" ] && [ "$DEBIAN_HAS_FRONTEND" ]; then
		# Use debconf to prompt.
		if [ -e "$statedir/cache/$cached_file" ] && [ "$THREEWAY" ]; then
			templ=ucf/changeprompt_threeway
		else
			templ=ucf/changeprompt
		fi
		if [ "$override_template" ]; then
			choices="$(db_metaget $templ Choices-C)"
			choices2="$(db_metaget "$override_template" Choices-C)"
			if [ "$choices" = "$choices2" ]; then
				templ=$override_template
			fi
		fi
		db_fset "$templ" seen false
		db_reset "$templ"
		db_subst "$templ" FILE "$dest_file"
		db_subst "$templ" NEW  "$new_file"
		db_subst "$templ" BASENAME "$(basename "$dest_file")"
		db_input critical "$templ" || true
		if ! db_go; then
			# The current ucf interface does not provide a way for it
			# to tell its caller that the user chose to back up.
			# However, we could get here, if the caller turned on
			# debconf's backup capb. The best thing to do seems to be
			# to ignore requests to back up.
			continue
		fi
		db_get "$templ"
		ANSWER="$RET"
	else
            echo >&2 "Need debconf to interact"
            exit 2
	fi

	case "$ANSWER" in
	    install_new|y|Y|I|i)
		echo >&2 "Replacing config file $dest_file with new version"
		RETAIN_OLD=YES
		replace_conf_file;
		exit 0;
		;;
	    diff|D|d)
		DIFF="$(run_diff diff -uBbwt "$dest_file" "$new_file")"
		show_diff "$DIFF"
		;;
	    sdiff|S|s)
		DIFF="$(run_diff sdiff -BbWt "$dest_file" "$new_file")"
		show_diff "$DIFF"
		;;
	    diff_threeway|3|t|T)
		if [ -e "$statedir/cache/$cached_file" ] && \
		    [ "$THREEWAY" ]; then
                    if [ -e "$dest_file" ]; then
		        DIFF="$(diff3 -L Current -L Older -L New -A \
			    "$dest_file" "$statedir/cache/$cached_file" \
			    "$new_file")"  || true
                    else
                        DIFF="$(diff3 -L Current -L Older -L New -A \
			    /dev/null "$statedir/cache/$cached_file" \
			    "$new_file")"  || true
                    fi
		    show_diff "$DIFF"
		else
		    DIFF="$(run_diff diff -uBbwt "$dest_file" "$new_file")"
		    show_diff "$DIFF"
		fi
		;;
	    merge_threeway|M|m)
		echo >&2 "Merging changes into the new version"
		if [ -e "$statedir/cache/$cached_file" ] && \
		       [ "$THREEWAY" ]; then
		    ret=0
		    diff3 -L Current -L Older -L New -m \
			"$dest_file" "$statedir/cache/$cached_file" \
			"$new_file" > "$dest_file.${NEW_SUFFIX}" || ret=$?
                    case "$ret" in
                        0)
		            new_file="$dest_file.${NEW_SUFFIX}"
		            RETAIN_OLD=YES
		            replace_conf_file
			    rm -f "$dest_file.${NEW_SUFFIX}" # don't need this around no mo'
			    exit 0
                            ;;
                        *)
			    mv "$dest_file.${NEW_SUFFIX}" "$dest_file.${ERR_SUFFIX}"
			    db_subst ucf/conflicts_found dest_file "$dest_file"
			    db_subst ucf/conflicts_found ERR_SUFFIX "${ERR_SUFFIX}"
			    db_input critical ucf/conflicts_found || true
			    db_go || true
			    ;;
                    esac
		else
		    replace_conf_file
		    rm -f "$dest_file.${NEW_SUFFIX}" # don't need this around no mo'
		    exit 0
		fi
		;;
	    shell|Z|z)
                # We explicitly connect STDIN and STDOUT to the
                # script's controlling terminal, so even if STDIN is
                # fed by a pipe, as is the case when run from
                # /usr/bin/debconf, the shell should be fully
                # functional. However, the test for a controlling
                # terminal uses /usr/bin/tty, which consults only
                # STDIN. As far as I can tell, when run from debconf,
                # ucf will _never_ use the current terminal. If the
                # goal is to check for access to a terminal, the test
                # should be for foreground process group membership,
                # not a terminal connected to STDIN (tty -s), and not
                # a terminal it doesn't necessarily own (tty -s
                # </dev/tty). The easiest way do this from a shell is
                # probably with /bin/ps.
                if [ "$(ps -o tpgid= $$)" -eq "$(ps -o pgid= $$)" ]; then
                    export UCF_CONFFILE_OLD="$dest_file"
                    export UCF_CONFFILE_NEW="$new_file"
		    bash >/dev/tty </dev/tty || true
                elif [ -n "$DISPLAY" ]; then
                    x-terminal-emulator || true
                else
                    # Don't know what to do
                    echo >&2 "No terminal, and no DISPLAY set, can't fork shell."
                    sleep 3
                fi
		;;
	    keep_current|n|N|o|O|'')
		replace_md5sum

		cp -pf "$orig_new_file" "$dest_file.${DIST_SUFFIX}"
		exit 0
		;;
	    *)
		if [ "$DEBCONF_OK" = "YES" ]; then
			echo "Error: unknown response from debconf:'$RET'" >&2
			exit 1
		else
			echo
			echo "Please answer with one of the single letters listed." >&2
			echo
		fi
	esac
    done
fi

db_stop

exit 0
