Package: base-passwd
Version: 3.5.29
Followup-For: Bug #184979

Hi Colin,

Here's a patch to teach update-passwd how to use debconf for
prompting.

Some caveats with this patch:

* I'm not particularly proud of the code layout and repetition,
  particularly how changes to user information are handled.
  Unfortunately, this sort of thing is exactly what C is the worst
  at, and the only alternative I came up with would have involved
  passing a ton of parameters to a bunch of specialist functions.

* I spot-tested this patch and ran through a few obvious scenarios,
  but I didn't do exhaustive testing.  It's possible that there are
  some fiddly bugs remaining in some of the specific cases of data
  changes for users.

* This patch converts postinst to use debconf unconditionally.  This
  is more an artifact of how I tested it than an intentional decision.
  Given that base-passwd is an essential package, I suspect there are
  special considerations, but I've not done much work with essential
  packages before.  I was hoping you'd sort out the right thing.  :)
  Let me know if you need for me to make changes.

Some additional changes that are unrelated, and therefore I didn't
include in this patch, but which I'd recommend making:

* Now that there's an xasprintf function, you may want to use it in
  the few places where you're currently using asprintf.  This would
  also get rid of some build warnings.

* Since this is a native package and you're already running
  dh_autoreconf, I would recommend just deleting configure and
  config.h.in from the package.

I did not include the changes to configure or config.h.in in this
patch, since they're uninteresting and best generated with autoreconf
instead of applying a patch.

-- System Information:
Debian Release: jessie/sid
  APT prefers unstable
  APT policy: (500, 'unstable'), (1, 'experimental')
Architecture: amd64 (x86_64)

Kernel: Linux 3.11-2-amd64 (SMP w/8 CPU cores)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

Versions of packages base-passwd depends on:
ii  libc6  2.17-97

base-passwd recommends no packages.

base-passwd suggests no packages.

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

If DEBCONF_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.

Rewrite postinst to use debconf prompting instead of manual
prompting.  If postinst has to work when debconf isn't available,
since base-passwd is in essential, this will require some further
work.
---
 Makefile.in             |   2 +-
 configure.ac            |   6 +
 debian/control          |   2 +-
 debian/po/POTFILES.in   |   1 +
 debian/po/templates.pot | 262 ++++++++++++++++++
 debian/postinst         |  39 +--
 debian/templates        | 229 ++++++++++++++++
 man/update-passwd.8     |  12 +
 update-passwd.c         | 410 +++++++++++++++++++++++-----
 12 files changed, 1607 insertions(+), 101 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 <cjwat...@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..438c439
--- /dev/null
+++ b/debian/po/templates.pot
@@ -0,0 +1,262 @@
+# 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-pas...@packages.debian.org\n"
+"POT-Creation-Date: 2014-01-03 10:27-0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <l...@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 \"+\" 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 \"+\" 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
+#. Type: boolean
+#. Description
+#: ../templates:10001 ../templates:11001
+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 "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..0a77d92 100755
--- a/debian/postinst
+++ b/debian/postinst
@@ -76,43 +76,12 @@ EOF
 fi
 
 tmp=`tempfile`
-if ! update-passwd --dry-run > $tmp ; then
-	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
-see /usr/share/doc/base-passwd/README.
-
-The list of proposed changes is:
-
-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
-
-if [ "$a" = "y" ] ; then
-	echo "Okay, I am going to make the necessary updates now"
+if ! update-passwd --dry-run > /dev/null ; then
+	. /usr/share/debconf/confmodule
+	db_version 2.0
 	update-passwd --verbose
+	db_stop
 	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
 
 if [ "$changes" -gt 0 ] ; then
diff --git a/debian/templates b/debian/templates
new file mode 100644
index 0000000..1185b05
--- /dev/null
+++ b/debian/templates
@@ -0,0 +1,229 @@
+Template: base-passwd/user-move
+Type: boolean
+Default: false
+_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 "+" 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: false
+_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 "+" 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: false
+_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: false
+_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: false
+_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: false
+_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: false
+_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: false
+_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: false
+_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: false
+_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: false
+_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: false
+_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..344222f 100644
--- a/man/update-passwd.8
+++ b/man/update-passwd.8
@@ -63,6 +63,18 @@ Show a summary of how to use
 .TP
 .BR \-V ,\  \-\-version
 Show the version number
+.SH ENVIRONMENT
+.TP
+DEBCONF_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.
+Most questions will be asked at medium priority.
+Questions about whether to move entries above the NIS compatibility switch
+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..67b2ae2 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.  Only use ret 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,22 @@ 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.
+ */
+void xasprintf(char** strp, const char* fmt, ...) {
+    va_list	args;
+    int		ret;
+
+    va_start(args, fmt);
+    ret=vasprintf(strp, fmt, args);
+    va_end(args);
+    if (ret<0) {
+	fprintf(stderr, "Formatting string failed: %s\n", strerror(errno));
+	exit(1);
+    }
+}
+
 /* Create an empty list-entry
  */
 struct _node* create_node() {
@@ -570,6 +616,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;
+    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 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='_';
+    return copy;
+}
+
+
 /* Check if we need to move any master file entries above NIS compat
  * switching entries ("+").
  */
@@ -587,13 +677,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;
+		    xasprintf(&question, "base-passwd/%s/%s/%s/move", domain, descr, movednode->name);
+		    xasprintf(&template, "base-passwd/%s-move", descr);
+		    xasprintf(&id, "%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 +723,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;
+		xasprintf(&question, "base-passwd/%s/%s/%s/add", domain, descr, master->name);
+		xasprintf(&template, "base-passwd/%s-add", descr);
+		xasprintf(&id, "%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 +784,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;
+		xasprintf(&question, "base-passwd/%s/%s/%s/remove", domain, descr, oldnode->name);
+		xasprintf(&template, "base-passwd/%s-remove", descr);
+		xasprintf(&id, "%u", oldnode->id);
+		DEBCONF_REGISTER(template, question);
+		DEBCONF_SUBST(question, "name", oldnode->name);
+		DEBCONF_SUBST(question, "id", id);
+		make_change=ask_debconf("medium", 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 +825,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 +840,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) {
+		xasprintf(&question, "base-passwd/%s/user/%s/uid/%u/%u", user_domain, passwd->name, passwd->id, mc->id);
+		xasprintf(&old_id, "%u", passwd->id);
+		xasprintf(&new_id, "%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("medium", 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) {
+		xasprintf(&question, "base-passwd/%s/user/%s/gid/%u/%u", user_domain, passwd->name, passwd->d.pw.pw_gid, mc->d.pw.pw_gid);
+		xasprintf(&old_id, "%u", passwd->d.pw.pw_gid);
+		xasprintf(&new_id, "%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("medium", 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) {
+		    xasprintf(&question, "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);
+		    xasprintf(&question, "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("medium", 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);
+		    xasprintf(&question, "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 +1001,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;
+
+		xasprintf(&question, "base-passwd/%s/group/%s/gid/%u/%u", group_domain, group->name, group->id, mc->id);
+		xasprintf(&old_gid, "%u", group->id);
+		xasprintf(&new_gid, "%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("medium", 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 +1412,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 +1447,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 +1503,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

Reply via email to