From 6d2b8f872ac20327017428cf4ae3e65a0a52f77b Mon Sep 17 00:00:00 2001
From: Russ Allbery <rra@debian.org>
Date: Fri, 3 Jan 2014 10:11:41 -0800
Subject: [PATCH] Add support for debconf prompting to update-passwd

If DEBIAN_HAS_FRONTEND is set in the environment, update-passwd
will prompt with debconf for each change and skip changes that the
user declines.  Questions are generated dynamically for each
semantic change, instantiated from shared templates.  The only
exception are GECOS changes, where one question is used for all
GECOS changes for the same user.

update-passwd uses libdebconfclient to do the debconf prompting,
so add a build dependency on that package.

Modify postinst to use debconf prompting instead of manual
prompting if the debconf confmodule is available.
---
 Makefile.in             |   2 +-
 debian/control          |   2 +-
 debian/po/POTFILES.in   |   1 +
 debian/po/templates.pot | 266 +++++++++++++++++++++++++++++++
 debian/postinst         |  43 +++--
 debian/templates        | 229 +++++++++++++++++++++++++++
 man/update-passwd.8     |  15 ++
 update-passwd.c         | 412 ++++++++++++++++++++++++++++++++++++++++--------
 8 files changed, 891 insertions(+), 79 deletions(-)
 create mode 100644 debian/po/POTFILES.in
 create mode 100644 debian/po/templates.pot
 create mode 100644 debian/templates

diff --git a/Makefile.in b/Makefile.in
index 9ba097c..a8208a7 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -36,7 +36,7 @@ install: all
 update-passwd.o: version.h
 
 update-passwd: $(objects)
-	$(CC) $(LDFLAGS) -o $@ $^
+	$(CC) $(LDFLAGS) -o $@ $^ -ldebconfclient
 
 clean:
 	rm -f update-passwd update-passwd.o core
diff --git a/debian/control b/debian/control
index 4d6b370..5ebd6ef 100644
--- a/debian/control
+++ b/debian/control
@@ -3,7 +3,7 @@ Section: admin
 Priority: required
 Maintainer: Colin Watson <cjwatson@debian.org>
 Standards-Version: 3.6.0
-Build-Depends: dpkg-dev (>= 1.15.7~), debhelper (>= 9~), dh-autoreconf, sgmltools-lite, dpkg (>= 1.16.4) | sgml-base (<< 1.26+nmu2), w3m, po4a
+Build-Depends: dpkg-dev (>= 1.15.7~), debhelper (>= 9~), dh-autoreconf, sgmltools-lite, dpkg (>= 1.16.4) | sgml-base (<< 1.26+nmu2), w3m, po4a, libdebconfclient0-dev
 Vcs-Git: git://anonscm.debian.org/users/cjwatson/base-passwd.git
 Vcs-Browser: http://anonscm.debian.org/gitweb/?p=users/cjwatson/base-passwd.git
 
diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in
new file mode 100644
index 0000000..cef83a3
--- /dev/null
+++ b/debian/po/POTFILES.in
@@ -0,0 +1 @@
+[type: gettext/rfc822deb] templates
diff --git a/debian/po/templates.pot b/debian/po/templates.pot
new file mode 100644
index 0000000..06730ae
--- /dev/null
+++ b/debian/po/templates.pot
@@ -0,0 +1,266 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: base-passwd\n"
+"Report-Msgid-Bugs-To: base-passwd@packages.debian.org\n"
+"POT-Creation-Date: 2014-01-05 13:38-0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. Type: boolean
+#. Description
+#: ../templates:1001
+msgid "Do you want to move the user ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#: ../templates:1001 ../templates:2001 ../templates:3001 ../templates:4001
+#: ../templates:5001 ../templates:6001 ../templates:7001 ../templates:8001
+#: ../templates:9001 ../templates:10001 ../templates:11001 ../templates:12001
+msgid ""
+"update-passwd has found a difference between your system accounts and the "
+"current Debian defaults.  It is advisable to allow update-passwd to change "
+"your system; without those changes some packages might not work correctly.  "
+"For more documentation on the Debian account policies, please see /usr/share/"
+"doc/base-passwd/README."
+msgstr ""
+
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#: ../templates:1001 ../templates:2001 ../templates:3001 ../templates:4001
+#: ../templates:5001 ../templates:6001 ../templates:7001 ../templates:8001
+#: ../templates:9001 ../templates:10001 ../templates:11001 ../templates:12001
+msgid "The proposed change is:"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:1001
+msgid "Move user \"${name}\" (${id}) to before the NIS compat \"+\" entry"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#. Type: boolean
+#. Description
+#: ../templates:1001 ../templates:2001 ../templates:3001 ../templates:4001
+#: ../templates:5001 ../templates:6001 ../templates:7001 ../templates:8001
+#: ../templates:9001 ../templates:10001 ../templates:11001 ../templates:12001
+msgid ""
+"If you allow this change, a backup of modified files will be made with the "
+"extension .org, which you can use if necessary to restore the current "
+"settings.  If you do not make this change now, you can make it later with "
+"the update-passwd utility."
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:2001
+msgid "Do you want to move the group ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:2001
+msgid "Move group \"${name}\" (${id}) to before the NIS compat \"+\" entry"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:3001
+msgid "Do you want to add the user ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:3001
+msgid "Add user \"${name}\" (${id})"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:4001
+msgid "Do you want to add the group ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:4001
+msgid "Add group \"${name}\" (${id})"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:5001
+msgid "Do you want to remove the user ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:5001
+msgid "Remove user \"${name}\" (${id})"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:6001
+msgid "Do you want to remove the group ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:6001
+msgid "Remove group \"${name}\" (${id})"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:7001
+msgid "Do you want to change the UID of user ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:7001
+msgid "Change the UID of user \"${name}\" from ${old_uid} to ${new_uid}"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:8001
+msgid "Do you want to change the GID of user ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:8001
+msgid ""
+"Change the GID of user \"${name}\" from ${old_gid} (${old_group}) to "
+"${new_gid} (${new_group})"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:9001
+msgid "Do you want to change the GECOS of user ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:9001
+msgid ""
+"Change the GECOS of user \"${name}\" from \"${old_gecos}\" to "
+"\"${new_gecos}\""
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:10001
+msgid "Do you want to change the home directory of user ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:10001
+msgid ""
+"Change the home directory of user \"${name}\" from ${old_home} to ${new_home}"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:11001
+msgid "Do you want to change the shell of user ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:11001
+msgid "Change the shell of user \"${name}\" from ${old_shell} to ${new_shell}"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:12001
+msgid "Do you want to change the GID of group ${name}?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:12001
+msgid "Change the GID of group \"${name}\" from ${old_gid} to ${new_gid}"
+msgstr ""
diff --git a/debian/postinst b/debian/postinst
index 422fa34..1e64e86 100755
--- a/debian/postinst
+++ b/debian/postinst
@@ -2,6 +2,12 @@
 
 set -e
 
+# Load the debconf confmodule if it is available.  It may not be because this
+# package is essential and therefore can't depend on debconf directly.
+if [ -f /usr/share/debconf/confmodule ]; then
+	. /usr/share/debconf/confmodule
+fi
+
 changes=0
 
 askyesno () {
@@ -77,44 +83,51 @@ fi
 
 tmp=`tempfile`
 if ! update-passwd --dry-run > $tmp ; then
-	cat <<EOF
+	if [ -f /usr/share/debconf/confmodule ] ; then
+		db_version 2.0
+		update-passwd --verbose
+		changes=1
+	else
+		cat <<EOF
 
 update-passwd has found some differences between your system accounts
 and the current Debian defaults. It is advisable to allow update-passwd
 to change your system; without those changes some packages might not work
-correctly.  For more documentation on the Debian account policies please
+correctly.  For more documentation on the Debian account policies, please
 see /usr/share/doc/base-passwd/README.
 
 The list of proposed changes is:
 
 EOF
-	
-	cat $tmp
-	cat <<EOF
+
+		cat $tmp
+		cat <<EOF
 
 It is highly recommended that you allow update-passwd to make these changes
 (a backup file of modified files is made with the extension .org so you can
 always restore the current settings).
 
 EOF
-	askyesno "May I update your system? [Y/n]"
-fi
-
-rm -f $tmp
+		askyesno "May I update your system? [Y/n]"
+	fi
 
-if [ "$a" = "y" ] ; then
-	echo "Okay, I am going to make the necessary updates now"
-	update-passwd --verbose
-	changes=1
-elif [ "$a" = "n" ] ; then
-	cat <<EOF
+	if [ "$a" = "y" ] ; then
+		echo "Okay, I am going to make the necessary updates now"
+		update-passwd --verbose
+		changes=1
+	elif [ "$a" = "n" ] ; then
+		cat <<EOF
 
 Okay, I will not update your system. If you want to make this update later
 please check the update-passwd utility.
 
 EOF
+
+	fi
 fi
 
+rm -f $tmp
+
 if [ "$changes" -gt 0 ] ; then
 	if searchpath nscd; then
 		nscd -i passwd -i group || true
diff --git a/debian/templates b/debian/templates
new file mode 100644
index 0000000..efca6a2
--- /dev/null
+++ b/debian/templates
@@ -0,0 +1,229 @@
+Template: base-passwd/user-move
+Type: boolean
+Default: true
+_Description: Do you want to move the user ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Move user "${name}" (${id}) to before the NIS compat "+" entry
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/group-move
+Type: boolean
+Default: true
+_Description: Do you want to move the group ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Move group "${name}" (${id}) to before the NIS compat "+" entry
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/user-add
+Type: boolean
+Default: true
+_Description: Do you want to add the user ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Add user "${name}" (${id})
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/group-add
+Type: boolean
+Default: true
+_Description: Do you want to add the group ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Add group "${name}" (${id})
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/user-remove
+Type: boolean
+Default: true
+_Description: Do you want to remove the user ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Remove user "${name}" (${id})
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/group-remove
+Type: boolean
+Default: true
+_Description: Do you want to remove the group ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Remove group "${name}" (${id})
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/user-change-uid
+Type: boolean
+Default: true
+_Description: Do you want to change the UID of user ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Change the UID of user "${name}" from ${old_uid} to ${new_uid}
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/user-change-gid
+Type: boolean
+Default: true
+_Description: Do you want to change the GID of user ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Change the GID of user "${name}" from ${old_gid} (${old_group}) to
+ ${new_gid} (${new_group})
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/user-change-gecos
+Type: boolean
+Default: true
+_Description: Do you want to change the GECOS of user ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Change the GECOS of user "${name}" from "${old_gecos}" to "${new_gecos}"
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/user-change-home
+Type: boolean
+Default: true
+_Description: Do you want to change the home directory of user ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Change the home directory of user "${name}" from ${old_home} to
+ ${new_home}
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/user-change-shell
+Type: boolean
+Default: true
+_Description: Do you want to change the shell of user ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Change the shell of user "${name}" from ${old_shell} to ${new_shell}
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
+
+Template: base-passwd/group-change-gid
+Type: boolean
+Default: true
+_Description: Do you want to change the GID of group ${name}?
+ update-passwd has found a difference between your system accounts and the
+ current Debian defaults.  It is advisable to allow update-passwd to
+ change your system; without those changes some packages might not work
+ correctly.  For more documentation on the Debian account policies, please
+ see /usr/share/doc/base-passwd/README.
+ .
+ The proposed change is:
+ .
+ Change the GID of group "${name}" from ${old_gid} to ${new_gid}
+ .
+ If you allow this change, a backup of modified files will be made with
+ the extension .org, which you can use if necessary to restore the
+ current settings.  If you do not make this change now, you can make it
+ later with the update-passwd utility.
diff --git a/man/update-passwd.8 b/man/update-passwd.8
index 05480c4..6d7e1c6 100644
--- a/man/update-passwd.8
+++ b/man/update-passwd.8
@@ -63,6 +63,21 @@ Show a summary of how to use
 .TP
 .BR \-V ,\  \-\-version
 Show the version number
+.SH ENVIRONMENT
+.TP
+DEBIAN_HAS_FRONTEND
+If this environment variable is sent and the
+.B \-\-dry\-run
+flag was not given,
+.B update\-passwd
+uses debconf to prompt for whether to make changes.
+Each proposed change will produce a separate prompt.
+User or group removals, UID or GID changes, and home directory changes
+will be asked with high priority.
+User or group additions and shell changes will be asked with medium
+priority.
+Questions about whether to move entries above the NIS compat inclusion
+entry or whether to change the GECOS of a user are asked at low priority.
 .SH BUGS
 At this moment
 .B update\-passwd
diff --git a/update-passwd.c b/update-passwd.c
index e333157..b7b0579 100644
--- a/update-passwd.c
+++ b/update-passwd.c
@@ -38,6 +38,10 @@
 #include <pwd.h>
 #include <shadow.h>
 #include <grp.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#include <cdebconf/debconfclient.h>
 
 #define DEFAULT_PASSWD_MASTER	"/usr/share/base-passwd/passwd.master"
 #define DEFAULT_GROUP_MASTER	"/usr/share/base-passwd/group.master"
@@ -45,6 +49,8 @@
 #define DEFAULT_SHADOW_SYSTEM	_PATH_SHADOW
 #define DEFAULT_GROUP_SYSTEM	"/etc/group"
 
+#define DEFAULT_DEBCONF_DOMAIN	"system"
+
 #define	WRITE_EXTENSION		".upwd-write"
 #define	BACKUP_EXTENSION	".org"
 
@@ -134,6 +140,30 @@ int		opt_nolock	= 0;
 int		opt_sanity	= 0;
 
 int		flag_dirty	= 0;
+int		flag_debconf	= 0;
+
+const char*	user_domain	= DEFAULT_DEBCONF_DOMAIN;
+const char*	group_domain	= DEFAULT_DEBCONF_DOMAIN;
+
+struct debconfclient*	debconf	= NULL;
+
+/* Abort the program if talking to debconf fails.  Use ret exactly once. */
+#define DEBCONF_CHECK(ret)					\
+    do {							\
+	if ((ret)!=0) {						\
+	    fprintf(stderr, "Debconf interaction failed\n");	\
+	    exit(1);						\
+	}							\
+    } while (0)
+
+/* Wrapper macros around the debconfclient interface that check the return
+ * status and use the global debconf client.  The mechanics of asking the
+ * question and retrieving the answer are handled by the ask_debconf
+ * function below. */
+#define DEBCONF_REGISTER(template, question) \
+    DEBCONF_CHECK(debconf_register(debconf, (template), (question)))
+#define DEBCONF_SUBST(question, var, value) \
+    DEBCONF_CHECK(debconf_subst(debconf, (question), (var), (value)))
 
 
 /* malloc() with out-of-memory checking.
@@ -158,6 +188,24 @@ char* xstrdup(const char *string) {
     return strcpy(xmalloc(strlen(string) + 1), string);
 }
 
+/* asprintf() with out-of-memory checking.  Also fail if formatting fails so
+ * that the caller doesn't have to check any error return.
+ */
+char* xasprintf(const char* fmt, ...) {
+    va_list	args;
+    int		ret;
+    char*	result;
+
+    va_start(args, fmt);
+    ret=vasprintf(&result, fmt, args);
+    va_end(args);
+    if (ret<0) {
+	fprintf(stderr, "Formatting string failed: %s\n", strerror(errno));
+	exit(1);
+    }
+    return result;
+}
+
 /* Create an empty list-entry
  */
 struct _node* create_node() {
@@ -570,6 +618,50 @@ void version() {
 }
 
 
+/* Assuming that we've already queued up a debconf question using REGISTER and
+ * any necessary SUBST, ask the question and return the answer as a boolean
+ * flag.  Aborts the problem on any failure.
+ */
+int ask_debconf(const char* priority, const char* question) {
+    int		ret;
+    const char*	response;
+
+    ret=debconf_input(debconf, priority, question);
+    if (ret==0)
+	ret=debconf_go(debconf);
+    else if (ret==30)
+	ret=0;
+    if (ret==0)
+	ret=debconf_get(debconf, question);
+    if (ret!=0) {
+	fprintf(stderr, "Debconf interaction failed\n");
+	exit(1);
+    }
+    response=debconf->ret(debconf);
+    if (response!=NULL && strcmp(response, "true")==0)
+	return 1;
+    else
+	return 0;
+}
+
+
+/* Escape an arbitrary string for use in a debconf question.  We take the
+ * conservative approach of replacing all non-alphanumeric characters except
+ * hyphen (-) and underscore (_) with underscores.  Returns newly allocated
+ * memory that the caller is responsible for freeing.
+ */
+char* escape_debconf(const char* string) {
+    char*	copy;
+    char*       p;
+
+    copy=xstrdup(string);
+    for (p=copy; *p!='\0'; p++)
+	if (!isalnum((int)*p) && *p!='-' && *p!='_')
+	    *p='_';
+    return copy;
+}
+
+
 /* Check if we need to move any master file entries above NIS compat
  * switching entries ("+").
  */
@@ -587,13 +679,37 @@ void process_moved_entries(const struct _info* lst, struct _node** passwd, struc
 	if (find_by_named_entry(master, walk)) {
 	    if (!noautoadd(lst, walk->id)) {
 		struct _node*	movednode=walk;
-		walk=walk->next;
-		remove_node(passwd, movednode);
-		add_node(passwd, movednode, 1);
-		flag_dirty++;
+		int		make_change=1;
+
+		if (flag_debconf) {
+		    char*	question;
+		    char*	template;
+		    char*	id;
+		    const char*	domain=user_domain;
+
+		    if (strcmp(descr, "group")==0)
+			domain=group_domain;
+		    question=xasprintf("base-passwd/%s/%s/%s/move", domain, descr, movednode->name);
+		    template=xasprintf("base-passwd/%s-move", descr);
+		    id=xasprintf("%u", movednode->id);
+		    DEBCONF_REGISTER(template, question);
+		    DEBCONF_SUBST(question, "name", movednode->name);
+		    DEBCONF_SUBST(question, "id", id);
+		    make_change=ask_debconf("low", question);
+		    free(question);
+		    free(template);
+		    free(id);
+		}
 
-		if (opt_verbose)
-		    printf("Moving %s \"%s\" (%u) to before \"+\" entry\n", descr, movednode->name, movednode->id);
+		if (make_change) {
+		    if (opt_verbose)
+			printf("Moving %s \"%s\" (%u) to before \"+\" entry\n", descr, movednode->name, movednode->id);
+		    remove_node(passwd, movednode);
+		    add_node(passwd, movednode, 1);
+		    flag_dirty++;
+		}
+
+		walk=walk->next;
 		continue;
 	    }
 	}
@@ -609,19 +725,41 @@ void process_moved_entries(const struct _info* lst, struct _node** passwd, struc
 void process_new_entries(const struct _info* lst, struct _node** passwd, struct _node* master, const char* descr) {
     while (master) {
 	if (find_by_named_entry(*passwd, master)==NULL) {
-	    struct _node* newnode;
+	    struct _node*	newnode;
+	    int			make_change=1;
 
 	    if (noautoadd(lst, master->id)) {
 		master=master->next;
 		continue;
 	    }
 
-	    newnode=copy_node(master);
-	    add_node(passwd, newnode, 1);
-	    flag_dirty++;
+	    if (flag_debconf) {
+		char*		question;
+		char*		template;
+		char*		id;
+		const char*	domain=user_domain;
+
+		if (strcmp(descr, "group")==0)
+		    domain=group_domain;
+		question=xasprintf("base-passwd/%s/%s/%s/add", domain, descr, master->name);
+		template=xasprintf("base-passwd/%s-add", descr);
+		id=xasprintf("%u", master->id);
+		DEBCONF_REGISTER(template, question);
+		DEBCONF_SUBST(question, "name", master->name);
+		DEBCONF_SUBST(question, "id", id);
+		make_change=ask_debconf("medium", question);
+		free(question);
+		free(template);
+		free(id);
+	    }
 
-	    if (opt_verbose)
-		printf("Adding %s \"%s\" (%u)\n", descr, newnode->name, newnode->id);
+	    if (make_change) {
+		if (opt_verbose)
+		    printf("Adding %s \"%s\" (%u)\n", descr, master->name, master->id);
+		newnode=copy_node(master);
+		add_node(passwd, newnode, 1);
+		flag_dirty++;
+	    }
 	}
 	master=master->next;
     }
@@ -648,13 +786,35 @@ void process_old_entries(const struct _info* lst, struct _node** passwd, struct
 
 	if (find_by_named_entry(master, walk)==NULL) {
 	    struct _node*	oldnode=walk;
+	    int			make_change=1;
+
+	    if (flag_debconf) {
+		char*		question;
+		char*		template;
+		char*		id;
+		const char*	domain=user_domain;
+
+		if (strcmp(descr, "group")==0)
+		    domain=group_domain;
+		question=xasprintf("base-passwd/%s/%s/%s/remove", domain, descr, oldnode->name);
+		template=xasprintf("base-passwd/%s-remove", descr);
+		id=xasprintf("%u", oldnode->id);
+		DEBCONF_REGISTER(template, question);
+		DEBCONF_SUBST(question, "name", oldnode->name);
+		DEBCONF_SUBST(question, "id", id);
+		make_change=ask_debconf("high", question);
+		free(question);
+		free(template);
+		free(id);
+	    }
 
-	    if (opt_verbose)
-		printf("Removing %s \"%s\" (%u)\n", descr, oldnode->name, oldnode->id);
-
+	    if (make_change) {
+		if (opt_verbose)
+		    printf("Removing %s \"%s\" (%u)\n", descr, oldnode->name, oldnode->id);
+		remove_node(passwd, oldnode);
+		flag_dirty++;
+	    }
 	    walk=walk->next;
-	    remove_node(passwd, oldnode);
-	    flag_dirty++;
 	    continue;
 	}
 	walk=walk->next;
@@ -667,6 +827,12 @@ void process_old_entries(const struct _info* lst, struct _node** passwd, struct
 void process_changed_accounts(struct _node* passwd, struct _node* group, struct _node* master) {
     for (;passwd; passwd=passwd->next) {
 	struct _node*	mc;	/* mastercopy of this account */
+	char*		question;
+	char*		old_id;
+	char*		new_id;
+	char*		oldpart;
+	char*		newpart;
+	int		make_change;
 
 	if (((passwd->id<0) || (passwd->id>99)) && (passwd->id!=65534))
 	    continue;
@@ -676,65 +842,148 @@ void process_changed_accounts(struct _node* passwd, struct _node* group, struct
 	    continue;
 
 	if (passwd->id!=mc->id) {
-	    if (opt_verbose)
-		printf("Changing uid of %s from %u to %u\n", passwd->name, passwd->id, mc->id);
-	    passwd->id=mc->id;
-	    passwd->d.pw.pw_uid=mc->d.pw.pw_uid;
-	    flag_dirty++;
+	    make_change=1;
+	    if (flag_debconf) {
+		question=xasprintf("base-passwd/%s/user/%s/uid/%u/%u", user_domain, passwd->name, passwd->id, mc->id);
+		old_id=xasprintf("%u", passwd->id);
+		new_id=xasprintf("%u", mc->id);
+		DEBCONF_REGISTER("base-passwd/user-change-uid", question);
+		DEBCONF_SUBST(question, "name", passwd->name);
+		DEBCONF_SUBST(question, "old_uid", old_id);
+		DEBCONF_SUBST(question, "new_uid", new_id);
+		make_change=ask_debconf("high", question);
+		free(question);
+		free(old_id);
+		free(new_id);
+	    }
+
+	    if (make_change) {
+		if (opt_verbose)
+		    printf("Changing uid of %s from %u to %u\n", passwd->name, passwd->id, mc->id);
+		passwd->id=mc->id;
+		passwd->d.pw.pw_uid=mc->d.pw.pw_uid;
+		flag_dirty++;
+	    }
 	}
 
 	if (passwd->d.pw.pw_gid!=mc->d.pw.pw_gid) {
-	    if (opt_verbose) {
-		const struct _node* oldentry = find_by_id(group, passwd->d.pw.pw_gid);
-		const struct _node* newentry = find_by_id(group, mc->d.pw.pw_gid);
-		const char* oldname = oldentry ? oldentry->name : "ABSENT";
-		const char* newname = newentry ? newentry->name : "ABSENT";
-		printf("Changing gid of %s from %u (%s) to %u (%s)\n", passwd->name, passwd->d.pw.pw_gid, oldname, mc->d.pw.pw_gid, newname);
+	    const struct _node* oldentry = find_by_id(group, passwd->d.pw.pw_gid);
+	    const struct _node* newentry = find_by_id(group, mc->d.pw.pw_gid);
+	    const char* oldname = oldentry ? oldentry->name : "ABSENT";
+	    const char* newname = newentry ? newentry->name : "ABSENT";
+
+	    make_change=1;
+	    if (flag_debconf) {
+		question=xasprintf("base-passwd/%s/user/%s/gid/%u/%u", user_domain, passwd->name, passwd->d.pw.pw_gid, mc->d.pw.pw_gid);
+		old_id=xasprintf("%u", passwd->d.pw.pw_gid);
+		new_id=xasprintf("%u", mc->d.pw.pw_gid);
+		DEBCONF_REGISTER("base-passwd/user-change-gid", question);
+		DEBCONF_SUBST(question, "name", passwd->name);
+		DEBCONF_SUBST(question, "old_gid", old_id);
+		DEBCONF_SUBST(question, "old_group", oldname);
+		DEBCONF_SUBST(question, "new_gid", new_id);
+		DEBCONF_SUBST(question, "new_group", newname);
+		make_change=ask_debconf("high", question);
+		free(question);
+		free(old_id);
+		free(new_id);
+	    }
+
+	    if (make_change) {
+		if (opt_verbose)
+		    printf("Changing gid of %s from %u (%s) to %u (%s)\n", passwd->name, passwd->d.pw.pw_gid, oldname, mc->d.pw.pw_gid, newname);
+		passwd->d.pw.pw_gid=mc->d.pw.pw_gid;
+		flag_dirty++;
 	    }
-	    passwd->d.pw.pw_gid=mc->d.pw.pw_gid;
-	    flag_dirty++;
 	}
 
 	if (!keepgecos(specialusers, passwd->id))
 	    if ((passwd->d.pw.pw_gecos==NULL) || (strcmp(passwd->d.pw.pw_gecos, mc->d.pw.pw_gecos)!=0)) {
-		if (opt_verbose) {
-		    const char *oldgecos = passwd->d.pw.pw_gecos ? passwd->d.pw.pw_gecos : "";
-		    printf("Changing GECOS of %s from \"%s\" to \"%s\".\n", passwd->name, oldgecos, mc->d.pw.pw_gecos);
+		const char *oldgecos = passwd->d.pw.pw_gecos ? passwd->d.pw.pw_gecos : "";
+
+		make_change=1;
+		if (flag_debconf) {
+		    question=xasprintf("base-passwd/%s/user/%s/gecos", user_domain, passwd->name);
+		    DEBCONF_REGISTER("base-passwd/user-change-gecos", question);
+		    DEBCONF_SUBST(question, "name", passwd->name);
+		    DEBCONF_SUBST(question, "old_gecos", oldgecos);
+		    DEBCONF_SUBST(question, "new_gecos", mc->d.pw.pw_gecos);
+		    make_change=ask_debconf("low", question);
+		    free(question);
+		}
+
+		if (make_change) {
+		    if (opt_verbose)
+			printf("Changing GECOS of %s from \"%s\" to \"%s\".\n", passwd->name, oldgecos, mc->d.pw.pw_gecos);
+		    /* We update the pw_gecos entry of passwd so it now points into the
+		     * buffer from mc. This is safe for us, since we know we won't free
+		     * the data in mc until after we are done.
+		     */
+		    passwd->d.pw.pw_gecos=mc->d.pw.pw_gecos;
+		    flag_dirty++;
 		}
-		/* We update the pw_gecos entry of passwd so it now points into the
-		 * buffer from mc. This is safe for us, since we know we won't free
-		 * the data in mc until after we are done.
-		 */
-		passwd->d.pw.pw_gecos=mc->d.pw.pw_gecos;
-		flag_dirty++;
 	    }
 
 	if (!keephome(specialusers, passwd->id))
 	    if ((passwd->d.pw.pw_dir==NULL) || (strcmp(passwd->d.pw.pw_dir, mc->d.pw.pw_dir)!=0)) {
-		if (opt_verbose) {
-		    const char *olddir = passwd->d.pw.pw_dir ? passwd->d.pw.pw_dir : "(none)";
-		    printf("Changing home-directory of %s from %s to %s\n", passwd->name, olddir, mc->d.pw.pw_dir);
+		const char *olddir = passwd->d.pw.pw_dir ? passwd->d.pw.pw_dir : "(none)";
+
+		make_change=1;
+		if (flag_debconf) {
+		    oldpart=escape_debconf(olddir);
+		    newpart=escape_debconf(mc->d.pw.pw_dir);
+		    question=xasprintf("base-passwd/%s/user/%s/home/%s/%s", user_domain, passwd->name, oldpart, newpart);
+		    free(oldpart);
+		    free(newpart);
+		    DEBCONF_REGISTER("base-passwd/user-change-home", question);
+		    DEBCONF_SUBST(question, "name", passwd->name);
+		    DEBCONF_SUBST(question, "old_home", olddir);
+		    DEBCONF_SUBST(question, "new_home", mc->d.pw.pw_dir);
+		    make_change=ask_debconf("high", question);
+		    free(question);
+		}
+
+		if (make_change) {
+		    if (opt_verbose)
+			printf("Changing home-directory of %s from %s to %s\n", passwd->name, olddir, mc->d.pw.pw_dir);
+		    /* We update the pw_dir entry of passwd so it now points into the
+		     * buffer from mc. This is safe for us, since we know we won't free
+		     * the data in mc until after we are done.
+		     */
+		    passwd->d.pw.pw_dir=mc->d.pw.pw_dir;
+		    flag_dirty++;
 		}
-		/* We update the pw_dir entry of passwd so it now points into the
-		 * buffer from mc. This is safe for us, since we know we won't free
-		 * the data in mc until after we are done.
-		 */
-		passwd->d.pw.pw_dir=mc->d.pw.pw_dir;
-		flag_dirty++;
 	    }
 
 	if (!keepshell(specialusers, passwd->id))
 	    if ((passwd->d.pw.pw_shell==NULL) || (strcmp(passwd->d.pw.pw_shell, mc->d.pw.pw_shell)!=0)) {
-		if (opt_verbose) {
-		    const char *oldshell = passwd->d.pw.pw_shell ? passwd->d.pw.pw_shell : "(none)";
-		    printf("Changing shell of %s from %s to %s\n", passwd->name, oldshell, mc->d.pw.pw_shell);
+		const char *oldshell = passwd->d.pw.pw_shell ? passwd->d.pw.pw_shell : "(none)";
+
+		make_change=1;
+		if (flag_debconf) {
+		    oldpart=escape_debconf(oldshell);
+		    newpart=escape_debconf(mc->d.pw.pw_shell);
+		    question=xasprintf("base-passwd/%s/user/%s/shell/%s/%s", user_domain, passwd->name, oldpart, newpart);
+		    free(oldpart);
+		    free(newpart);
+		    DEBCONF_REGISTER("base-passwd/user-change-shell", question);
+		    DEBCONF_SUBST(question, "name", passwd->name);
+		    DEBCONF_SUBST(question, "old_shell", oldshell);
+		    DEBCONF_SUBST(question, "new_shell", mc->d.pw.pw_shell);
+		    make_change=ask_debconf("medium", question);
+		    free(question);
+		}
+
+		if (make_change) {
+		    if (opt_verbose)
+			printf("Changing shell of %s from %s to %s\n", passwd->name, oldshell, mc->d.pw.pw_shell);
+		    /* We update the pw_shell entry of passwd so it now points into the
+		     * buffer from mc. This is safe for us, since we know we won't free
+		     * the data in mc until after we are done.
+		     */
+		    passwd->d.pw.pw_shell=mc->d.pw.pw_shell;
+		    flag_dirty++;
 		}
-		/* We update the pw_shell entry of passwd so it now points into the
-		 * buffer from mc. This is safe for us, since we know we won't free
-		 * the data in mc until after we are done.
-		 */
-		passwd->d.pw.pw_shell=mc->d.pw.pw_shell;
-		flag_dirty++;
 	    }
     }
 }
@@ -754,11 +1003,33 @@ void process_changed_groups(struct _node* group, struct _node* master) {
 	    continue;
 
 	if (group->id!=mc->id) {
-	    if (opt_verbose)
-		printf("Changing gid of %s from %u to %u\n", group->name, group->id, mc->id);
-	    group->id=mc->id;
-	    group->d.gr.gr_gid=mc->d.gr.gr_gid;
-	    flag_dirty++;
+	    int	make_change=1;
+
+	    if (flag_debconf) {
+		char*	question;
+		char*	old_gid;
+		char*	new_gid;
+
+		question=xasprintf("base-passwd/%s/group/%s/gid/%u/%u", group_domain, group->name, group->id, mc->id);
+		old_gid=xasprintf("%u", group->id);
+		new_gid=xasprintf("%u", mc->id);
+		DEBCONF_REGISTER("base-passwd/group-change-gid", question);
+		DEBCONF_SUBST(question, "name", group->name);
+		DEBCONF_SUBST(question, "old_gid", old_gid);
+		DEBCONF_SUBST(question, "new_gid", new_gid);
+		make_change=ask_debconf("high", question);
+		free(question);
+		free(old_gid);
+		free(new_gid);
+	    }
+
+	    if (make_change) {
+		if (opt_verbose)
+		    printf("Changing gid of %s from %u to %u\n", group->name, group->id, mc->id);
+		group->id=mc->id;
+		group->d.gr.gr_gid=mc->d.gr.gr_gid;
+		flag_dirty++;
+	    }
 	}
     }
 }
@@ -1143,12 +1414,14 @@ int main(int argc, char** argv) {
 		break;
 	    case 'P':
 		sys_passwd=optarg;
+		user_domain="other";
 		break;
 	    case 'S':
 		sys_shadow=optarg;
 		break;
 	    case 'G':
 		sys_group=optarg;
+		group_domain="other";
 		break;
 	    case 'v':
 		opt_verbose++;
@@ -1176,6 +1449,18 @@ int main(int argc, char** argv) {
 		return 1;
 	}
 
+    /* If DEBIAN_HAS_FRONTEND is set in the environment, we're running under
+     * debconf.  Enable debconf prompting unless --dry-run was also given.
+     */
+    if (getenv("DEBIAN_HAS_FRONTEND")!=NULL && !opt_dryrun) {
+	debconf=debconfclient_new();
+	if (debconf==NULL) {
+	    fprintf(stderr, "Cannot initialize debconf\n");
+	    exit(1);
+	}
+	flag_debconf=1;
+    }
+
     if (read_passwd(&master_accounts, master_passwd)!=0)
 	return 2;
 
@@ -1220,6 +1505,9 @@ int main(int argc, char** argv) {
 	if (!unlock_files())
 	    return 5;
 
+    if (debconf!=NULL)
+	debconfclient_delete(debconf);
+
     if (opt_dryrun)
 	return flag_dirty;
     else
-- 
1.8.5.2

