Author: Giovanni Campagna <gcampagna@src.gnome.org>
Description: Add PAM module for PIN codes
 This patch gathers six patches dated 2013-02-16 in the upstream bug.
 There are further Ubuntu tweaks in a second patch, separated to make it easier
 to update this patch from the upstream bug.
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=51833

Index: accountsservice-0.6.35/data/Makefile.am
===================================================================
--- accountsservice-0.6.35.orig/data/Makefile.am	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/data/Makefile.am	2013-11-13 15:20:32.208131938 +0100
@@ -19,6 +19,9 @@
 policy_in_files = org.freedesktop.accounts.policy.in
 policy_DATA     = $(policy_in_files:.policy.in=.policy)
 
+pamdir = $(sysconfdir)/pam.d
+dist_pam_DATA = accountsservice
+
 @INTLTOOL_POLICY_RULE@
 
 if HAVE_SYSTEMD
Index: accountsservice-0.6.35/data/accountsservice
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ accountsservice-0.6.35/data/accountsservice	2013-11-13 15:20:32.208131938 +0100
@@ -0,0 +1,6 @@
+#%PAM-1.0
+# Must use substack here, so the success of pam_unix will still
+# cause our pam_pin to run
+password   substack      password-auth
+password   optional      pam_pin.so
+
Index: accountsservice-0.6.35/src/Makefile.am
===================================================================
--- accountsservice-0.6.35.orig/src/Makefile.am	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/src/Makefile.am	2013-11-13 15:21:48.484134234 +0100
@@ -1,14 +1,21 @@
 NULL =
 BUILT_SOURCES =
 SUBDIRS = . libaccountsservice
+
+if HAVE_PAM_MODULE
+SUBDIRS += pam-pin
+endif
+
 INCLUDES = 			\
 	-DLOCALSTATEDIR=\""$(localstatedir)"\" \
 	-DDATADIR=\""$(datadir)"\" \
 	-DICONDIR=\"$(localstatedir)/lib/AccountsService/icons\" \
 	-DUSERDIR=\"$(localstatedir)/lib/AccountsService/users\" \
+	-DLIBEXECDIR=\"$(libexecdir)\" \
 	-I$(srcdir)		\
 	-I$(builddir)		\
 	$(POLKIT_CFLAGS)	\
+	$(GCR_CFLAGS)		\
 	$(WARN_CFLAGS)
 
 noinst_LTLIBRARIES = libaccounts-generated.la
@@ -27,7 +34,7 @@
 accounts-user-generated.c accounts-user-generated.h: $(top_srcdir)/data/org.freedesktop.Accounts.User.xml Makefile
 	gdbus-codegen --generate-c-code accounts-user-generated --c-namespace Accounts --interface-prefix=org.freedesktop.Accounts. $(top_srcdir)/data/org.freedesktop.Accounts.User.xml
 
-libexec_PROGRAMS = accounts-daemon
+libexec_PROGRAMS = accounts-daemon accounts-daemon-pam-password-helper
 
 accounts_daemon_SOURCES = 	\
 	$(enums_h_sources)	\
@@ -45,8 +52,15 @@
 
 accounts_daemon_LDADD = 	\
 	libaccounts-generated.la	\
+	$(GCR_LIBS)			\
 	$(POLKIT_LIBS)
 
+accounts_daemon_pam_password_helper_SOURCES = \
+	pam-password-helper.c
+accounts_daemon_pam_password_helper_LDADD = \
+	$(GIO_LIBS) 		\
+	-lpam
+
 CLEANFILES = \
 	$(BUILT_SOURCES) \
 	*.gcda \
@@ -56,3 +70,7 @@
 install-data-hook:
 	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/AccountsService/users"
 	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/AccountsService/icons"
+	
+install-exec-hook:
+	-chown root $(DESTDIR)$(libexecdir)/accounts-daemon-pam-password-helper
+	-chmod 4755 $(DESTDIR)$(libexecdir)/accounts-daemon-pam-password-helper
Index: accountsservice-0.6.35/src/libaccountsservice/act-user.c
===================================================================
--- accountsservice-0.6.35.orig/src/libaccountsservice/act-user.c	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/src/libaccountsservice/act-user.c	2013-11-13 15:20:32.212131938 +0100
@@ -30,6 +30,9 @@
 #include <glib/gi18n.h>
 #include <gio/gio.h>
 
+#define GCR_API_SUBJECT_TO_CHANGE
+#include <gcr/gcr-base.h>
+
 #include "act-user-private.h"
 #include "accounts-user-generated.h"
 
@@ -2097,42 +2100,6 @@
         }
 }
 
-static gchar
-salt_char (GRand *rand)
-{
-        gchar salt[] = "ABCDEFGHIJKLMNOPQRSTUVXYZ"
-                       "abcdefghijklmnopqrstuvxyz"
-                       "./0123456789";
-
-        return salt[g_rand_int_range (rand, 0, G_N_ELEMENTS (salt))];
-}
-
-static gchar *
-make_crypted (const gchar *plain)
-{
-        GString *salt;
-        gchar *result;
-        GRand *rand;
-        gint i;
-
-        rand = g_rand_new ();
-        salt = g_string_sized_new (21);
-
-        /* SHA 256 */
-        g_string_append (salt, "$6$");
-        for (i = 0; i < 16; i++) {
-                g_string_append_c (salt, salt_char (rand));
-        }
-        g_string_append_c (salt, '$');
-
-        result = g_strdup (crypt (plain, salt->str));
-
-        g_string_free (salt, TRUE);
-        g_rand_free (rand);
-
-        return result;
-}
-
 /**
  * act_user_set_password:
  * @user: the user object to alter.
@@ -2149,24 +2116,85 @@
                        const gchar         *password,
                        const gchar         *hint)
 {
-        GError *error = NULL;
-        gchar *crypted;
+        GHashTable *table;
 
         g_return_if_fail (ACT_IS_USER (user));
         g_return_if_fail (password != NULL);
+
+        table = g_hash_table_new (g_direct_hash, g_direct_equal);
+        g_hash_table_insert (table, GINT_TO_POINTER (ACT_USER_PASSWORD_REGULAR),
+                             (gpointer) password);
+        g_hash_table_insert (table, GINT_TO_POINTER (ACT_USER_PASSWORD_HINT),
+                             (gpointer) hint);
+
+        act_user_set_multiple_passwords (user, table);
+
+        g_hash_table_unref (table);
+}
+
+/**
+ * act_user_set_multiple_passwords:
+ * @user: the user object to alter.
+ * @password_map: (element-type guint utf8): a #GHashTable mapping
+ *                #ActUserPasswordType to a plain-text password
+ *
+ * Changes password, password hint and PIN of @user, using the
+ * values taken from @password_map.
+ *
+ * Note this function is synchronous and ignores errors.
+ **/
+/* Note on the API: what we really want here is a list of (int-string)
+   values, but short of passing #GVariants (which is ugly), we can't
+   do that in a introspectable way without a boxed struct
+*/
+void
+act_user_set_multiple_passwords (ActUser             *user,
+                                 GHashTable          *password_map)
+{
+        GError *error = NULL;
+        GcrSecretExchange *exchange;
+        GHashTableIter iter;
+        GVariantBuilder builder;
+        char *exchange_begin;
+        gpointer key, value;
+
+        g_return_if_fail (ACT_IS_USER (user));
+        g_return_if_fail (password_map != NULL);
         g_return_if_fail (ACCOUNTS_IS_USER (user->accounts_proxy));
 
-        crypted = make_crypted (password);
-        if (!accounts_user_call_set_password_sync (user->accounts_proxy,
-                                                   crypted,
-                                                   hint,
-                                                   NULL,
-                                                   &error)) {
-                g_warning ("SetPassword call failed: %s", error->message);
+        if (!accounts_user_call_begin_set_password_sync (user->accounts_proxy,
+                                                         &exchange_begin,
+                                                         NULL, &error)) {
+                g_warning ("BeginSetPassword call failed: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        exchange = gcr_secret_exchange_new (GCR_SECRET_EXCHANGE_PROTOCOL_1);
+        gcr_secret_exchange_receive (exchange, exchange_begin);
+        g_free (exchange_begin);
+
+        g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(us)"));
+        g_hash_table_iter_init (&iter, password_map);
+        while (g_hash_table_iter_next (&iter, &key, &value)) {
+                char *next_str;
+
+                next_str = gcr_secret_exchange_send (exchange, value, -1);
+                g_variant_builder_add (&builder, "(us)",
+                                       GPOINTER_TO_INT (key),
+                                       next_str);
+                g_free (next_str);
+        }
+
+        if (!accounts_user_call_continue_set_password_sync (user->accounts_proxy,
+                                                            g_variant_builder_end (&builder),
+                                                            NULL,
+                                                            &error)) {
+                g_warning ("ContinueSetPassword call failed: %s", error->message);
                 g_error_free (error);
         }
-        memset (crypted, 0, strlen (crypted));
-        g_free (crypted);
+
+        g_object_unref (exchange);
 }
 
 /**
Index: accountsservice-0.6.35/src/pam-password-helper.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ accountsservice-0.6.35/src/pam-password-helper.c	2013-11-13 15:20:32.212131938 +0100
@@ -0,0 +1,169 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+#include <glib.h>
+
+static char *password;
+static char *old_password;
+static char *pin;
+
+static int
+answer_pam_message (int                        num_msg,
+                    const struct pam_message **msg,
+                    struct pam_response      **resp,
+                    void                      *appdata_ptr)
+{
+        int i;
+        struct pam_response *resps;
+
+        /* Must use malloc here, not g_malloc (and same below, must
+           use strdup, not g_strdup) */
+        resps = malloc((sizeof (struct pam_response)) * num_msg);
+
+        /* We run non-interactively, so just copy the password
+           into each question, and hope that configuration is
+           correct. */
+        /* Despite what's documented, msg is not a pointer
+           to an array of pointers. It's a pointer to a pointer
+           to an array of structures. Ugh!
+        */
+        for (i = 0; i < num_msg; i++) {
+                if ((*msg)[i].msg_style == PAM_PROMPT_ECHO_OFF) {
+                        const char *message = (*msg)[i].msg;
+
+                        if (old_password && strstr(message, "current") != NULL)
+                                resps[i].resp = strdup(old_password);
+                        else if (strcmp(message, "PIN") == 0)
+                                resps[i].resp = pin ? strdup(pin) : NULL;
+                        else
+                                resps[i].resp = strdup(password);
+                } else {
+                        resps[i].resp = NULL;
+                }
+
+                resps[i].resp_retcode = 0;
+
+        }
+
+        *resp = resps;
+
+        return PAM_SUCCESS;
+}
+
+static char *
+read_word (GIOChannel *from,
+           gboolean can_fail)
+{
+        char *str, *decoded;
+        gsize term_pos;
+        GIOStatus ok;
+        GError *error;
+
+        error = NULL;
+        while ((ok = g_io_channel_read_line (from,
+                                             &str,
+                                             NULL,
+                                             &term_pos,
+                                             &error)) == G_IO_STATUS_AGAIN);
+
+        if (ok == G_IO_STATUS_EOF && can_fail)
+                return NULL;
+
+        if (ok != G_IO_STATUS_NORMAL) {
+                if (error) {
+                        g_printerr ("Error reading from standard input: %s\n", error->message);
+                        g_error_free (error);
+                } else {
+                        g_printerr ("Generic error reading from standard input\n");
+                }
+
+                exit(PAM_AUTHTOK_ERR);
+        }
+
+        str[term_pos] = 0;
+
+        /* URI escaping is a simple form of encoding that avoids
+           ambiguity with \n while allowing arbitrary byte sequences */
+        decoded = g_uri_unescape_string (str, "");
+
+        if (decoded == NULL) {
+                g_printerr ("Failed to decode password (probably contained a NUL).\n");
+                exit(PAM_AUTHTOK_ERR);
+        }
+
+        g_free (str);
+        return decoded;
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+        const char *username;
+        pam_handle_t *pamh;
+        struct pam_conv conv = { answer_pam_message, NULL };
+        int res;
+        GIOChannel *stdin;
+
+        if (argc != 2) {
+                g_printerr ("Wrong number of arguments passed, 1 expected\n");
+                return PAM_AUTHTOK_ERR;
+        }
+
+        username = argv[1];
+
+        stdin = g_io_channel_unix_new (STDIN_FILENO);
+        if (getuid() != 0) {
+                /* Running from the user session, read the old password first */
+                old_password = read_word (stdin, FALSE);
+        }
+
+        password = read_word (stdin, FALSE);
+        pin = read_word (stdin, TRUE);
+        g_io_channel_unref (stdin);
+
+        res = pam_start ("accountsservice", username,
+                         &conv, &pamh);
+        if (res != PAM_SUCCESS) {
+                /* pam_strerror can't be used without a pam handle */
+                g_printerr ("Pam handle creation failed (not enough memory?)\n");
+                return res;
+        }
+
+        res = pam_chauthtok (pamh, PAM_SILENT);
+        if (res != PAM_SUCCESS) {
+                g_printerr ("Password change failed: %s\n", pam_strerror (pamh, res));
+        }
+        pam_end (pamh, res);
+
+        g_free (password);
+        g_free (pin);
+
+        return res;
+}
Index: accountsservice-0.6.35/src/user.c
===================================================================
--- accountsservice-0.6.35.orig/src/user.c	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/src/user.c	2013-11-13 15:20:32.216131938 +0100
@@ -42,6 +42,9 @@
 #include <gio/gunixinputstream.h>
 #include <polkit/polkit.h>
 
+#define GCR_API_SUBJECT_TO_CHANGE
+#include <gcr/gcr-base.h>
+
 #include "user-classify.h"
 #include "daemon.h"
 #include "user.h"
@@ -86,6 +89,8 @@
 
         GKeyFile     *keyfile;
 
+        GHashTable *secret_exchanges;
+
         uid_t         uid;
         gid_t         gid;
         gchar        *user_name;
@@ -2508,49 +2513,100 @@
         return TRUE;
 }
 
+static char *
+build_pam_helper_stdin (const char *password,
+                        const char *pin)
+{
+        char *encoded_pin, *encoded_password, *result;
+
+        encoded_password = g_uri_escape_string (password, NULL, FALSE);
+
+        if (pin != NULL) {
+                encoded_pin = g_uri_escape_string (pin, NULL, FALSE);
+
+                result = g_strdup_printf ("%s\n%s\n", encoded_password, encoded_pin);
+                g_free (encoded_pin);
+        } else {
+                result = g_strdup_printf ("%s\n", encoded_password);
+        }
+
+        g_free (encoded_password);
+        return result;
+}
+
 static void
-user_change_password_authorized_cb (Daemon                *daemon,
-                                    User                  *user,
-                                    GDBusMethodInvocation *context,
-                                    gpointer               data)
+user_continue_change_password_authorized_cb (Daemon                *daemon,
+                                             User                  *user,
+                                             GDBusMethodInvocation *context,
+                                             gpointer               data)
 
 {
-        gchar **strings = data;
+        GVariant *passwords = data;
+        GVariantIter iter;
+        GcrSecretExchange *exchange;
         GError *error;
         const gchar *argv[6];
+        unsigned int type;
+        const char *encrypted_password;
+        char *password_hint, *password, *pin;
+        char *stdin;
 
-        sys_log (context,
-                 "set password and hint of user '%s' (%d)",
+        sys_log (context, "change password of user '%s' (%d)",
                  user->user_name, user->uid);
 
+        exchange = g_hash_table_lookup (user->secret_exchanges,
+                                        g_dbus_method_invocation_get_sender (context));
+        g_object_ref (exchange);
+        g_hash_table_remove (user->secret_exchanges, g_dbus_method_invocation_get_sender (context));
+
+        if (exchange == NULL) {
+                /* The caller never called BeginSetPassword */
+                throw_error (context, ERROR_INVALID, "secret exchange not initialized");
+                return;
+        }
+
         g_object_freeze_notify (G_OBJECT (user));
 
-        argv[0] = "/usr/sbin/usermod";
-        argv[1] = "-p";
-        argv[2] = strings[0];
-        argv[3] = "--";
-        argv[4] = user->user_name;
-        argv[5] = NULL;
+        password_hint = NULL;
+        password = NULL;
+        pin = NULL;
+        g_variant_iter_init (&iter, passwords);
+        while (g_variant_iter_next (&iter, "(u&s)", &type, &encrypted_password)) {
+                if (!gcr_secret_exchange_receive (exchange, encrypted_password)) {
+                        throw_error (context, ERROR_INVALID, "could not decrypt password");
+                        goto out;
+                }
 
-        error = NULL;
-        if (!spawn_with_login_uid (context, argv, &error)) {
-                throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message);
-                g_error_free (error);
-                return;
+                switch (type) {
+                case 0:
+                        g_free (password);
+                        password = g_strdup (gcr_secret_exchange_get_secret (exchange, NULL));
+                        break;
+                case 1:
+                        g_free (password_hint);
+                        password_hint = g_strdup (gcr_secret_exchange_get_secret (exchange, NULL));
+                        break;
+                case 2:
+                        g_free (pin);
+                        pin = g_strdup (gcr_secret_exchange_get_secret (exchange, NULL));
+                        break;
+
+                default:
+                        throw_error (context, ERROR_INVALID, "invalid password type");
+                        goto out;
+                }
         }
 
-        /* Drop user from nopasswdlogin group */
-        argv[0] = "/usr/bin/gpasswd";
-        argv[1] = "-d";
-        argv[2] = user->user_name;
-        argv[3] = "nopasswdlogin";
-        argv[4] = NULL;
+        argv[0] = LIBEXECDIR "/accounts-daemon-pam-password-helper";
+        argv[1] = user->user_name;
+        argv[2] = NULL;
 
+        stdin = build_pam_helper_stdin (password, pin);
         error = NULL;
-        if (!spawn_with_login_uid (context, argv, &error)) {
+
+        if (!spawn_with_login_uid_and_stdin (context, argv, stdin, &error)) {
                 throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message);
-                g_error_free (error);
-                return;
+                goto out;
         }
 
         if (user->password_mode != PASSWORD_MODE_REGULAR) {
@@ -2563,52 +2619,99 @@
                 g_object_notify (G_OBJECT (user), "locked");
         }
 
-        if (g_strcmp0 (user->password_hint, strings[1]) != 0) {
+        if (password_hint != NULL &&
+            g_strcmp0 (user->password_hint, password_hint) != 0) {
                 g_free (user->password_hint);
-                user->password_hint = g_strdup (strings[1]);
+                /* An empty password hint means no password hint */
+                if (*password_hint)
+                        user->password_hint = g_strdup (password_hint);
+                else
+                        user->password_hint = NULL;
                 g_object_notify (G_OBJECT (user), "password-hint");
         }
 
         save_extra_data (user);
 
-        g_object_thaw_notify (G_OBJECT (user));
-
         accounts_user_emit_changed (ACCOUNTS_USER (user));
 
-        accounts_user_complete_set_password (ACCOUNTS_USER (user), context);
-}
+        accounts_user_complete_continue_set_password (ACCOUNTS_USER (user), context);
 
-static void
-free_passwords (gchar **strings)
-{
-        memset (strings[0], 0, strlen (strings[0]));
-        g_strfreev (strings);
+ out:
+        g_free (password_hint);
+        g_free (password);
+        g_free (pin);
+        g_object_thaw_notify (G_OBJECT (user));
+        g_object_unref (exchange);
 }
 
 static gboolean
-user_set_password (AccountsUser          *auser,
-                   GDBusMethodInvocation *context,
-                   const gchar           *password,
-                   const gchar           *hint)
+user_continue_set_password (AccountsUser          *auser,
+                            GDBusMethodInvocation *context,
+                            GVariant              *passwords)
 {
         User *user = (User*)auser;
-        gchar **data;
-
-        data = g_new (gchar *, 3);
-        data[0] = g_strdup (password);
-        data[1] = g_strdup (hint);
-        data[2] = NULL;
 
         daemon_local_check_auth (user->daemon,
                                  user,
                                  "org.freedesktop.accounts.user-administration",
                                  TRUE,
-                                 user_change_password_authorized_cb,
+                                 user_continue_change_password_authorized_cb,
                                  context,
-                                 data,
-                                 (GDestroyNotify)free_passwords);
+                                 g_variant_ref (passwords),
+                                 (GDestroyNotify)g_variant_unref);
+
+        return TRUE;
+}
 
-        memset ((char*)password, 0, strlen (password));
+static void
+on_name_vanished (GDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+        User *user = USER (user_data);
+
+        g_hash_table_remove (user->secret_exchanges, name);
+}
+
+static void
+on_exchange_unreffed (gpointer  user_data,
+                      GObject  *object)
+{
+        g_bus_unwatch_name (GPOINTER_TO_INT (user_data));
+}
+
+static gboolean
+user_begin_set_password (AccountsUser          *auser,
+                         GDBusMethodInvocation *context)
+{
+        User *user = (User*)auser;
+        const char *who;
+        GcrSecretExchange *exchange;
+        char *begin_str;
+        int name_id;
+
+        who = g_dbus_method_invocation_get_sender (context);
+        exchange = g_hash_table_lookup (user->secret_exchanges, who);
+
+        if (exchange != NULL) {
+                throw_error (context, ERROR_INVALID, "secret exchange already in progress");
+                return TRUE;
+        }
+
+        exchange = gcr_secret_exchange_new (GCR_SECRET_EXCHANGE_PROTOCOL_1);
+        g_hash_table_insert (user->secret_exchanges, g_strdup (who), exchange);
+
+        name_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, who,
+                                    G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                    NULL, /* name appeared */
+                                    on_name_vanished,
+                                    auser, /* weak ref */
+                                    NULL);
+        g_object_weak_ref (G_OBJECT (exchange), on_exchange_unreffed, GINT_TO_POINTER (name_id));
+
+        begin_str = gcr_secret_exchange_begin (exchange);
+        accounts_user_complete_begin_set_password (auser, context, begin_str);
+        g_free (begin_str);
 
         return TRUE;
 }
@@ -2784,6 +2887,8 @@
 
         g_clear_pointer (&user->keyfile, g_key_file_unref);
 
+        g_hash_table_unref (user->secret_exchanges);
+
         g_free (user->object_path);
         g_free (user->user_name);
         g_free (user->real_name);
@@ -2981,6 +3086,8 @@
         iface->handle_set_language = user_set_language;
         iface->handle_set_location = user_set_location;
         iface->handle_set_locked = user_set_locked;
+        iface->handle_continue_set_password = user_continue_set_password;
+        iface->handle_begin_set_password = user_begin_set_password;
         iface->handle_set_password = user_set_password;
         iface->handle_set_password_mode = user_set_password_mode;
         iface->handle_set_real_name = user_set_real_name;
@@ -3015,6 +3122,10 @@
 {
         user->system_bus_connection = NULL;
         user->object_path = NULL;
+
+        user->secret_exchanges = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                        g_free, g_object_unref);
+
         user->user_name = NULL;
         user->real_name = NULL;
         user->account_type = ACCOUNT_TYPE_STANDARD;
Index: accountsservice-0.6.35/src/util.c
===================================================================
--- accountsservice-0.6.35.orig/src/util.c	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/src/util.c	2013-11-13 15:20:32.216131938 +0100
@@ -259,6 +259,109 @@
         return ret;
 }
 
+static char *
+read_all_from_fd (int      fd,
+                  GError **error)
+{
+        GIOChannel *channel;
+        char *str;
+        GIOStatus status;
+
+        channel = g_io_channel_unix_new (fd);
+        while ((status = g_io_channel_read_to_end (channel, &str, NULL, error))
+               == G_IO_STATUS_AGAIN);
+
+        if (status != G_IO_STATUS_NORMAL)
+                str = NULL;
+
+        g_io_channel_unref (channel);
+        return str;
+}
+
+static GIOStatus
+write_all_to_fd (int          fd,
+                 const char  *str,
+                 GError     **error)
+{
+        GIOChannel *channel;
+        GIOStatus status;
+        gsize written;
+
+        channel = g_io_channel_unix_new (fd);
+        written = 0;
+        while ((status = g_io_channel_write_chars (channel, str + written, -1, &written, error))
+               == G_IO_STATUS_AGAIN);
+
+        g_io_channel_unref (channel);
+        return status;
+}
+
+gboolean
+spawn_with_login_uid_and_stdin (GDBusMethodInvocation  *context,
+                                const gchar            *argv[],
+                                const gchar            *stdin,
+                                GError                **error)
+{
+        GError *local_error;
+        gchar loginuid[20];
+        gint std_in, std_err;
+        gint status;
+        char *std_err_str;
+        GPid pid;
+
+        get_caller_loginuid (context, loginuid, 20);
+
+        local_error = NULL;
+
+        if (!g_spawn_async_with_pipes (NULL, (gchar**)argv, NULL,
+                                       G_SPAWN_DO_NOT_REAP_CHILD,
+                                       setup_loginuid, loginuid,
+                                       &pid, &std_in, NULL, &std_err, &local_error)) {
+                g_propagate_error (error, local_error);
+                return FALSE;
+        }
+
+        if (write_all_to_fd (std_in, stdin, &local_error) == G_IO_STATUS_ERROR) {
+                g_prefix_error (&local_error, "Error writing to child standard input: ");
+                g_propagate_error (error, local_error);
+                close (std_in);
+                close (std_err);
+                return FALSE;
+        }
+
+        close (std_in);
+        /* We need to read from stderr before calling waitpid, or the child
+           could be blocked on a pipe buffer flush */
+        std_err_str = read_all_from_fd (std_err, &local_error);
+        /* At this point, it is safe to close std_err: EOF is returned from a pipe
+           read only when the writing end is closed, so when we return from read_all_from_fd()
+           the child is already done, and there is no risk of SIGPIPE
+        */
+        close (std_err);
+
+        waitpid (pid, &status, 0);
+
+        if (std_err_str == NULL) {
+                g_prefix_error (&local_error, "Error reading from child standard error: ");
+                g_propagate_error (error, local_error);
+                return FALSE;
+        }
+
+        if (WEXITSTATUS (status) != 0) {
+                g_set_error (error,
+                             G_SPAWN_ERROR,
+                             G_SPAWN_ERROR_FAILED,
+                             "%s returned an error (%d): %s",
+                             argv[0], WEXITSTATUS(status), std_err_str);
+                g_free (std_err_str);
+                return FALSE;
+        }
+
+        g_free (std_err_str);
+
+        return TRUE;
+}
+
 gint
 get_user_groups (const gchar  *user,
                  gid_t         group,
Index: accountsservice-0.6.35/src/util.h
===================================================================
--- accountsservice-0.6.35.orig/src/util.h	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/src/util.h	2013-11-13 15:20:32.216131938 +0100
@@ -36,6 +36,11 @@
                                const gchar            *argv[],
                                GError                **error);
 
+gboolean spawn_with_login_uid_and_stdin (GDBusMethodInvocation  *context,
+                                         const gchar            *argv[],
+                                         const gchar            *stdin,
+                                         GError                **error);
+
 gint get_user_groups (const gchar  *username,
                       gid_t         group,
                       gid_t       **groups);
Index: accountsservice-0.6.35/configure.ac
===================================================================
--- accountsservice-0.6.35.orig/configure.ac	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/configure.ac	2013-11-13 15:20:32.220131938 +0100
@@ -27,13 +27,14 @@
 
 PKG_CHECK_MODULES(GIO, gio-2.0 >= 2.37.3 gio-unix-2.0)
 PKG_CHECK_MODULES(POLKIT, gio-unix-2.0 polkit-gobject-1)
+PKG_CHECK_MODULES(GCR, gcr-base-3)
 
 AM_MAINTAINER_MODE([enable])
 
 # client library dependencies
-LIBACCOUNTSSERVICE_LIBS="$GIO_LIBS"
+LIBACCOUNTSSERVICE_LIBS="$GIO_LIBS $GCR_LIBS"
 AC_SUBST(LIBACCOUNTSSERVICE_LIBS)
-LIBACCOUNTSSERVICE_CFLAGS="$GIO_CFLAGS"
+LIBACCOUNTSSERVICE_CFLAGS="$GIO_CFLAGS $GCR_CFLAGS"
 AC_SUBST(LIBACCOUNTSSERVICE_CFLAGS)
 
 GOBJECT_INTROSPECTION_CHECK([0.9.12])
@@ -281,6 +282,19 @@
 fi
 AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != "xno" ])
 
+dnl ---------------------------------------------------------------------------
+dnl PIN authentication support
+dnl ---------------------------------------------------------------------------
+
+AC_ARG_ENABLE([pin-authentication],
+	AS_HELP_STRING([--disable-pin-authentication], [Disable PAM module for PIN (short password) login]),
+	[],
+	[enable_pin_authentication=yes])
+if test x$enable_pin_autentication != xno ; then
+   AM_PATH_LIBGCRYPT
+fi
+AM_CONDITIONAL(HAVE_PAM_MODULE, [test x$enable_pin_authentication != xno])
+
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_FILES([
 Makefile
@@ -290,6 +304,7 @@
 src/Makefile
 src/libaccountsservice/Makefile
 src/libaccountsservice/accountsservice.pc
+src/pam-pin/Makefile
 doc/Makefile
 doc/dbus/Makefile
 doc/dbus/AccountsService.xml
Index: accountsservice-0.6.35/data/org.freedesktop.Accounts.User.xml
===================================================================
--- accountsservice-0.6.35.orig/data/org.freedesktop.Accounts.User.xml	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/data/org.freedesktop.Accounts.User.xml	2013-11-13 15:20:32.220131938 +0100
@@ -595,26 +595,54 @@
     </doc:doc>
   </method>
 
-  <method name="SetPassword">
+  <method name="BeginSetPassword">
     <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
-    <arg name="password" direction="in" type="s">
+    <arg name="data" direction="out" type="s">
       <doc:doc>
         <doc:summary>
-          The crypted password.
+          Unspecified opaque data that should be passed to gcr_secret_exchange_receive()
         </doc:summary>
       </doc:doc>
     </arg>
-    <arg name="hint" direction="in" type="s">
+    <doc:doc>
+      <doc:description>
+        <doc:para>
+          Begins a #GcrSecretExchange to set the new password for this user.
+          The expected next step is a call to ContinueSetPassword with the encrypted
+          passwords.
+        </doc:para>
+        <doc:para>
+          Note that setting a password has the side-effect of
+          unlocking the account.
+        </doc:para>
+      </doc:description>
+      <doc:permission>
+        The caller does not need PolicyKit authorization, but the following ContinueSetPassword
+        might fail if the caller is not authorized.
+      </doc:permission>
+    </doc:doc>
+  </method>
+
+  <method name="ContinueSetPassword">
+    <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+    <arg name="passwords" direction="in" type="a(us)">
+      <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
       <doc:doc>
         <doc:summary>
-          The password hint.
+          The encrypted passwords. Each password is a DBus struct holding
+          - an unsigned integer specifying the kind of data:
+            - 0 if the password is a regular password
+            - 1 if the password is a really a hint (not a secret)
+            - 2 if the password is a PIN
+          - the result of calling gcr_secret_exchange_send(). The calls must
+            have been in the same order as they appear in the array.
         </doc:summary>
       </doc:doc>
     </arg>
     <doc:doc>
       <doc:description>
         <doc:para>
-          Sets a new password for this user.
+          Sets a new password, hint and PIN for this user.
         </doc:para>
         <doc:para>
           Note that setting a password has the side-effect of
@@ -633,6 +661,7 @@
       <doc:errors>
         <doc:error name="org.freedesktop.Accounts.Error.PermissionDenied">if the caller lacks the appropriate PolicyKit authorization</doc:error>
         <doc:error name="org.freedesktop.Accounts.Error.Failed">if the operation failed</doc:error>
+        <doc:error name="org.freedesktop.Accounts.Error.InvalidArguments">if the caller did not call BeginSetPassword beforehand</doc:error>
       </doc:errors>
     </doc:doc>
   </method>
Index: accountsservice-0.6.35/src/daemon.c
===================================================================
--- accountsservice-0.6.35.orig/src/daemon.c	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/src/daemon.c	2013-11-13 15:20:32.220131938 +0100
@@ -97,7 +97,8 @@
         { ERROR_USER_EXISTS, "org.freedesktop.Accounts.Error.UserExists" },
         { ERROR_USER_DOES_NOT_EXIST, "org.freedesktop.Accounts.Error.UserDoesNotExist" },
         { ERROR_PERMISSION_DENIED, "org.freedesktop.Accounts.Error.PermissionDenied" },
-        { ERROR_NOT_SUPPORTED, "org.freedesktop.Accounts.Error.NotSupported" }
+        { ERROR_NOT_SUPPORTED, "org.freedesktop.Accounts.Error.NotSupported" },
+        { ERROR_INVALID, "org.freedesktop.Accounts.Error.InvalidArguments" },
 };
 
 GQuark
@@ -128,6 +129,7 @@
           ENUM_ENTRY (ERROR_USER_DOES_NOT_EXIST, "UserDoesntExist"),
           ENUM_ENTRY (ERROR_PERMISSION_DENIED, "PermissionDenied"),
           ENUM_ENTRY (ERROR_NOT_SUPPORTED, "NotSupported"),
+          ENUM_ENTRY (ERROR_INVALID, "InvalidArguments"),
           { 0, 0, 0 }
         };
       g_assert (NUM_ERRORS == G_N_ELEMENTS (values) - 1);
Index: accountsservice-0.6.35/src/daemon.h
===================================================================
--- accountsservice-0.6.35.orig/src/daemon.h	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/src/daemon.h	2013-11-13 15:20:32.220131938 +0100
@@ -57,6 +57,7 @@
         ERROR_USER_DOES_NOT_EXIST,
         ERROR_PERMISSION_DENIED,
         ERROR_NOT_SUPPORTED,
+        ERROR_INVALID,
         NUM_ERRORS
 } Error;
 
Index: accountsservice-0.6.35/src/libaccountsservice/act-user.h
===================================================================
--- accountsservice-0.6.35.orig/src/libaccountsservice/act-user.h	2013-11-13 15:20:32.228131938 +0100
+++ accountsservice-0.6.35/src/libaccountsservice/act-user.h	2013-11-13 15:20:32.224131938 +0100
@@ -46,6 +46,12 @@
         ACT_USER_PASSWORD_MODE_NONE,
 } ActUserPasswordMode;
 
+typedef enum {
+        ACT_USER_PASSWORD_REGULAR,
+        ACT_USER_PASSWORD_HINT,
+        ACT_USER_PASSWORD_PIN
+} ActUserPasswordType;
+
 typedef struct _ActUser ActUser;
 typedef struct _ActUserClass ActUserClass;
 
@@ -106,6 +112,8 @@
 void           act_user_set_password              (ActUser     *user,
                                                    const gchar *password,
                                                    const gchar *hint);
+void           act_user_set_multiple_passwords    (ActUser     *user,
+                                                   GHashTable  *password_map);
 void           act_user_set_password_mode         (ActUser             *user,
                                                    ActUserPasswordMode  password_mode);
 void           act_user_set_locked                (ActUser    *user,
Index: accountsservice-0.6.35/src/pam-pin/Makefile.am
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ accountsservice-0.6.35/src/pam-pin/Makefile.am	2013-11-13 15:20:32.224131938 +0100
@@ -0,0 +1,16 @@
+securitydir = $(libdir)/security
+
+security_LTLIBRARIES = pam_pin.la
+
+pam_pin_la_SOURCES = pam-module.c
+pam_pin_la_LDFLAGS = -module -avoid-version
+pam_pin_la_LIBADD = $(LIBGCRYPT_LIBS) $(GIO_LIBS)
+pam_pin_la_CFLAGS = \
+	-DLOCALSTATEDIR=\""$(localstatedir)"\" \
+	-DDATADIR=\""$(datadir)"\" \
+	-DUSERDIR=\"$(localstatedir)/lib/AccountsService/users\" \
+	-DPASSWDDIR=\"$(localstatedir)/lib/AccountsService/encrypted-password\" \
+	-DLIBEXECDIR=\"$(libexecdir)\" \
+	$(LIBGCRYPT_CFLAGS) \
+	$(GIO_CFLAGS) \
+	$(WARN_CFLAGS)
Index: accountsservice-0.6.35/src/pam-pin/pam-module.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ accountsservice-0.6.35/src/pam-pin/pam-module.c	2013-11-13 15:20:32.224131938 +0100
@@ -0,0 +1,422 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <security/pam_modules.h>
+#include <security/pam_misc.h>
+#include <security/pam_ext.h>
+#include <glib.h>
+#include <gcrypt.h>
+
+/* We use AES256 in Cipher Block Chaining mode,
+   using a key derived from the PIN, salted with the machine id
+   and passed through PBKDF2.
+*/
+#define KEY_LENGTH 256/8
+#define N_ITERATIONS 100000
+
+static void
+init_libgcrypt (void)
+{
+        /* Despite the name, this is a library init function... */
+        gcry_check_version (GCRYPT_VERSION);
+}
+
+static char *
+make_key (const char *pin)
+{
+        char *key;
+        char *machine_id;
+        size_t machine_id_len;
+
+        if (!g_file_get_contents ("/etc/machine-id", &machine_id,
+                                  &machine_id_len, NULL))
+                return NULL;
+
+        key = g_malloc (KEY_LENGTH);
+
+        if (gcry_kdf_derive (pin, strlen (pin),
+                             GCRY_KDF_PBKDF2, GCRY_MD_SHA1,
+                             machine_id, machine_id_len,
+                             N_ITERATIONS, KEY_LENGTH, key)) {
+                g_free (key);
+                key = NULL;
+        }
+
+        g_free (machine_id);
+        return key;
+}
+
+static size_t
+get_block_size (void)
+{
+        size_t sz;
+
+        gcry_cipher_algo_info (GCRY_CIPHER_AES256, GCRYCTL_GET_BLKLEN, NULL, &sz);
+        return sz;
+}
+
+static char *
+make_iv (void)
+{
+        char *iv;
+        size_t sz;
+
+        sz = get_block_size ();
+        iv = g_malloc (sz);
+        memset(iv, 0, sz);
+
+        return iv;
+}
+
+static char *
+make_padded_password (const char *password,
+                      size_t     *out_size)
+{
+        size_t total;
+        size_t blksz;
+        size_t current;
+        char *padded;
+
+        blksz = get_block_size ();
+        current = strlen (password);
+
+        if (current % blksz) {
+                total = (current / blksz + 1) * blksz;
+                padded = g_malloc (total + 1);
+                stpncpy (padded, password, total + 1);
+
+                *out_size = total;
+                return padded;
+        } else {
+                *out_size = current;
+                return g_strdup (password);
+        }
+}
+
+static char *
+decode_password (const char *ciphertext,
+                 size_t      ciphertext_len,
+                 const char *pin)
+{
+        gcry_cipher_hd_t hd = NULL;
+        char *key = NULL;
+        char *iv = NULL;
+        char *password = NULL;
+
+        key = make_key (pin);
+        if (key == NULL)
+                return NULL;
+
+        if (gcry_cipher_open (&hd, GCRY_CIPHER_AES256,
+                              GCRY_CIPHER_MODE_CBC, 0))
+                goto out;
+
+        iv = make_iv ();
+        gcry_cipher_setiv (hd, iv, get_block_size ());
+        gcry_cipher_setkey (hd, key, KEY_LENGTH);
+
+        password = g_malloc (ciphertext_len + 1);
+        if (password == NULL)
+                goto out;
+
+        if (gcry_cipher_decrypt (hd, password, ciphertext_len,
+                                 ciphertext, ciphertext_len)) {
+                g_free (password);
+                password = NULL;
+        } else {
+                password[ciphertext_len] = 0;
+        }
+
+ out:
+        g_free (key);
+        g_free (iv);
+        gcry_cipher_close (hd);
+        return password;
+}
+
+static char *
+encode_password (const char *password,
+                 const char *pin,
+                 size_t     *ciphertext_len)
+{
+        gcry_cipher_hd_t hd = NULL;
+        char *key = NULL;
+        char *iv = NULL;
+        char *ciphertext = NULL;
+        char *padded = NULL;
+        size_t password_len;
+
+        key = make_key (pin);
+        if (key == NULL)
+                return NULL;
+
+        if (gcry_cipher_open (&hd, GCRY_CIPHER_AES256,
+                              GCRY_CIPHER_MODE_CBC, 0))
+                goto out;
+
+        iv = make_iv ();
+        gcry_cipher_setiv (hd, iv, get_block_size ());
+        gcry_cipher_setkey (hd, key, KEY_LENGTH);
+
+        padded = make_padded_password (password, &password_len);
+        *ciphertext_len = password_len;
+        ciphertext = g_malloc(*ciphertext_len + 1);
+
+        if (gcry_cipher_encrypt (hd, ciphertext, password_len,
+                                 padded, password_len)) {
+                g_free (ciphertext);
+                ciphertext = NULL;
+        }
+
+ out:
+        g_free (key);
+        g_free (iv);
+        g_free (padded);
+        gcry_cipher_close (hd);
+        return ciphertext;
+}
+
+int
+pam_sm_authenticate (pam_handle_t  *handle,
+		     int            flags,
+		     int            argc,
+		     const char   **argv)
+{
+        const char *username;
+        char *filename = NULL;
+        char *ciphertext = NULL;
+        size_t ciphertext_len;
+        const char *pin;
+        char *password;
+        int result;
+        GError *error;
+
+        init_libgcrypt ();
+
+        /* We require CAP_DAC_OVERRIDE to access the encrypted password
+           (like /etc/shadow) */
+        if (g_mkdir_with_parents (PASSWDDIR, 0) < 0)
+                return PAM_AUTHINFO_UNAVAIL;
+
+        /* Username must not be localized, there is an exact match
+           in gnome-shell */
+        result = pam_get_user (handle, &username, "Username: ");
+        if (result != PAM_SUCCESS)
+                return result;
+
+        filename = g_build_filename (PASSWDDIR, username, NULL);
+
+        error = NULL;
+        if (!g_file_get_contents (filename, &ciphertext, &ciphertext_len, &error)) {
+                if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+                        result = PAM_AUTHINFO_UNAVAIL;
+                else
+                        result = PAM_AUTHTOK_ERR;
+
+                g_error_free (error);
+                goto out;
+        }
+
+        /* Do not translate this string, it's a marker used by gnome-shell
+           to show the PIN keypad */
+        result = pam_get_authtok (handle, PAM_AUTHTOK, &pin, "PIN");
+        if (result != PAM_SUCCESS)
+                goto out;
+
+        password = decode_password (ciphertext, ciphertext_len, pin);
+        if (password)
+                result = pam_set_item (handle, PAM_AUTHTOK, password);
+        else
+                result = PAM_AUTH_ERR;
+
+ out:
+        g_free (ciphertext);
+        g_free (filename);
+
+        return result;
+}
+
+static int
+do_preliminary_check(pam_handle_t *handle)
+{
+        char *filename;
+        const char *username;
+        int result, fd;
+
+        if (g_mkdir_with_parents (PASSWDDIR, 0) < 0)
+                return PAM_AUTHINFO_UNAVAIL;
+
+        result = pam_get_user (handle, &username, "Username: ");
+        if (result != PAM_SUCCESS)
+                return result;
+
+        /* Check that we can access and write the database */
+        filename = g_build_filename (PASSWDDIR, username, NULL);
+
+        fd = open(filename, O_RDWR);
+        if (fd < 0 && errno != ENOENT)
+                result = PAM_AUTHTOK_ERR;
+        else
+                result = PAM_SUCCESS;
+
+        g_free(filename);
+        if (fd >= 0)
+                close(fd);
+        return result;
+}
+
+static int
+request_and_encrypt_pin(pam_handle_t  *handle,
+                        const char    *username,
+                        char         **ciphertext,
+                        size_t        *ciphertext_len)
+{
+        const char *password;
+        char *pin;
+        int result;
+
+        *ciphertext = NULL;
+
+        result = pam_get_authtok (handle, PAM_AUTHTOK, &password, "Password: ");
+        if (result != PAM_SUCCESS)
+                return result;
+
+        result = pam_prompt (handle, PAM_PROMPT_ECHO_OFF, &pin, "PIN");
+        if (result != PAM_SUCCESS)
+                return result;
+
+        if (pin == NULL || strlen (pin) == 0) {
+                result = PAM_SUCCESS;
+        } else {
+                *ciphertext = encode_password (password, pin, ciphertext_len);
+                if (!*ciphertext)
+                        result = PAM_AUTHTOK_ERR;
+                else
+                        result = PAM_SUCCESS;
+        }
+
+        /* Don't use g_free here, the string comes from PAM */
+        free(pin);
+        return result;
+}
+
+static int
+do_change_authtok(pam_handle_t  *handle)
+{
+        const char *username;
+        char *filename;
+        char *ciphertext;
+        size_t ciphertext_len;
+        int result, ok;
+        uid_t ruid, euid;
+
+        result = pam_get_user (handle, &username, "Username: ");
+        if (result != PAM_SUCCESS)
+                return result;
+
+        filename = g_build_filename (PASSWDDIR, username, NULL);
+
+        ciphertext = NULL;
+        result = PAM_AUTHTOK_ERR;
+
+        /* libgcrypt has the interesting habit to drop
+           all privileges when allocating secure memory,
+           and it does so with setuid() instead of seteuid()
+           Workaround that by dropping the priviliges ourselves
+           and then regaining them before leaving control to
+           the rest of the PAM stack.
+
+           This is not thread-safe and very, very bad in general!
+        */
+        ruid = getuid();
+        euid = geteuid();
+
+        if (euid != ruid) {
+                ok = seteuid(ruid);
+                if (ok < 0)
+                        goto out;
+        }
+
+        result = request_and_encrypt_pin (handle, username,
+                                          &ciphertext, &ciphertext_len);
+
+        ok = seteuid (euid);
+
+        if (result != PAM_SUCCESS)
+                goto out;
+        if (ok < 0) {
+                result = PAM_AUTHTOK_ERR;
+                goto out;
+        }
+
+        if (ciphertext) {
+                if (!g_file_set_contents (filename, ciphertext, ciphertext_len, NULL))
+                        goto out;
+
+                /* Set file mode to 0, and require DAC_OVERRIDE to access it */
+                if (chmod (filename, 0) < 0)
+                        goto out;
+        } else {
+                if (unlink (filename) < 0 && errno != ENOENT)
+                        goto out;
+        }
+
+        result = PAM_SUCCESS;
+
+ out:
+        g_free(filename);
+        g_free(ciphertext);
+        return result;
+}
+
+int
+pam_sm_setcred(pam_handle_t  *handle,
+               int            flags,
+               int            argc,
+               const char   **argv)
+{
+        return PAM_SUCCESS;
+}
+
+int
+pam_sm_chauthtok (pam_handle_t  *handle,
+                  int            flags,
+                  int            argc,
+                  const char   **argv)
+{
+        init_libgcrypt ();
+
+        if (flags & PAM_PRELIM_CHECK)
+                return do_preliminary_check(handle);
+        else if (flags & PAM_UPDATE_AUTHTOK)
+                return do_change_authtok(handle);
+
+        return PAM_SUCCESS;
+}
