[gdm/wip/multi-stack: 7/9] greeter: Add a plugin based extension system to greeter



commit e93ce1b00beac289480184a98a405b3afb442c35
Author: Ray Strode <rstrode redhat com>
Date:   Fri Jan 30 23:57:31 2009 -0500

    greeter: Add a plugin based extension system to greeter
    
    This allows plugins to drive which PAM conversations
    get run.  This commit just adds one plugin "password"
    which does the one PAM conversation we've traditionally
    run.

 common/gdm-marshal.list                            |    1 +
 configure.ac                                       |   46 +
 gui/simple-greeter/Makefile.am                     |   13 +
 gui/simple-greeter/gdm-extension-list.c            |  388 ++++++
 gui/simple-greeter/gdm-extension-list.h            |   70 ++
 gui/simple-greeter/gdm-greeter-client.c            |   21 +
 gui/simple-greeter/gdm-greeter-client.h            |    2 +
 gui/simple-greeter/gdm-greeter-login-window.c      | 1307 +++++++++++++++-----
 gui/simple-greeter/gdm-greeter-login-window.h      |   29 +-
 gui/simple-greeter/gdm-greeter-login-window.ui     |   67 +-
 gui/simple-greeter/gdm-greeter-session.c           |  133 ++-
 gui/simple-greeter/gdm-user-chooser-widget.c       |   23 +-
 gui/simple-greeter/libgdmsimplegreeter/Makefile.am |   43 +
 .../libgdmsimplegreeter/gdm-login-extension.c      |  278 +++++
 .../libgdmsimplegreeter/gdm-login-extension.h      |  128 ++
 .../libgdmsimplegreeter/gdmsimplegreeter.pc        |   11 +
 .../libgdmsimplegreeter/gdmsimplegreeter.pc.in     |   11 +
 gui/simple-greeter/plugins/Makefile.am             |    1 +
 gui/simple-greeter/plugins/password/Makefile.am    |   51 +
 gui/simple-greeter/plugins/password/gdm-password   |   19 +
 .../plugins/password/gdm-password-extension.c      |  446 +++++++
 .../plugins/password/gdm-password-extension.h      |   56 +
 .../plugins/password/gdm-password.pam              |   19 +
 gui/simple-greeter/plugins/password/page.ui        |   57 +
 po/POTFILES.in                                     |    1 +
 25 files changed, 2843 insertions(+), 378 deletions(-)
---
diff --git a/common/gdm-marshal.list b/common/gdm-marshal.list
index d5455e1..d8a9e72 100644
--- a/common/gdm-marshal.list
+++ b/common/gdm-marshal.list
@@ -5,3 +5,4 @@ VOID:STRING,STRING
 VOID:UINT,UINT
 VOID:STRING,INT
 VOID:DOUBLE
+BOOLEAN:STRING
diff --git a/configure.ac b/configure.ac
index 3234ed8..958a5b6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -18,6 +18,22 @@ AC_PROG_CXX
 AM_PROG_CC_C_O
 AC_PROG_LIBTOOL()
 
+## increment if the plugin interface has additions, changes, removals.
+LT_CURRENT=1
+
+## increment any time the source changes; set to
+##  0 if you increment CURRENT
+LT_REVISION=0
+
+## increment if any interfaces have been added; set to 0
+## if any interfaces have been changed or removed. removal has
+## precedence over adding, so set to 0 if both happened.
+LT_AGE=0
+
+AC_SUBST(LT_CURRENT)
+AC_SUBST(LT_REVISION)
+AC_SUBST(LT_AGE)
+
 AC_HEADER_STDC
 
 AC_SUBST(VERSION)
@@ -76,6 +92,8 @@ PKG_CHECK_MODULES(DAEMON,
 AC_SUBST(DAEMON_CFLAGS)
 AC_SUBST(DAEMON_LIBS)
 
+GLIB_GSETTINGS
+
 PKG_CHECK_MODULES(XLIB, x11 xau xrandr, ,
   [AC_PATH_XTRA
     if test "x$no_x" = xyes; then
@@ -193,6 +211,15 @@ AC_ARG_WITH(dmconfdir,
 AC_SUBST(dmconfdir)
 
 dnl ---------------------------------------------------------------------------
+dnl - Configuration file stuff
+dnl ---------------------------------------------------------------------------
+AC_ARG_WITH(extensionsdatadir,
+            AS_HELP_STRING([--with-extensions-datadir],
+                           [directory where extensions store data, default=DATADIR/gdm/simple-greeter/extensions]),
+            extensionsdatadir=${withval}, extensionsdatadir=${datadir}/gdm/simple-greeter/extensions)
+AC_SUBST(extensionsdatadir)
+
+dnl ---------------------------------------------------------------------------
 dnl - Configure arguments
 dnl ---------------------------------------------------------------------------
 
@@ -1259,6 +1286,21 @@ fi
 
 AC_SUBST(GDM_SCREENSHOT_DIR)
 
+dnl ---------------------------------------------------------------------------
+dnl - Directory for simple greeter plugins
+dnl ---------------------------------------------------------------------------
+
+AC_ARG_WITH(simple-greeter-plugins-dir,
+            AS_HELP_STRING([--with-simple-greeter-plugins-dir=<dir>],
+                           [simple greeter plugins directory]))
+
+if ! test -z "$with_simple_greeter_plugins_dir"; then
+   GDM_SIMPLE_GREETER_PLUGINS_DIR=$with_simple_greeter_plugins_dir
+else
+   GDM_SIMPLE_GREETER_PLUGINS_DIR=${libdir}/gdm/simple-greeter/plugins
+fi
+
+AC_SUBST(GDM_SIMPLE_GREETER_PLUGINS_DIR)
 
 dnl ---------------------------------------------------------------------------
 dnl - Finish
@@ -1384,6 +1426,10 @@ daemon/Makefile
 docs/Makefile
 gui/Makefile
 gui/simple-greeter/Makefile
+gui/simple-greeter/libgdmsimplegreeter/Makefile
+gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc
+gui/simple-greeter/plugins/Makefile
+gui/simple-greeter/plugins/password/Makefile
 gui/simple-chooser/Makefile
 utils/Makefile
 data/gdm.conf
diff --git a/gui/simple-greeter/Makefile.am b/gui/simple-greeter/Makefile.am
index a842c7b..10fb09e 100644
--- a/gui/simple-greeter/Makefile.am
+++ b/gui/simple-greeter/Makefile.am
@@ -1,8 +1,13 @@
 NULL =
+SUBDIRS = 				\
+	libgdmsimplegreeter		\
+	plugins				\
+	$(NULL)
 
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/common				\
 	-I$(top_builddir)/common			\
+	-I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter	\
 	-DDMCONFDIR=\""$(dmconfdir)"\"			\
 	-DGDMCONFDIR=\"$(gdmconfdir)\"                  \
 	-DDATADIR=\""$(datadir)"\"		 	\
@@ -15,6 +20,7 @@ AM_CPPFLAGS = \
 	-DGDM_CACHE_DIR=\""$(localstatedir)/cache/gdm"\"	\
 	-DAT_SPI_REGISTRYD_DIR="\"$(AT_SPI_REGISTRYD_DIR)\""	\
 	$(UPOWER_CFLAGS)				\
+	-DGDM_SIMPLE_GREETER_PLUGINS_DIR="\"$(GDM_SIMPLE_GREETER_PLUGINS_DIR)\""\
 	$(DISABLE_DEPRECATED_CFLAGS)			\
 	$(GTK_CFLAGS)					\
 	$(SIMPLE_GREETER_CFLAGS)			\
@@ -60,10 +66,13 @@ test_greeter_login_window_SOURCES = 	\
 	gdm-user-chooser-widget.c	\
 	gdm-user-chooser-dialog.h	\
 	gdm-user-chooser-dialog.c	\
+	gdm-extension-list.h		\
+	gdm-extension-list.c		\
 	$(NULL)
 
 test_greeter_login_window_LDADD =	\
 	$(top_builddir)/common/libgdmcommon.la	\
+	$(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la	\
 	$(COMMON_LIBS)			\
 	$(SIMPLE_GREETER_LIBS)		\
 	$(RBAC_LIBS)			\
@@ -104,6 +113,7 @@ test_greeter_panel_SOURCES = 	\
 
 test_greeter_panel_LDADD =	\
 	$(top_builddir)/common/libgdmcommon.la	\
+	$(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la	\
 	$(SIMPLE_GREETER_LIBS)		\
 	$(GTK_LIBS)			\
 	$(GCONF_LIBS)			\
@@ -251,10 +261,13 @@ gdm_simple_greeter_SOURCES =  		\
 	gdm-session-option-widget.c	\
 	gdm-user-chooser-widget.h	\
 	gdm-user-chooser-widget.c	\
+	gdm-extension-list.h		\
+	gdm-extension-list.c		\
 	$(NULL)
 
 gdm_simple_greeter_LDADD = 		\
 	$(top_builddir)/common/libgdmcommon.la	\
+	$(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la	\
 	$(COMMON_LIBS)			\
 	$(EXTRA_GREETER_LIBS)   	\
 	$(SIMPLE_GREETER_LIBS)		\
diff --git a/gui/simple-greeter/gdm-extension-list.c b/gui/simple-greeter/gdm-extension-list.c
new file mode 100644
index 0000000..9309565
--- /dev/null
+++ b/gui/simple-greeter/gdm-extension-list.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "gdm-extension-list.h"
+
+#define GDM_EXTENSION_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_EXTENSION_LIST, GdmExtensionListPrivate))
+typedef gboolean (* GdmExtensionListForeachFunc) (GdmExtensionList    *extension_list,
+                                                  GdmLoginExtension *extension,
+                                                  gpointer             data);
+
+
+struct GdmExtensionListPrivate
+{
+        GtkWidget *box;
+        GList     *extensions;
+};
+
+enum {
+        ACTIVATED = 0,
+        DEACTIVATED,
+        NUMBER_OF_SIGNALS
+};
+
+static guint    signals[NUMBER_OF_SIGNALS];
+
+static void     gdm_extension_list_class_init  (GdmExtensionListClass *klass);
+static void     gdm_extension_list_init        (GdmExtensionList      *extension_list);
+static void     gdm_extension_list_finalize    (GObject          *object);
+
+G_DEFINE_TYPE (GdmExtensionList, gdm_extension_list, GTK_TYPE_ALIGNMENT);
+
+static void
+on_extension_toggled (GdmExtensionList *widget,
+                      GtkRadioButton          *button)
+{
+        GdmLoginExtension *extension;
+
+        extension = g_object_get_data (G_OBJECT (button), "gdm-extension");
+
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
+
+                GList     *extension_node;
+                /* Sort the list such that the extensions the user clicks last end
+                 * up first.  This doesn't change the order in which the extensions
+                 * appear in the UI, but will affect which extensions we implicitly
+                 * activate if the currently active extension gets disabled.
+                 */
+                extension_node = g_list_find (widget->priv->extensions, extension);
+                if (extension_node != NULL) {
+                        widget->priv->extensions = g_list_delete_link (widget->priv->extensions, extension_node);
+                        widget->priv->extensions = g_list_prepend (widget->priv->extensions,
+                                                              extension);
+                }
+
+                g_signal_emit (widget, signals[ACTIVATED], 0, extension);
+        } else {
+                g_signal_emit (widget, signals[DEACTIVATED], 0, extension);
+        }
+}
+
+static GdmLoginExtension *
+gdm_extension_list_foreach_extension (GdmExtensionList           *extension_list,
+                                      GdmExtensionListForeachFunc search_func,
+                                      gpointer                           data)
+{
+        GList *node;
+
+        for (node = extension_list->priv->extensions; node != NULL; node = node->next) {
+                GdmLoginExtension *extension;
+
+                extension = node->data;
+
+                if (search_func (extension_list, extension, data)) {
+                        return g_object_ref (extension);
+                }
+        }
+
+        return NULL;
+}
+
+static void
+on_extension_enabled (GdmExtensionList *extension_list,
+                      GdmLoginExtension     *extension)
+{
+        GtkWidget *button;
+
+        button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button");
+
+        gtk_widget_set_sensitive (button, TRUE);
+}
+
+static gboolean
+gdm_extension_list_set_active_extension (GdmExtensionList    *widget,
+                                         GdmLoginExtension *extension)
+{
+        GtkWidget *button;
+        gboolean   was_sensitive;
+        gboolean   was_activated;
+
+        if (!gdm_login_extension_is_visible (extension)) {
+                return FALSE;
+        }
+
+        was_sensitive = gtk_widget_get_sensitive (GTK_WIDGET (widget));
+        gtk_widget_set_sensitive (GTK_WIDGET (widget), TRUE);
+
+        button = GTK_WIDGET (g_object_get_data (G_OBJECT (extension),
+                             "gdm-extension-list-button"));
+
+        was_activated = FALSE;
+        if (gtk_widget_is_sensitive (button)) {
+                if (gtk_widget_activate (button)) {
+                        was_activated = TRUE;
+                }
+        }
+
+        gtk_widget_set_sensitive (GTK_WIDGET (widget), was_sensitive);
+        return was_activated;
+}
+
+static void
+activate_first_available_extension (GdmExtensionList *extension_list)
+{
+        GList *node;
+
+        node = extension_list->priv->extensions;
+        while (node != NULL) {
+                GdmLoginExtension   *extension;
+
+                extension = GDM_LOGIN_EXTENSION (node->data);
+
+                if (gdm_extension_list_set_active_extension (extension_list, extension)) {
+                        break;
+                }
+
+                node = node->next;
+        }
+}
+
+static void
+on_extension_disabled (GdmExtensionList *extension_list,
+                       GdmLoginExtension     *extension)
+{
+        GtkWidget *button;
+        gboolean   was_active;
+
+        button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button");
+        was_active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+        gtk_widget_set_sensitive (button, FALSE);
+
+        if (was_active) {
+                activate_first_available_extension (extension_list);
+        }
+}
+
+void
+gdm_extension_list_add_extension (GdmExtensionList  *extension_list,
+                                  GdmLoginExtension *extension)
+{
+        GtkWidget *image;
+        GtkWidget *button;
+        GIcon     *icon;
+        char      *description;
+
+        if (extension_list->priv->extensions == NULL) {
+                button = gtk_radio_button_new (NULL);
+        } else {
+                GdmLoginExtension *previous_extension;
+                GtkRadioButton *previous_button;
+
+                previous_extension = GDM_LOGIN_EXTENSION (extension_list->priv->extensions->data);
+                previous_button = GTK_RADIO_BUTTON (g_object_get_data (G_OBJECT (previous_extension), "gdm-extension-list-button"));
+                button = gtk_radio_button_new_from_widget (previous_button);
+        }
+        g_object_set_data (G_OBJECT (extension), "gdm-extension-list-button", button);
+
+        g_object_set (G_OBJECT (button), "draw-indicator", FALSE, NULL);
+        g_object_set_data (G_OBJECT (button), "gdm-extension", extension);
+        g_signal_connect_swapped (button, "toggled",
+                                  G_CALLBACK (on_extension_toggled),
+                                  extension_list);
+
+        gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
+        gtk_widget_set_sensitive (button, gdm_login_extension_is_enabled (extension));
+
+        g_signal_connect_swapped (G_OBJECT (extension), "enabled",
+                                  G_CALLBACK (on_extension_enabled),
+                                  extension_list);
+
+        g_signal_connect_swapped (G_OBJECT (extension), "disabled",
+                                  G_CALLBACK (on_extension_disabled),
+                                  extension_list);
+
+        icon = gdm_login_extension_get_icon (extension);
+        image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_SMALL_TOOLBAR);
+        g_object_unref (icon);
+
+        gtk_widget_show (image);
+        gtk_container_add (GTK_CONTAINER (button), image);
+        description = gdm_login_extension_get_description (extension);
+        gtk_widget_set_tooltip_text (button, description);
+        g_free (description);
+        gtk_widget_show (button);
+
+        gtk_container_add (GTK_CONTAINER (extension_list->priv->box), button);
+        extension_list->priv->extensions = g_list_append (extension_list->priv->extensions,
+                                                g_object_ref (extension));
+
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
+                g_signal_emit (extension_list, signals[ACTIVATED], 0, extension);
+        }
+}
+
+void
+gdm_extension_list_remove_extension (GdmExtensionList    *extension_list,
+                                     GdmLoginExtension *extension)
+{
+        GtkWidget *button;
+        GList     *node;
+
+        node = g_list_find (extension_list->priv->extensions, extension);
+
+        if (node == NULL) {
+                return;
+        }
+
+        extension_list->priv->extensions = g_list_delete_link (extension_list->priv->extensions, node);
+
+        button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button");
+
+        if (button != NULL) {
+            g_signal_handlers_disconnect_by_func (G_OBJECT (extension),
+                                                  G_CALLBACK (on_extension_enabled),
+                                                  extension_list);
+            g_signal_handlers_disconnect_by_func (G_OBJECT (extension),
+                                                  G_CALLBACK (on_extension_disabled),
+                                                  extension_list);
+            gtk_widget_destroy (button);
+            g_object_set_data (G_OBJECT (extension), "gdm-extension-list-button", NULL);
+        }
+
+        g_object_unref (extension);
+
+        activate_first_available_extension (extension_list);
+}
+
+static void
+gdm_extension_list_class_init (GdmExtensionListClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gdm_extension_list_finalize;
+
+        signals [ACTIVATED] = g_signal_new ("activated",
+                                            G_TYPE_FROM_CLASS (object_class),
+                                            G_SIGNAL_RUN_FIRST,
+                                            G_STRUCT_OFFSET (GdmExtensionListClass, activated),
+                                            NULL,
+                                            NULL,
+                                            g_cclosure_marshal_VOID__OBJECT,
+                                            G_TYPE_NONE,
+                                            1, G_TYPE_OBJECT);
+
+        signals [DEACTIVATED] = g_signal_new ("deactivated",
+                                            G_TYPE_FROM_CLASS (object_class),
+                                            G_SIGNAL_RUN_FIRST,
+                                            G_STRUCT_OFFSET (GdmExtensionListClass, deactivated),
+                                            NULL,
+                                            NULL,
+                                            g_cclosure_marshal_VOID__OBJECT,
+                                            G_TYPE_NONE,
+                                            1, G_TYPE_OBJECT);
+
+        g_type_class_add_private (klass, sizeof (GdmExtensionListPrivate));
+}
+
+static void
+gdm_extension_list_init (GdmExtensionList *widget)
+{
+        widget->priv = GDM_EXTENSION_LIST_GET_PRIVATE (widget);
+
+        gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0);
+        gtk_alignment_set (GTK_ALIGNMENT (widget), 0.0, 0.0, 0, 0);
+
+        widget->priv->box = gtk_hbox_new (TRUE, 2);
+        gtk_widget_show (widget->priv->box);
+        gtk_container_add (GTK_CONTAINER (widget),
+                           widget->priv->box);
+}
+
+static void
+gdm_extension_list_finalize (GObject *object)
+{
+        GdmExtensionList *widget;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_EXTENSION_LIST (object));
+
+        widget = GDM_EXTENSION_LIST (object);
+
+        g_list_foreach (widget->priv->extensions, (GFunc) g_object_unref, NULL);
+        g_list_free (widget->priv->extensions);
+
+        G_OBJECT_CLASS (gdm_extension_list_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_extension_list_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_EXTENSION_LIST, NULL);
+
+        return GTK_WIDGET (object);
+}
+
+static gboolean
+gdm_extension_list_extension_is_active (GdmExtensionList *extension_list,
+                                        GdmLoginExtension     *extension)
+{
+        GtkWidget *button;
+
+        button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button");
+
+        return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+}
+
+GdmLoginExtension *
+gdm_extension_list_get_active_extension (GdmExtensionList *widget)
+{
+        return gdm_extension_list_foreach_extension (widget,
+                                                     (GdmExtensionListForeachFunc)
+                                                     gdm_extension_list_extension_is_active,
+                                                     NULL);
+}
+
+int
+gdm_extension_list_get_number_of_visible_extensions (GdmExtensionList *widget)
+{
+        GList *node;
+        int number_of_visible_extensions;
+
+        number_of_visible_extensions = 0;
+        for (node = widget->priv->extensions; node != NULL; node = node->next) {
+                GdmLoginExtension *extension;
+
+                extension = node->data;
+
+                if (gdm_login_extension_is_enabled (extension) && gdm_login_extension_is_visible (extension)) {
+                        number_of_visible_extensions++;
+                }
+        }
+
+        return number_of_visible_extensions;
+}
diff --git a/gui/simple-greeter/gdm-extension-list.h b/gui/simple-greeter/gdm-extension-list.h
new file mode 100644
index 0000000..acce3f8
--- /dev/null
+++ b/gui/simple-greeter/gdm-extension-list.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_EXTENSION_LIST_H
+#define __GDM_EXTENSION_LIST_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "gdm-login-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_EXTENSION_LIST         (gdm_extension_list_get_type ())
+#define GDM_EXTENSION_LIST(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_EXTENSION_LIST, GdmExtensionList))
+#define GDM_EXTENSION_LIST_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_EXTENSION_LIST, GdmExtensionListClass))
+#define GDM_IS_EXTENSION_LIST(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_EXTENSION_LIST))
+#define GDM_IS_EXTENSION_LIST_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_EXTENSION_LIST))
+#define GDM_EXTENSION_LIST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_EXTENSION_LIST, GdmExtensionListClass))
+
+typedef struct GdmExtensionListPrivate GdmExtensionListPrivate;
+typedef struct _GdmExtensionList GdmExtensionList;
+
+struct _GdmExtensionList
+{
+        GtkAlignment        parent;
+        GdmExtensionListPrivate *priv;
+};
+
+typedef struct
+{
+        GtkAlignmentClass       parent_class;
+
+        void (* deactivated)    (GdmExtensionList    *widget,
+                                 GdmLoginExtension *extension);
+        void (* activated)      (GdmExtensionList    *widget,
+                                 GdmLoginExtension *extension);
+} GdmExtensionListClass;
+
+GType       gdm_extension_list_get_type               (void);
+GtkWidget * gdm_extension_list_new                    (void);
+
+GdmLoginExtension *gdm_extension_list_get_active_extension (GdmExtensionList    *widget);
+void               gdm_extension_list_add_extension        (GdmExtensionList    *widget,
+                                                            GdmLoginExtension *extension);
+void               gdm_extension_list_remove_extension     (GdmExtensionList    *widget,
+                                                            GdmLoginExtension *extension);
+
+int         gdm_extension_list_get_number_of_visible_extensions (GdmExtensionList *widget);
+G_END_DECLS
+
+#endif /* __GDM_EXTENSION_LIST_H */
diff --git a/gui/simple-greeter/gdm-greeter-client.c b/gui/simple-greeter/gdm-greeter-client.c
index 65d21d9..312b52c 100644
--- a/gui/simple-greeter/gdm-greeter-client.c
+++ b/gui/simple-greeter/gdm-greeter-client.c
@@ -65,6 +65,7 @@ enum {
         SECRET_INFO_QUERY,
         SERVICE_UNAVAILABLE,
         READY,
+        CONVERSATION_STOPPED,
         RESET,
         AUTHENTICATION_FAILED,
         SELECTED_USER_CHANGED,
@@ -271,6 +272,13 @@ on_ready (GdmGreeterClient *client,
 }
 
 static void
+on_conversation_stopped (GdmGreeterClient *client,
+                         DBusMessage      *message)
+{
+        emit_string_signal_for_message (client, "ConversationStopped", message, CONVERSATION_STOPPED);
+}
+
+static void
 on_reset (GdmGreeterClient *client,
           DBusMessage      *message)
 {
@@ -742,6 +750,8 @@ client_dbus_handle_message (DBusConnection *connection,
                 on_service_unavailable (client, message);
         } else if (dbus_message_is_signal (message, GREETER_SERVER_DBUS_INTERFACE, "Ready")) {
                 on_ready (client, message);
+        } else if (dbus_message_is_signal (message, GREETER_SERVER_DBUS_INTERFACE, "ConversationStopped")) {
+                on_conversation_stopped (client, message);
         } else if (dbus_message_is_signal (message, GREETER_SERVER_DBUS_INTERFACE, "Reset")) {
                 on_reset (client, message);
         } else if (dbus_message_is_signal (message, GREETER_SERVER_DBUS_INTERFACE, "AuthenticationFailed")) {
@@ -992,6 +1002,17 @@ gdm_greeter_client_class_init (GdmGreeterClientClass *klass)
                               G_TYPE_NONE,
                               1, G_TYPE_STRING);
 
+        gdm_greeter_client_signals[CONVERSATION_STOPPED] =
+                g_signal_new ("conversation-stopped",
+                              G_OBJECT_CLASS_TYPE (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmGreeterClientClass, conversation_stopped),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+
         gdm_greeter_client_signals[RESET] =
                 g_signal_new ("reset",
                               G_OBJECT_CLASS_TYPE (object_class),
diff --git a/gui/simple-greeter/gdm-greeter-client.h b/gui/simple-greeter/gdm-greeter-client.h
index 124f53d..965035d 100644
--- a/gui/simple-greeter/gdm-greeter-client.h
+++ b/gui/simple-greeter/gdm-greeter-client.h
@@ -63,6 +63,8 @@ typedef struct
                                           const char        *service_name);
         void (* ready)                   (GdmGreeterClient  *client,
                                           const char        *service_name);
+        void (* conversation_stopped)    (GdmGreeterClient  *client,
+                                          const char        *service_name);
         void (* reset)                   (GdmGreeterClient  *client);
         void (* authentication_failed)   (GdmGreeterClient  *client);
         void (* selected_user_changed)   (GdmGreeterClient  *client,
diff --git a/gui/simple-greeter/gdm-greeter-login-window.c b/gui/simple-greeter/gdm-greeter-login-window.c
index e77ad9b..fe308b1 100644
--- a/gui/simple-greeter/gdm-greeter-login-window.c
+++ b/gui/simple-greeter/gdm-greeter-login-window.c
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
  *
  * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
- * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2008, 2009 Red Hat, Inc.
  *
  * 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
@@ -17,6 +17,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  *
+ * Written by: William Jon McCann <mccann jhu edu>
+ *             Ray Strode <rstrode redhat com>
+ *
  */
 
 #include "config.h"
@@ -50,13 +53,17 @@
 #include <dbus/dbus-glib.h>
 #include <dbus/dbus-glib-lowlevel.h>
 
+#include "gdm-marshal.h"
+
 #include "gdm-settings-client.h"
 #include "gdm-settings-keys.h"
 #include "gdm-profile.h"
 
+#include "gdm-greeter-client.h"
 #include "gdm-greeter-login-window.h"
 #include "gdm-user-chooser-widget.h"
 #include "gdm-session-option-widget.h"
+#include "gdm-extension-list.h"
 
 #ifdef HAVE_PAM
 #include <security/pam_appl.h>
@@ -96,6 +103,7 @@ enum {
         MODE_TIMED_LOGIN,
         MODE_SELECTION,
         MODE_AUTHENTICATION,
+        MODE_MULTIPLE_AUTHENTICATION,
 };
 
 enum {
@@ -109,19 +117,24 @@ struct GdmGreeterLoginWindowPrivate
         GtkBuilder      *builder;
         GtkWidget       *session_option_widget;
         GtkWidget       *user_chooser;
+        GtkWidget       *extension_list;
         GtkWidget       *auth_banner_label;
         GtkWidget       *current_button;
-
+        GtkWidget       *auth_page_box;
         guint            display_is_local : 1;
         guint            user_chooser_loaded : 1;
-        gboolean         session_ready_to_start : 1;
         GConfClient     *client;
+        GList           *extensions;
+        GdmLoginExtension *active_extension;
+        GList           *extensions_to_enable;
+        GList           *extensions_to_stop;
 
         gboolean         banner_message_enabled;
         guint            gconf_cnxn;
 
         guint            last_mode;
         guint            dialog_mode;
+        guint            next_mode;
 
         gboolean         user_list_disabled;
         guint            num_queries;
@@ -135,27 +148,16 @@ struct GdmGreeterLoginWindowPrivate
         guint            login_button_handler_id;
         guint            start_session_handler_id;
 
-        GQueue          *message_queue;
-        guint            message_timeout_id;
-        guint            message_queue_empty_reset_dialog_mode;
+        char            *service_name_of_session_ready_to_start;
 };
 
-typedef enum {
-        QUEUE_MESSAGE_TYPE_INFO,
-        QUEUE_MESSAGE_TYPE_PROBLEM
-} QueuedMessageType;
-
-typedef struct {
-        char              *text;
-        QueuedMessageType  type;
-} QueuedMessage;
-
 enum {
         PROP_0,
         PROP_DISPLAY_IS_LOCAL,
 };
 
 enum {
+        START_CONVERSATION,
         BEGIN_AUTO_LOGIN,
         BEGIN_VERIFICATION,
         BEGIN_VERIFICATION_FOR_USER,
@@ -182,6 +184,13 @@ static void     switch_mode                 (GdmGreeterLoginWindow *login_window
 static void     update_banner_message       (GdmGreeterLoginWindow *login_window);
 static void     reset_dialog                (GdmGreeterLoginWindow *login_window,
                                              guint                  dialog_mode);
+static void     gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window,
+                                                                   const char            *service_name);
+static void     handle_stopped_conversation (GdmGreeterLoginWindow *login_window,
+                                             const char            *service_name);
+
+static void     begin_single_service_verification (GdmGreeterLoginWindow *login_window,
+                                                   const char            *service_name);
 
 G_DEFINE_TYPE (GdmGreeterLoginWindow, gdm_greeter_login_window, GTK_TYPE_WINDOW)
 
@@ -207,9 +216,6 @@ set_sensitive (GdmGreeterLoginWindow *login_window,
 {
         GtkWidget *box;
 
-        box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-input-box"));
-        gtk_widget_set_sensitive (box, sensitive);
-
         box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "buttonbox"));
         gtk_widget_set_sensitive (box, sensitive);
 
@@ -219,117 +225,39 @@ set_sensitive (GdmGreeterLoginWindow *login_window,
 static void
 set_focus (GdmGreeterLoginWindow *login_window)
 {
-        GtkWidget *entry;
-
-        entry = GTK_WIDGET (gtk_builder_get_object (GDM_GREETER_LOGIN_WINDOW (login_window)->priv->builder, "auth-prompt-entry"));
-
         gdk_window_focus (gtk_widget_get_window (GTK_WIDGET (login_window)), GDK_CURRENT_TIME);
 
-        if (gtk_widget_get_realized (entry) && ! gtk_widget_has_focus (entry)) {
-                gtk_widget_grab_focus (entry);
+        if (login_window->priv->active_extension != NULL &&
+            gdm_login_extension_focus (login_window->priv->active_extension)) {
+                char *name;
+                name = gdm_login_extension_get_name (login_window->priv->active_extension);
+                g_debug ("GdmGreeterLoginWindow: focusing extension %s", name);
+                g_free (name);
         } else if (gtk_widget_get_realized (login_window->priv->user_chooser) && ! gtk_widget_has_focus (login_window->priv->user_chooser)) {
                 gtk_widget_grab_focus (login_window->priv->user_chooser);
         }
-}
-
-static void
-set_message (GdmGreeterLoginWindow *login_window,
-             const char            *text)
-{
-        GtkWidget *label;
-
-        label = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-message-label"));
-        gtk_label_set_text (GTK_LABEL (label), text);
-}
-
-static void
-free_queued_message (QueuedMessage *message)
-{
-        g_free (message->text);
-        g_slice_free (QueuedMessage, message);
-}
-
-static void
-purge_message_queue (GdmGreeterLoginWindow *login_window)
-{
-        if (login_window->priv->message_timeout_id) {
-                g_source_remove (login_window->priv->message_timeout_id);
-                login_window->priv->message_timeout_id = 0;
-        }
-
-        g_queue_foreach (login_window->priv->message_queue,
-                         (GFunc) free_queued_message,
-                         NULL);
-        g_queue_clear (login_window->priv->message_queue);
 
-        login_window->priv->message_queue_empty_reset_dialog_mode = MODE_UNDEFINED;
 }
 
 static gboolean
-set_next_message_or_continue (GdmGreeterLoginWindow *login_window)
-{
-        if (!g_queue_is_empty (login_window->priv->message_queue)) {
-                int duration;
-                gboolean needs_beep;
-
-                QueuedMessage *message;
-                message = (QueuedMessage *) g_queue_pop_head (login_window->priv->message_queue);
-
-                switch (message->type) {
-                        case QUEUE_MESSAGE_TYPE_INFO:
-                                duration = INFO_MESSAGE_DURATION;
-                                needs_beep = FALSE;
-                                break;
-                        case QUEUE_MESSAGE_TYPE_PROBLEM:
-                                duration = PROBLEM_MESSAGE_DURATION;
-                                needs_beep = TRUE;
-                                break;
-                        default:
-                                g_assert_not_reached ();
-                }
-                set_message (login_window, message->text);
-                login_window->priv->message_timeout_id = g_timeout_add_seconds (duration,
-                                                                                (GSourceFunc) set_next_message_or_continue,
-                                                                                login_window);
-                if (needs_beep) {
-                        gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (login_window)));
-                }
-
-                free_queued_message (message);
-        } else {
-                set_message (login_window, "");
-                login_window->priv->message_timeout_id = 0;
-
-                if (login_window->priv->message_queue_empty_reset_dialog_mode != MODE_UNDEFINED) {
-                        /* All messages have been shown, reset */
-                        g_debug ("GdmGreeterLoginWindow: finally resetting dialog");
-                        reset_dialog (login_window,
-                                      login_window->priv->message_queue_empty_reset_dialog_mode);
-
-                } else if (login_window->priv->session_ready_to_start) {
-                        /* All messages have been shown, proceed */
-                        g_debug ("GdmGreeterLoginWindow: finally starting session");
-                        g_signal_emit (login_window, signals[START_SESSION], 0);
-                }
-        }
+queue_message_for_extension (GdmLoginExtension *extension,
+                             const char          *message)
+{
+        gdm_login_extension_queue_message (extension,
+                                             GDM_SERVICE_MESSAGE_TYPE_INFO,
+                                             message);
         return FALSE;
 }
 
 static void
-queue_message (GdmGreeterLoginWindow *login_window,
-               QueuedMessageType      message_type,
-               const char            *text)
+set_message (GdmGreeterLoginWindow *login_window,
+             const char            *text)
 {
-        QueuedMessage *message = g_slice_new (QueuedMessage);
-
-        message->text = g_strdup (text);
-        message->type = message_type;
-
-        g_queue_push_tail (login_window->priv->message_queue, message);
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
 
-        if (login_window->priv->message_timeout_id == 0) {
-                set_next_message_or_continue (login_window);
-        }
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) queue_message_for_extension,
+                        (gpointer) text);
 }
 
 static void
@@ -440,19 +368,64 @@ show_widget (GdmGreeterLoginWindow *login_window,
 }
 
 static void
-on_login_button_clicked_answer_query (GtkButton             *button,
-                                      GdmGreeterLoginWindow *login_window)
+hide_extension_actions (GdmLoginExtension *extension)
 {
-        GtkWidget  *entry;
-        const char *text;
+        GtkActionGroup *actions;
 
-        set_busy (login_window);
-        set_sensitive (login_window, FALSE);
+        actions = gdm_login_extension_get_actions (extension);
+
+        if (actions != NULL) {
+                gtk_action_group_set_visible (actions, FALSE);
+                gtk_action_group_set_sensitive (actions, FALSE);
+                g_object_unref (actions);
+        }
+}
+
+static void
+grab_default_button_for_extension (GdmLoginExtension *extension)
+{
+        GtkActionGroup *actions;
+        GtkAction *action;
+        GSList    *proxies, *node;
+
+        actions = gdm_login_extension_get_actions (extension);
 
-        entry = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-prompt-entry"));
-        text = gtk_entry_get_text (GTK_ENTRY (entry));
+        if (actions == NULL) {
+                return;
+        }
+
+        action = gtk_action_group_get_action (actions, GDM_LOGIN_EXTENSION_DEFAULT_ACTION);
+        g_object_unref (actions);
+
+        if (action == NULL) {
+                return;
+        }
+
+        proxies = gtk_action_get_proxies (action);
+        for (node = proxies; node != NULL; node = node->next) {
+                GtkWidget *widget;
+
+                widget = GTK_WIDGET (node->data);
+
+                if (gtk_widget_get_can_default (widget) &&
+                    gtk_widget_get_visible (widget)) {
+                        gtk_widget_grab_default (widget);
+                        break;
+                }
+        }
+}
+
+static void
+show_extension_actions (GdmLoginExtension *extension)
+{
+        GtkActionGroup *actions;
 
-        g_signal_emit (login_window, signals[QUERY_ANSWER], 0, text);
+        actions = gdm_login_extension_get_actions (extension);
+        if (actions != NULL) {
+                gtk_action_group_set_sensitive (actions, TRUE);
+                gtk_action_group_set_visible (actions, TRUE);
+                g_object_unref (actions);
+        }
 }
 
 static void
@@ -515,13 +488,23 @@ set_log_in_button_mode (GdmGreeterLoginWindow *login_window,
 
         login_window->priv->current_button = button;
 
+        g_list_foreach (login_window->priv->extensions, (GFunc) hide_extension_actions, NULL);
+
         switch (mode) {
         case LOGIN_BUTTON_HIDDEN:
+                if (login_window->priv->active_extension != NULL) {
+                        hide_extension_actions (login_window->priv->active_extension);
+                }
+
                 gtk_widget_hide (button);
                 break;
         case LOGIN_BUTTON_ANSWER_QUERY:
-                login_window->priv->login_button_handler_id = g_signal_connect (button, "clicked", G_CALLBACK (on_login_button_clicked_answer_query), login_window);
-                gtk_widget_show (button);
+                if (login_window->priv->active_extension != NULL) {
+                        show_extension_actions (login_window->priv->active_extension);
+                        grab_default_button_for_extension (login_window->priv->active_extension);
+                }
+
+                gtk_widget_hide (button);
                 break;
         case LOGIN_BUTTON_TIMED_LOGIN:
                 login_window->priv->login_button_handler_id = g_signal_connect (button, "clicked", G_CALLBACK (on_login_button_clicked_timed_login), login_window);
@@ -565,6 +548,7 @@ maybe_show_cancel_button (GdmGreeterLoginWindow *login_window)
                 show = TRUE;
                 break;
         case MODE_AUTHENTICATION:
+        case MODE_MULTIPLE_AUTHENTICATION:
                 if (login_window->priv->num_queries > 1) {
                         /* if we are inside a pam conversation past
                            the first step */
@@ -585,6 +569,24 @@ maybe_show_cancel_button (GdmGreeterLoginWindow *login_window)
 }
 
 static void
+update_extension_list_visibility (GdmGreeterLoginWindow *login_window)
+{
+        int number_of_extensions;
+
+        if (login_window->priv->dialog_mode != MODE_MULTIPLE_AUTHENTICATION) {
+                gtk_widget_hide (login_window->priv->extension_list);
+                return;
+        }
+
+        number_of_extensions = gdm_extension_list_get_number_of_visible_extensions (GDM_EXTENSION_LIST (login_window->priv->extension_list));
+        if (number_of_extensions > 1) {
+                gtk_widget_show (login_window->priv->extension_list);
+        } else {
+                gtk_widget_hide (login_window->priv->extension_list);
+        }
+}
+
+static void
 switch_mode (GdmGreeterLoginWindow *login_window,
              int                    number)
 {
@@ -601,6 +603,8 @@ switch_mode (GdmGreeterLoginWindow *login_window,
                 login_window->priv->dialog_mode = number;
         }
 
+        login_window->priv->next_mode = MODE_UNDEFINED;
+
         switch (number) {
         case MODE_SELECTION:
                 set_log_in_button_mode (login_window, LOGIN_BUTTON_HIDDEN);
@@ -613,6 +617,7 @@ switch_mode (GdmGreeterLoginWindow *login_window,
                 gtk_widget_show (login_window->priv->session_option_widget);
                 break;
         case MODE_AUTHENTICATION:
+        case MODE_MULTIPLE_AUTHENTICATION:
                 set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY);
                 set_sensitive (login_window, FALSE);
                 gtk_widget_show (login_window->priv->session_option_widget);
@@ -622,6 +627,7 @@ switch_mode (GdmGreeterLoginWindow *login_window,
         }
 
         show_widget (login_window, "auth-input-box", FALSE);
+        update_extension_list_visibility (login_window);
         maybe_show_cancel_button (login_window);
 
         /*
@@ -652,58 +658,72 @@ switch_mode (GdmGreeterLoginWindow *login_window,
         }
 }
 
-static void
-choose_user (GdmGreeterLoginWindow *login_window,
-             const char            *user_name)
+static GdmLoginExtension *
+find_extension_with_service_name (GdmGreeterLoginWindow *login_window,
+                                  const char            *service_name)
 {
-        guint mode;
+        GList *node;
 
-        g_assert (user_name != NULL);
+        node = login_window->priv->extensions;
+        while (node != NULL) {
+                GdmLoginExtension *extension;
+                char *extension_service_name;
+                gboolean has_service_name;
 
-        g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
-                       0, user_name);
+                extension = GDM_LOGIN_EXTENSION (node->data);
 
-        mode = MODE_AUTHENTICATION;
-        if (strcmp (user_name, GDM_USER_CHOOSER_USER_OTHER) == 0) {
-                g_signal_emit (login_window, signals[BEGIN_VERIFICATION], 0);
-        } else if (strcmp (user_name, GDM_USER_CHOOSER_USER_GUEST) == 0) {
-                /* FIXME: handle guest account stuff */
-        } else if (strcmp (user_name, GDM_USER_CHOOSER_USER_AUTO) == 0) {
-                g_signal_emit (login_window, signals[BEGIN_AUTO_LOGIN], 0,
-                               login_window->priv->timed_login_username);
+                extension_service_name = gdm_login_extension_get_service_name (extension);
+                has_service_name = strcmp (service_name, extension_service_name) == 0;
+                g_free (extension_service_name);
 
-                login_window->priv->timed_login_enabled = TRUE;
-                restart_timed_login_timeout (login_window);
+                if (has_service_name) {
+                        return extension;
+                }
 
-                /* just wait for the user to select language and stuff */
-                mode = MODE_TIMED_LOGIN;
-                set_message (login_window, _("Select language and click Log In"));
-        } else {
-                g_signal_emit (login_window, signals[BEGIN_VERIFICATION_FOR_USER], 0, user_name);
+                node = node->next;
         }
 
-        switch_mode (login_window, mode);
+        return NULL;
 }
 
-static void
-retry_login (GdmGreeterLoginWindow *login_window)
+static gboolean
+reset_extension (GdmLoginExtension   *extension,
+                 GdmGreeterLoginWindow *login_window)
 {
-        GtkWidget  *entry;
-        char       *user_name;
+        char *name;
 
-        user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser));
-        if (user_name == NULL) {
-                return;
-        }
+        name = gdm_login_extension_get_name (extension);
+        g_debug ("Resetting extension '%s'", name);
+        g_free (name);
 
-        g_debug ("GdmGreeterLoginWindow: Retrying login for %s", user_name);
+        login_window->priv->extensions_to_enable = g_list_remove (login_window->priv->extensions_to_enable, extension);
 
-        entry = GTK_WIDGET (gtk_builder_get_object (GDM_GREETER_LOGIN_WINDOW (login_window)->priv->builder, "auth-prompt-entry"));
-        gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1);
+        hide_extension_actions (extension);
+        gdm_extension_list_remove_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list), extension);
+        gdm_login_extension_reset (extension);
+        return FALSE;
+}
 
-        choose_user (login_window, user_name);
+static gboolean
+extensions_are_enabled (GdmGreeterLoginWindow *login_window)
+{
 
-        g_free (user_name);
+        GList *node;
+
+        node = login_window->priv->extensions;
+        while (node != NULL) {
+                GdmLoginExtension *extension;
+
+                extension = GDM_LOGIN_EXTENSION (node->data);
+
+                if (!gdm_login_extension_is_enabled (extension)) {
+                        return FALSE;
+                }
+
+                node = node->next;
+        }
+
+        return TRUE;
 }
 
 static gboolean
@@ -713,6 +733,12 @@ can_jump_to_authenticate (GdmGreeterLoginWindow *login_window)
 
         if (!login_window->priv->user_chooser_loaded) {
                 res = FALSE;
+        } else if (!extensions_are_enabled (login_window)) {
+                res = FALSE;
+        } else if (login_window->priv->dialog_mode == MODE_AUTHENTICATION) {
+                res = FALSE;
+        } else if (login_window->priv->dialog_mode == MODE_MULTIPLE_AUTHENTICATION) {
+                res = FALSE;
         } else if (login_window->priv->user_list_disabled) {
                 res = (login_window->priv->timed_login_username == NULL);
         } else {
@@ -723,20 +749,93 @@ can_jump_to_authenticate (GdmGreeterLoginWindow *login_window)
 }
 
 static void
+begin_other_verification (GdmGreeterLoginWindow *login_window)
+{
+        /* FIXME: we should drop this code and do all OTHER handling
+         * entirely from within the password plugin
+         * (ala how smart card manages its "Smartcard Authentication" item)
+         */
+        begin_single_service_verification (login_window, "gdm-password");
+}
+
+static void
+set_extension_active (GdmGreeterLoginWindow *login_window,
+                      GdmLoginExtension   *extension)
+{
+        GtkWidget *container;
+        char *name;
+
+        name = gdm_login_extension_get_name (extension);
+        g_debug ("GdmGreeterLoginWindow: extension '%s' activated", name);
+        g_free (name);
+
+        container = g_object_get_data (G_OBJECT (extension),
+                                       "gdm-greeter-login-window-page-container");
+
+        if (container == NULL) {
+                GtkWidget *page;
+
+                container = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+                gtk_container_add (GTK_CONTAINER (login_window->priv->auth_page_box),
+                                   container);
+
+                page = gdm_login_extension_get_page (extension);
+                if (page != NULL) {
+                        gtk_container_add (GTK_CONTAINER (container), page);
+                        gtk_widget_show (page);
+                }
+                g_object_set_data (G_OBJECT (extension),
+                                   "gdm-greeter-login-window-page-container",
+                                   container);
+        }
+
+        gtk_widget_show (container);
+
+        login_window->priv->active_extension = extension;
+        switch_mode (login_window, login_window->priv->dialog_mode);
+}
+
+static void
+clear_active_extension (GdmGreeterLoginWindow *login_window)
+{
+
+        GtkWidget *container;
+        GtkActionGroup *actions;
+
+        if (login_window->priv->active_extension == NULL) {
+                return;
+        }
+
+        container = g_object_get_data (G_OBJECT (login_window->priv->active_extension),
+                                       "gdm-greeter-login-window-page-container");
+
+        if (container != NULL) {
+                gtk_widget_hide (container);
+        }
+
+        actions = gdm_login_extension_get_actions (login_window->priv->active_extension);
+
+        if (actions != NULL) {
+                gtk_action_group_set_sensitive (actions, FALSE);
+                gtk_action_group_set_visible (actions, FALSE);
+                g_object_unref (actions);
+        }
+
+        login_window->priv->active_extension = NULL;
+}
+
+static void
 reset_dialog (GdmGreeterLoginWindow *login_window,
               guint                  dialog_mode)
 {
-        GtkWidget  *entry;
-        GtkWidget  *label;
-
         g_debug ("GdmGreeterLoginWindow: Resetting dialog to mode %u", dialog_mode);
         set_busy (login_window);
         set_sensitive (login_window, FALSE);
 
         login_window->priv->num_queries = 0;
 
-        purge_message_queue (login_window);
-        login_window->priv->session_ready_to_start = FALSE;
+        g_free (login_window->priv->service_name_of_session_ready_to_start);
+        login_window->priv->service_name_of_session_ready_to_start = NULL;
 
         if (dialog_mode == MODE_SELECTION) {
                 if (login_window->priv->timed_login_enabled) {
@@ -760,28 +859,20 @@ reset_dialog (GdmGreeterLoginWindow *login_window,
                 set_message (login_window, "");
         }
 
-        entry = GTK_WIDGET (gtk_builder_get_object (GDM_GREETER_LOGIN_WINDOW (login_window)->priv->builder, "auth-prompt-entry"));
-
-        gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1);
-
-        gtk_entry_set_visibility (GTK_ENTRY (entry), TRUE);
-
-        label = GTK_WIDGET (gtk_builder_get_object (GDM_GREETER_LOGIN_WINDOW (login_window)->priv->builder, "auth-prompt-label"));
-        gtk_label_set_text (GTK_LABEL (label), "");
+        g_list_foreach (login_window->priv->extensions, (GFunc) reset_extension, login_window);
 
         if (can_jump_to_authenticate (login_window)) {
                 /* If we don't have a user list jump straight to authenticate */
                 g_debug ("GdmGreeterLoginWindow: jumping straight to authenticate");
-                switch_mode (login_window, MODE_AUTHENTICATION);
-
-                g_debug ("Starting PAM conversation since no local users");
                 g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
                                0, GDM_USER_CHOOSER_USER_OTHER);
-                g_signal_emit (login_window, signals[BEGIN_VERIFICATION], 0);
+                begin_other_verification (login_window);
         } else {
+                clear_active_extension (login_window);
                 switch_mode (login_window, dialog_mode);
         }
 
+        gtk_widget_set_sensitive (login_window->priv->extension_list, TRUE);
         set_ready (login_window);
         set_focus (GDM_GREETER_LOGIN_WINDOW (login_window));
         update_banner_message (login_window);
@@ -792,23 +883,80 @@ reset_dialog (GdmGreeterLoginWindow *login_window,
 }
 
 static void
-do_cancel (GdmGreeterLoginWindow *login_window)
+restart_conversations (GdmGreeterLoginWindow *login_window)
 {
-        /* need to wait for response from backend */
-        set_message (login_window, _("Cancellingâ?¦"));
         set_busy (login_window);
         set_sensitive (login_window, FALSE);
         g_signal_emit (login_window, signals[CANCELLED], 0);
 }
 
+static gboolean
+has_queued_messages (GdmGreeterLoginWindow *login_window)
+{
+        GList *node;
+
+        node = login_window->priv->extensions;
+        while (node != NULL) {
+                GdmLoginExtension *extension;
+
+                extension = (GdmLoginExtension *) node->data;
+
+                if (gdm_login_extension_has_queued_messages (extension)) {
+                        return TRUE;
+                }
+                node = node->next;
+        }
+
+        return FALSE;
+}
+
+static void
+reset_dialog_after_messages (GdmGreeterLoginWindow *login_window,
+                             guint                  dialog_mode)
+{
+        if (has_queued_messages (login_window)) {
+                g_debug ("GdmGreeterLoginWindow: will reset dialog after pending messages");
+                login_window->priv->next_mode = dialog_mode;
+        } else {
+                g_debug ("GdmGreeterLoginWindow: resetting dialog");
+                reset_dialog (login_window, dialog_mode);
+        }
+
+}
+
+static void
+do_cancel (GdmGreeterLoginWindow *login_window)
+{
+        /* need to wait for response from backend */
+        set_message (login_window, _("Cancellingâ?¦"));
+        restart_conversations (login_window);
+        reset_dialog_after_messages (login_window, MODE_SELECTION);
+}
+
 gboolean
-gdm_greeter_login_window_ready (GdmGreeterLoginWindow *login_window)
+gdm_greeter_login_window_ready (GdmGreeterLoginWindow *login_window,
+                                const char            *service_name)
 {
+        GdmLoginExtension *extension;
+
         g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
 
-        if (login_window->priv->message_queue_empty_reset_dialog_mode != MODE_UNDEFINED) {
-                g_debug ("GdmGreeterLoginWindow: Ignoring daemon Ready event since still showing messages");
-                return TRUE;
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                if (!login_window->priv->user_chooser_loaded) {
+                        g_debug ("GdmGreeterLoginWindow: Ignoring daemon Ready event since not loaded yet");
+                        login_window->priv->extensions_to_enable = g_list_prepend (login_window->priv->extensions_to_enable,
+                                                                              extension);
+                        return TRUE;
+                } else if (login_window->priv->next_mode != MODE_UNDEFINED) {
+                        g_debug ("GdmGreeterLoginWindow: Ignoring daemon Ready event since still showing messages");
+                        login_window->priv->extensions_to_enable = g_list_prepend (login_window->priv->extensions_to_enable,
+                                                                              extension);
+                        return TRUE;
+                }
+
+                gdm_login_extension_set_ready (extension);
         }
 
         set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE);
@@ -816,86 +964,177 @@ gdm_greeter_login_window_ready (GdmGreeterLoginWindow *login_window)
         set_focus (GDM_GREETER_LOGIN_WINDOW (login_window));
         gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (login_window)));
 
-        /* If we are retrying a previously selected user */
-        if (!login_window->priv->user_list_disabled &&
-            login_window->priv->dialog_mode == MODE_AUTHENTICATION) {
-                retry_login (login_window);
-        } else {
-                /* If the user list is disabled, then start the PAM conversation */
-                if (can_jump_to_authenticate (login_window)) {
-                        g_debug ("Starting PAM conversation since user list disabled");
-                        g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
-                                       0, GDM_USER_CHOOSER_USER_OTHER);
-                        g_signal_emit (login_window, signals[BEGIN_VERIFICATION], 0);
-                }
+        /* If the user list is disabled, then start the PAM conversation */
+        if (can_jump_to_authenticate (login_window)) {
+                g_debug ("Starting PAM conversation since user list disabled or no local users");
+                g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
+                               0, GDM_USER_CHOOSER_USER_OTHER);
+                begin_other_verification (login_window);
         }
 
         return TRUE;
 }
 
 static void
-reset_dialog_after_messages (GdmGreeterLoginWindow *login_window,
-                             guint                  dialog_mode)
+handle_stopped_conversation (GdmGreeterLoginWindow *login_window,
+                             const char            *service_name)
 {
-        if (!g_queue_is_empty (login_window->priv->message_queue)) {
-                g_debug ("GdmGreeterLoginWindow: will reset dialog after pending messages");
-                login_window->priv->message_queue_empty_reset_dialog_mode = dialog_mode;
-        } else {
-                g_debug ("GdmGreeterLoginWindow: resetting dialog");
-                reset_dialog (login_window, dialog_mode);
+        GdmLoginExtension *extension;
+
+        /* If the password conversation failed, then start over
+         *
+         * FIXME: we need to get this policy out of the source code
+         */
+        if (strcmp (service_name, "gdm-password") == 0) {
+                g_debug ("GdmGreeterLoginWindow: main conversation failed, starting over");
+                restart_conversations (login_window);
+                reset_dialog_after_messages (login_window, MODE_SELECTION);
+                return;
         }
 
+        if (login_window->priv->dialog_mode == MODE_AUTHENTICATION) {
+                g_debug ("GdmGreeterLoginWindow: conversation failed, starting over");
+                restart_conversations (login_window);
+                reset_dialog_after_messages (login_window, MODE_AUTHENTICATION);
+                return;
+        } else if (login_window->priv->dialog_mode != MODE_MULTIPLE_AUTHENTICATION) {
+                g_warning ("conversation %s stopped when it shouldn't have been running (mode %d)",
+                           service_name, login_window->priv->dialog_mode);
+                restart_conversations (login_window);
+                return;
+        }
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                gdm_login_extension_reset (extension);
+
+                login_window->priv->extensions_to_stop = g_list_remove (login_window->priv->extensions_to_stop, extension);
+        }
+
+        /* If every conversation has failed, then just start over.
+         */
+        extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list));
+
+        if (extension == NULL || !gdm_login_extension_is_enabled (extension)) {
+                g_debug ("GdmGreeterLoginWindow: No conversations left, starting over");
+                restart_conversations (login_window);
+                reset_dialog_after_messages (login_window, MODE_SELECTION);
+        }
+
+        if (extension != NULL) {
+                g_object_unref (extension);
+        }
+
+        update_extension_list_visibility (login_window);
 }
 
 gboolean
-gdm_greeter_login_window_authentication_failed (GdmGreeterLoginWindow *login_window)
+gdm_greeter_login_window_conversation_stopped (GdmGreeterLoginWindow *login_window,
+                                               const char            *service_name)
 {
+        GdmLoginExtension *extension;
+
         g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
 
-        g_debug ("GdmGreeterLoginWindow: got authentication failed");
-        reset_dialog_after_messages (login_window, MODE_AUTHENTICATION);
+        g_debug ("GdmGreeterLoginWindow: conversation '%s' has stopped", service_name);
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL && gdm_login_extension_is_enabled (extension)) {
+                if (gdm_login_extension_has_queued_messages (extension)) {
+                        login_window->priv->extensions_to_stop = g_list_prepend (login_window->priv->extensions_to_stop, extension);
+                } else {
+                        handle_stopped_conversation (login_window, service_name);
+                }
+        }
+
         return TRUE;
 }
 
+static gboolean
+restart_extension_conversation (GdmLoginExtension   *extension,
+                                GdmGreeterLoginWindow *login_window)
+{
+        char *service_name;
+
+        login_window->priv->extensions_to_stop = g_list_remove (login_window->priv->extensions_to_stop, extension);
+
+        service_name = gdm_login_extension_get_service_name (extension);
+        if (service_name != NULL) {
+                char *name;
+
+                name = gdm_login_extension_get_name (extension);
+                g_debug ("GdmGreeterLoginWindow: restarting '%s' conversation", name);
+                g_free (name);
+
+                g_signal_emit (login_window, signals[START_CONVERSATION], 0, service_name);
+                g_free (service_name);
+        }
+
+        return FALSE;
+}
+
 gboolean
 gdm_greeter_login_window_reset (GdmGreeterLoginWindow *login_window)
 {
+        g_debug ("GdmGreeterLoginWindow: window reset");
+
         g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
 
-        g_debug ("GdmGreeterLoginWindow: got reset");
         reset_dialog_after_messages (login_window, MODE_SELECTION);
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) restart_extension_conversation,
+                        login_window);
+
+        g_free (login_window->priv->service_name_of_session_ready_to_start);
+        login_window->priv->service_name_of_session_ready_to_start = NULL;
 
         return TRUE;
 }
 
 gboolean
 gdm_greeter_login_window_info (GdmGreeterLoginWindow *login_window,
+                               const char            *service_name,
                                const char            *text)
 {
-        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+        GdmLoginExtension *extension;
 
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
         g_debug ("GdmGreeterLoginWindow: info: %s", text);
 
-        queue_message (GDM_GREETER_LOGIN_WINDOW (login_window),
-                       QUEUE_MESSAGE_TYPE_INFO,
-                       text);
         maybe_show_cancel_button (login_window);
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                gdm_login_extension_queue_message (extension,
+                                                     GDM_SERVICE_MESSAGE_TYPE_INFO,
+                                                     text);
+                show_extension_actions (extension);
+        }
 
         return TRUE;
 }
 
 gboolean
 gdm_greeter_login_window_problem (GdmGreeterLoginWindow *login_window,
+                                  const char            *service_name,
                                   const char            *text)
 {
-        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+        GdmLoginExtension *extension;
 
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
         g_debug ("GdmGreeterLoginWindow: problem: %s", text);
         maybe_show_cancel_button (login_window);
 
-        queue_message (GDM_GREETER_LOGIN_WINDOW (login_window),
-                       QUEUE_MESSAGE_TYPE_PROBLEM,
-                       text);
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                gdm_login_extension_queue_message (extension,
+                                                     GDM_SERVICE_MESSAGE_TYPE_PROBLEM,
+                                                     text);
+                show_extension_actions (extension);
+        }
 
         return TRUE;
 }
@@ -919,6 +1158,36 @@ request_timed_login (GdmGreeterLoginWindow *login_window)
         login_window->priv->timed_login_already_enabled = TRUE;
 }
 
+gboolean
+gdm_greeter_login_window_service_unavailable (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name)
+{
+        GdmLoginExtension *extension;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+        g_debug ("GdmGreeterLoginWindow: service unavailable: %s", service_name);
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                GdmLoginExtension *active_extension;
+
+                gdm_login_extension_set_enabled (extension, FALSE);
+
+                active_extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list));
+
+                if (active_extension == extension) {
+                        restart_conversations (login_window);
+                }
+
+                if (active_extension != NULL) {
+                        g_object_unref (active_extension);
+                }
+        }
+
+        return TRUE;
+}
+
 void
 gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_window,
                                               const char            *username,
@@ -946,21 +1215,40 @@ gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_windo
 }
 
 static void
-gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window)
+gdm_greeter_login_window_start_session (GdmGreeterLoginWindow *login_window)
 {
-        login_window->priv->session_ready_to_start = TRUE;
+        g_debug ("GdmGreeterLoginWindow: starting session");
+        g_signal_emit (login_window,
+                       signals[START_SESSION],
+                       0,
+                       login_window->priv->service_name_of_session_ready_to_start);
+        g_free (login_window->priv->service_name_of_session_ready_to_start);
+        login_window->priv->service_name_of_session_ready_to_start = NULL;
+}
+
+static void
+gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window,
+                                                   const char            *service_name)
+{
+        GdmLoginExtension *extension;
 
-        if (login_window->priv->message_timeout_id == 0) {
-                set_next_message_or_continue (login_window);
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        login_window->priv->service_name_of_session_ready_to_start = g_strdup (service_name);
+
+        if (!gdm_login_extension_has_queued_messages (extension)) {
+                g_debug ("GdmGreeterLoginWindow: starting session");
+                g_signal_emit (login_window, signals[START_SESSION], 0, service_name);
+                gdm_greeter_login_window_start_session (login_window);
         }
 }
 
 gboolean
 gdm_greeter_login_window_info_query (GdmGreeterLoginWindow *login_window,
+                                     const char            *service_name,
                                      const char            *text)
 {
-        GtkWidget  *entry;
-        GtkWidget  *label;
+        GdmLoginExtension *extension;
 
         g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
 
@@ -969,16 +1257,13 @@ gdm_greeter_login_window_info_query (GdmGreeterLoginWindow *login_window,
 
         g_debug ("GdmGreeterLoginWindow: info query: %s", text);
 
-        entry = GTK_WIDGET (gtk_builder_get_object (GDM_GREETER_LOGIN_WINDOW (login_window)->priv->builder, "auth-prompt-entry"));
-        gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1);
-        gtk_entry_set_visibility (GTK_ENTRY (entry), TRUE);
-        set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY);
-
-        label = GTK_WIDGET (gtk_builder_get_object (GDM_GREETER_LOGIN_WINDOW (login_window)->priv->builder, "auth-prompt-label"));
-        gtk_label_set_text (GTK_LABEL (label), text);
+        extension = find_extension_with_service_name (login_window, service_name);
 
-        show_widget (login_window, "auth-input-box", TRUE);
+        if (extension != NULL) {
+                gdm_login_extension_ask_question (extension, text);
+        }
 
+        set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY);
         set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE);
         set_ready (GDM_GREETER_LOGIN_WINDOW (login_window));
         set_focus (GDM_GREETER_LOGIN_WINDOW (login_window));
@@ -990,26 +1275,24 @@ gdm_greeter_login_window_info_query (GdmGreeterLoginWindow *login_window,
 
 gboolean
 gdm_greeter_login_window_secret_info_query (GdmGreeterLoginWindow *login_window,
+                                            const char            *service_name,
                                             const char            *text)
 {
-        GtkWidget  *entry;
-        GtkWidget  *label;
+
+        GdmLoginExtension *extension;
 
         g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
 
         login_window->priv->num_queries++;
         maybe_show_cancel_button (login_window);
 
-        entry = GTK_WIDGET (gtk_builder_get_object (GDM_GREETER_LOGIN_WINDOW (login_window)->priv->builder, "auth-prompt-entry"));
-        gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1);
-        gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
-        set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY);
+        extension = find_extension_with_service_name (login_window, service_name);
 
-        label = GTK_WIDGET (gtk_builder_get_object (GDM_GREETER_LOGIN_WINDOW (login_window)->priv->builder, "auth-prompt-label"));
-        gtk_label_set_text (GTK_LABEL (label), text);
+        if (extension != NULL) {
+                gdm_login_extension_ask_secret (extension, text);
+        }
 
-        show_widget (login_window, "auth-input-box", TRUE);
-        gtk_widget_show (login_window->priv->session_option_widget);
+        set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY);
         set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE);
         set_ready (GDM_GREETER_LOGIN_WINDOW (login_window));
         set_focus (GDM_GREETER_LOGIN_WINDOW (login_window));
@@ -1020,13 +1303,16 @@ gdm_greeter_login_window_secret_info_query (GdmGreeterLoginWindow *login_window,
 }
 
 void
-gdm_greeter_login_window_session_opened (GdmGreeterLoginWindow *login_window)
+gdm_greeter_login_window_session_opened (GdmGreeterLoginWindow *login_window,
+                                          const char            *service_name)
 {
         g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
 
-        g_debug ("GdmGreeterLoginWindow: session now opened");
+        g_debug ("GdmGreeterLoginWindow: session now opened via service %s",
+                 service_name);
 
-        gdm_greeter_login_window_start_session_when_ready (login_window);
+        gdm_greeter_login_window_start_session_when_ready (login_window,
+                                                           service_name);
 }
 
 static void
@@ -1090,6 +1376,51 @@ on_user_chooser_visibility_changed (GdmGreeterLoginWindow *login_window)
         update_banner_message (login_window);
 }
 
+static gboolean
+begin_extension_verification_for_selected_user (GdmLoginExtension   *extension,
+                                           GdmGreeterLoginWindow *login_window)
+{
+        char *user_name;
+        char *service_name;
+
+        user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser));
+
+        if (user_name == NULL) {
+                return TRUE;
+        }
+
+        service_name = gdm_login_extension_get_service_name (extension);
+        if (service_name != NULL) {
+                g_signal_emit (login_window, signals[BEGIN_VERIFICATION_FOR_USER], 0, service_name, user_name);
+                g_free (service_name);
+        }
+
+        gdm_extension_list_add_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list),
+                                          extension);
+
+        g_free (user_name);
+        return FALSE;
+}
+
+static void
+enable_waiting_extensions (GdmGreeterLoginWindow *login_window)
+{
+        GList *node;
+
+        node = login_window->priv->extensions_to_enable;
+        while (node != NULL) {
+                GdmLoginExtension *extension;
+
+                extension = GDM_LOGIN_EXTENSION (node->data);
+
+                gdm_login_extension_set_ready (extension);
+
+                node = node->next;
+        }
+
+        login_window->priv->extensions_to_enable = NULL;
+}
+
 static void
 on_users_loaded (GdmUserChooserWidget  *user_chooser,
                  GdmGreeterLoginWindow *login_window)
@@ -1103,37 +1434,155 @@ on_users_loaded (GdmUserChooserWidget  *user_chooser,
                 gtk_widget_show (login_window->priv->user_chooser);
         }
 
+        enable_waiting_extensions (login_window);
+
         if (login_window->priv->timed_login_username != NULL
             && !login_window->priv->timed_login_already_enabled) {
                 request_timed_login (login_window);
         } else if (can_jump_to_authenticate (login_window)) {
+
                 /* jump straight to authenticate */
                 g_debug ("GdmGreeterLoginWindow: jumping straight to authenticate");
-
-                switch_mode (login_window, MODE_AUTHENTICATION);
-
-                g_debug ("Starting PAM conversation since no local users");
                 g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
                                0, GDM_USER_CHOOSER_USER_OTHER);
-                g_signal_emit (login_window, signals[BEGIN_VERIFICATION], 0);
+                begin_other_verification (login_window);
+        }
+}
+
+static void
+choose_user (GdmGreeterLoginWindow *login_window,
+             const char            *user_name)
+{
+        GdmLoginExtension *extension;
+
+        g_assert (user_name != NULL);
+        g_debug ("GdmGreeterLoginWindow: user chosen '%s'", user_name);
+
+        g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
+                       0, user_name);
+
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) begin_extension_verification_for_selected_user,
+                        login_window);
+
+        extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list));
+        set_extension_active (login_window, extension);
+        g_object_unref (extension);
+
+        switch_mode (login_window, MODE_MULTIPLE_AUTHENTICATION);
+        update_extension_list_visibility (login_window);
+}
+
+static void
+begin_auto_login (GdmGreeterLoginWindow *login_window)
+{
+        g_signal_emit (login_window, signals[BEGIN_AUTO_LOGIN], 0,
+                       login_window->priv->timed_login_username);
+
+        login_window->priv->timed_login_enabled = TRUE;
+        restart_timed_login_timeout (login_window);
+
+        /* just wait for the user to select language and stuff */
+        set_message (login_window, _("Select language and click Log In"));
+
+        clear_active_extension (login_window);
+        switch_mode (login_window, MODE_TIMED_LOGIN);
+
+        show_widget (login_window, "conversation-list", FALSE);
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) reset_extension,
+                        login_window);
+}
+
+static void
+reset_extension_if_not_given (GdmLoginExtension  *extension,
+                              GdmLoginExtension  *given_extension)
+{
+        if (extension == given_extension) {
+                return;
+        }
+
+        gdm_login_extension_reset (extension);
+}
+
+static void
+reset_every_extension_but_given_extension (GdmGreeterLoginWindow *login_window,
+                                 GdmLoginExtension   *extension)
+{
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) reset_extension_if_not_given,
+                        extension);
+
+}
+
+static void
+begin_single_service_verification (GdmGreeterLoginWindow *login_window,
+                                   const char            *service_name)
+{
+        GdmLoginExtension *extension;
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension == NULL) {
+                g_debug ("GdmGreeterLoginWindow: %s has no extension associated with it", service_name);
+                return;
         }
+
+        g_debug ("GdmGreeterLoginWindow: Beginning %s auth conversation", service_name);
+
+        /* FIXME: we should probably give the plugin more say for
+         * what happens here.
+         */
+        g_signal_emit (login_window, signals[BEGIN_VERIFICATION], 0, service_name);
+
+        reset_every_extension_but_given_extension (login_window, extension);
+
+        set_extension_active (login_window, extension);
+        switch_mode (login_window, MODE_AUTHENTICATION);
+
+        show_widget (login_window, "conversation-list", FALSE);
 }
 
 static void
-on_user_chosen (GdmUserChooserWidget  *user_chooser,
-                GdmGreeterLoginWindow *login_window)
+on_user_chooser_activated (GdmUserChooserWidget  *user_chooser,
+                           GdmGreeterLoginWindow *login_window)
 {
         char *user_name;
+        char *item_id;
 
         user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser));
-        g_debug ("GdmGreeterLoginWindow: user chosen '%s'", user_name);
 
-        if (user_name == NULL) {
+        if (user_name != NULL) {
+                g_debug ("GdmGreeterLoginWindow: user chosen '%s'", user_name);
+                choose_user (login_window, user_name);
+                g_free (user_name);
                 return;
         }
 
-        choose_user (login_window, user_name);
-        g_free (user_name);
+        item_id = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (user_chooser));
+        g_debug ("GdmGreeterLoginWindow: item chosen '%s'", item_id);
+
+        g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
+                       0, item_id);
+
+        if (strcmp (item_id, GDM_USER_CHOOSER_USER_OTHER) == 0) {
+                g_debug ("GdmGreeterLoginWindow: Starting all auth conversations");
+                g_free (item_id);
+
+                begin_other_verification (login_window);
+        } else if (strcmp (item_id, GDM_USER_CHOOSER_USER_GUEST) == 0) {
+                /* FIXME: handle guest account stuff */
+                g_free (item_id);
+        } else if (strcmp (item_id, GDM_USER_CHOOSER_USER_AUTO) == 0) {
+                g_debug ("GdmGreeterLoginWindow: Starting auto login");
+                g_free (item_id);
+
+                begin_auto_login (login_window);
+        } else {
+                g_debug ("GdmGreeterLoginWindow: Starting single auth conversation");
+                begin_single_service_verification (login_window, item_id);
+                g_free (item_id);
+        }
 }
 
 static void
@@ -1354,12 +1803,40 @@ create_computer_info (GdmGreeterLoginWindow *login_window)
 #define INVISIBLE_CHAR_BULLET        0x2022
 #define INVISIBLE_CHAR_NONE          0
 
+static void
+on_extension_activated (GdmGreeterLoginWindow *login_window,
+                        GdmLoginExtension   *extension)
+{
+        set_extension_active (login_window, extension);
+}
+
+static void
+on_extension_deactivated (GdmGreeterLoginWindow *login_window,
+                          GdmLoginExtension   *extension)
+{
+        char *name;
+
+        if (login_window->priv->active_extension != extension) {
+                g_warning ("inactive extension has been deactivated");
+                return;
+        }
+
+        name = gdm_login_extension_get_name (extension);
+        g_debug ("GdmGreeterLoginWindow: extension '%s' now in background", name);
+        g_free (name);
+
+        clear_active_extension (login_window);
+
+        login_window->priv->active_extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list));
+        g_object_unref (login_window->priv->active_extension);
+}
 
 static void
 register_custom_types (GdmGreeterLoginWindow *login_window)
 {
         GType types[] = { GDM_TYPE_USER_CHOOSER_WIDGET,
-                          GDM_TYPE_SESSION_OPTION_WIDGET };
+                          GDM_TYPE_SESSION_OPTION_WIDGET,
+                          GDM_TYPE_EXTENSION_LIST };
         int i;
 
         for (i = 0; i < G_N_ELEMENTS (types); i++) {
@@ -1370,7 +1847,6 @@ register_custom_types (GdmGreeterLoginWindow *login_window)
 static void
 load_theme (GdmGreeterLoginWindow *login_window)
 {
-        GtkWidget *entry;
         GtkWidget *button;
         GtkWidget *box;
         GtkWidget *image;
@@ -1423,7 +1899,7 @@ load_theme (GdmGreeterLoginWindow *login_window)
                           login_window);
         g_signal_connect (login_window->priv->user_chooser,
                           "activated",
-                          G_CALLBACK (on_user_chosen),
+                          G_CALLBACK (on_user_chooser_activated),
                           login_window);
         g_signal_connect (login_window->priv->user_chooser,
                           "deactivated",
@@ -1442,30 +1918,31 @@ load_theme (GdmGreeterLoginWindow *login_window)
                           G_CALLBACK (on_session_activated),
                           login_window);
 
+        login_window->priv->extension_list = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "extension-list"));
+
+        g_signal_connect_swapped (GDM_EXTENSION_LIST (login_window->priv->extension_list),
+                                  "activated",
+                                  G_CALLBACK (on_extension_activated),
+                                  login_window);
+        g_signal_connect_swapped (GDM_EXTENSION_LIST (login_window->priv->extension_list),
+                                  "deactivated",
+                                  G_CALLBACK (on_extension_deactivated),
+                                  login_window);
+
         login_window->priv->auth_banner_label = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-banner-label"));
         /*make_label_small_italic (login_window->priv->auth_banner_label);*/
+        login_window->priv->auth_page_box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-page-box"));
 
         button = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "cancel-button"));
         g_signal_connect (button, "clicked", G_CALLBACK (cancel_button_clicked), login_window);
 
-        entry = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-prompt-entry"));
-        /* Only change the invisible character if it '*' otherwise assume it is OK */
-        if ('*' == gtk_entry_get_invisible_char (GTK_ENTRY (entry))) {
-                gunichar invisible_char;
-                invisible_char = INVISIBLE_CHAR_BLACK_CIRCLE;
-                gtk_entry_set_invisible_char (GTK_ENTRY (entry), invisible_char);
-        }
-
         create_computer_info (login_window);
 
         box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "computer-info-event-box"));
         g_signal_connect (box, "button-press-event", G_CALLBACK (on_computer_info_label_button_press), login_window);
 
-        if (login_window->priv->user_list_disabled) {
-                switch_mode (login_window, MODE_AUTHENTICATION);
-        } else {
-                switch_mode (login_window, MODE_SELECTION);
-        }
+        clear_active_extension (login_window);
+        switch_mode (login_window, MODE_SELECTION);
 
         gdm_profile_end (NULL);
 }
@@ -1662,6 +2139,15 @@ gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass)
 
         gtk_container_class_handle_border_width (container_class);
 
+        signals [START_CONVERSATION] =
+                g_signal_new ("start-conversation",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, start_conversation),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE, 1, G_TYPE_STRING);
         signals [BEGIN_AUTO_LOGIN] =
                 g_signal_new ("begin-auto-login",
                               G_TYPE_FROM_CLASS (object_class),
@@ -1678,9 +2164,9 @@ gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass)
                               G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, begin_verification),
                               NULL,
                               NULL,
-                              g_cclosure_marshal_VOID__VOID,
+                              g_cclosure_marshal_VOID__STRING,
                               G_TYPE_NONE,
-                              0);
+                              1, G_TYPE_STRING);
         signals [BEGIN_VERIFICATION_FOR_USER] =
                 g_signal_new ("begin-verification-for-user",
                               G_TYPE_FROM_CLASS (object_class),
@@ -1688,9 +2174,9 @@ gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass)
                               G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, begin_verification_for_user),
                               NULL,
                               NULL,
-                              g_cclosure_marshal_VOID__STRING,
+                              gdm_marshal_VOID__STRING_STRING,
                               G_TYPE_NONE,
-                              1, G_TYPE_STRING);
+                              2, G_TYPE_STRING, G_TYPE_STRING);
         signals [QUERY_ANSWER] =
                 g_signal_new ("query-answer",
                               G_TYPE_FROM_CLASS (object_class),
@@ -1698,9 +2184,9 @@ gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass)
                               G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, query_answer),
                               NULL,
                               NULL,
-                              g_cclosure_marshal_VOID__STRING,
+                              gdm_marshal_VOID__STRING_STRING,
                               G_TYPE_NONE,
-                              1, G_TYPE_STRING);
+                              2, G_TYPE_STRING, G_TYPE_STRING);
         signals [USER_SELECTED] =
                 g_signal_new ("user-selected",
                               G_TYPE_FROM_CLASS (object_class),
@@ -1738,9 +2224,9 @@ gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass)
                               G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, start_session),
                               NULL,
                               NULL,
-                              g_cclosure_marshal_VOID__VOID,
+                              g_cclosure_marshal_VOID__STRING,
                               G_TYPE_NONE,
-                              0);
+                              1, G_TYPE_STRING);
 
         g_object_class_install_property (object_class,
                                          PROP_DISPLAY_IS_LOCAL,
@@ -1785,6 +2271,261 @@ on_gconf_key_changed (GConfClient           *client,
         }
 }
 
+static void
+on_login_extension_answer (GdmGreeterLoginWindow *login_window,
+                             const char            *text,
+                             GdmLoginExtension   *extension)
+{
+        if (text != NULL) {
+                char *service_name;
+
+                service_name = gdm_login_extension_get_service_name (extension);
+                if (service_name != NULL) {
+                        g_signal_emit (login_window, signals[QUERY_ANSWER], 0, service_name, text);
+                        g_free (service_name);
+                }
+        }
+
+        set_sensitive (login_window, TRUE);
+        set_ready (login_window);
+}
+
+static void
+on_login_extension_cancel (GdmGreeterLoginWindow *login_window,
+                             GdmLoginExtension   *extension)
+{
+        restart_conversations (login_window);
+}
+
+static gboolean
+on_login_extension_chose_user (GdmGreeterLoginWindow *login_window,
+                                 const char            *username,
+                                 GdmLoginExtension   *extension)
+{
+        if (!login_window->priv->user_chooser_loaded) {
+                char *name;
+
+                name = gdm_login_extension_get_name (extension);
+                g_warning ("Task %s is trying to choose user before list is loaded", name);
+                g_free (name);
+                return FALSE;
+        }
+
+        /* If we're already authenticating then we can't pick a user
+         */
+        if (login_window->priv->dialog_mode == MODE_AUTHENTICATION || login_window->priv->dialog_mode == MODE_MULTIPLE_AUTHENTICATION) {
+                return FALSE;
+        }
+
+        gdm_user_chooser_widget_set_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser),
+                                                      username);
+
+        return TRUE;
+}
+
+static void
+on_login_extension_message_queue_empty (GdmGreeterLoginWindow *login_window,
+                                          GdmLoginExtension   *extension)
+{
+        gboolean needs_to_be_stopped;
+
+        needs_to_be_stopped = g_list_find (login_window->priv->extensions_to_stop, extension) != NULL;
+
+        if (needs_to_be_stopped) {
+                char *service_name;
+
+                service_name = gdm_login_extension_get_service_name (extension);
+                handle_stopped_conversation (login_window, service_name);
+                g_free (service_name);
+        }
+
+        if (login_window->priv->service_name_of_session_ready_to_start != NULL) {
+                if (login_window->priv->active_extension == extension) {
+                        gdm_greeter_login_window_start_session (login_window);
+                }
+        } else if (login_window->priv->next_mode != MODE_UNDEFINED) {
+                reset_dialog_after_messages (login_window, login_window->priv->next_mode);
+        }
+}
+
+void
+gdm_greeter_login_window_remove_extension (GdmGreeterLoginWindow *login_window,
+                                           GdmLoginExtension   *extension)
+{
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
+}
+
+static void
+on_button_action_label_changed (GtkWidget *button)
+{
+        GtkAction *action;
+        char *text;
+
+        action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button));
+
+        g_object_get (G_OBJECT (action), "label", &text, NULL);
+
+        gtk_button_set_label (GTK_BUTTON (button), text);
+        g_free (text);
+}
+
+static void
+on_button_action_icon_name_changed (GtkWidget *button)
+{
+        GtkAction *action;
+        GtkWidget *image;
+
+        action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button));
+
+        if (gtk_action_get_is_important (action)) {
+                image = gtk_action_create_icon (GTK_ACTION (action), GTK_ICON_SIZE_BUTTON);
+        } else {
+                image = NULL;
+        }
+
+        gtk_button_set_image (GTK_BUTTON (button), image);
+
+}
+
+static void
+on_button_action_tooltip_changed (GtkWidget *button)
+{
+        GtkAction *action;
+        char *text;
+
+        action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button));
+
+        g_object_get (G_OBJECT (action), "tooltip", &text, NULL);
+
+        gtk_widget_set_tooltip_text (button, text);
+        g_free (text);
+}
+
+static GtkWidget *
+create_button_from_action (GtkAction *action)
+{
+        GtkWidget *button;
+
+        button = gtk_button_new ();
+
+        gtk_activatable_set_related_action (GTK_ACTIVATABLE (button), action);
+
+        g_signal_connect_swapped (action,
+                                  "notify::label",
+                                  G_CALLBACK (on_button_action_label_changed),
+                                  button);
+        g_signal_connect_swapped (action,
+                                  "notify::icon-name",
+                                  G_CALLBACK (on_button_action_icon_name_changed),
+                                  button);
+        g_signal_connect_swapped (action,
+                                  "notify::tooltip",
+                                  G_CALLBACK (on_button_action_tooltip_changed),
+                                  button);
+
+        on_button_action_label_changed (button);
+        on_button_action_icon_name_changed (button);
+        on_button_action_tooltip_changed (button);
+
+        if (strcmp (gtk_action_get_name (action),
+                    GDM_LOGIN_EXTENSION_DEFAULT_ACTION) == 0) {
+                gtk_widget_set_can_default (button, TRUE);
+        }
+
+        return button;
+}
+
+static void
+create_buttons_for_actions (GdmGreeterLoginWindow *login_window,
+                            GtkActionGroup        *actions)
+{
+        GList *action_list;
+        GList *node;
+        GtkWidget *box;
+
+        action_list = gtk_action_group_list_actions (actions);
+
+        box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "buttonbox"));
+        for (node = action_list; node != NULL; node = node->next) {
+                GtkAction *action;
+                GtkWidget *button;
+
+                action = node->data;
+
+                button = create_button_from_action (action);
+                gtk_container_add (GTK_CONTAINER (box), button);
+        }
+
+        g_list_free (action_list);
+}
+
+void
+gdm_greeter_login_window_add_extension (GdmGreeterLoginWindow *login_window,
+                                        GdmLoginExtension     *extension)
+{
+        char *name;
+        char *description;
+        char *service_name;
+        GtkActionGroup *actions;
+
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
+        g_return_if_fail (GDM_IS_LOGIN_EXTENSION (extension));
+
+        name = gdm_login_extension_get_name (extension);
+        description = gdm_login_extension_get_description (extension);
+
+        if (!gdm_login_extension_is_visible (extension)) {
+                g_debug ("GdmGreeterLoginWindow: new extension '%s - %s' won't be added",
+                         name, description);
+                g_free (name);
+                g_free (description);
+                return;
+        }
+
+        actions = gdm_login_extension_get_actions (extension);
+
+        create_buttons_for_actions (login_window, actions);
+        hide_extension_actions (extension);
+
+        g_object_unref (actions);
+
+        g_signal_connect_swapped (extension,
+                                  "answer",
+                                  G_CALLBACK (on_login_extension_answer),
+                                  login_window);
+        g_signal_connect_swapped (extension,
+                                  "cancel",
+                                  G_CALLBACK (on_login_extension_cancel),
+                                  login_window);
+        g_signal_connect_swapped (extension,
+                                  "user-chosen",
+                                  G_CALLBACK (on_login_extension_chose_user),
+                                  login_window);
+        g_signal_connect_swapped (extension,
+                                  "message-queue-empty",
+                                  G_CALLBACK (on_login_extension_message_queue_empty),
+                                  login_window);
+
+        g_debug ("GdmGreeterLoginWindow: new extension '%s - %s' added",
+                name, description);
+
+        login_window->priv->extensions = g_list_append (login_window->priv->extensions, extension);
+        service_name = gdm_login_extension_get_service_name (extension);
+
+        if (gdm_login_extension_is_choosable (extension)) {
+                gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser),
+                                             service_name, NULL, name, description, ~0,
+                                             FALSE, TRUE, NULL, NULL);
+        }
+
+        g_free (name);
+        g_free (description);
+
+        g_debug ("GdmGreeterLoginWindow: starting conversation with '%s'", service_name);
+        g_signal_emit (login_window, signals[START_CONVERSATION], 0, service_name);
+        g_free (service_name);
+}
+
 static gboolean
 on_window_state_event (GtkWidget           *widget,
                        GdkEventWindowState *event,
@@ -1810,8 +2551,7 @@ gdm_greeter_login_window_init (GdmGreeterLoginWindow *login_window)
         login_window->priv = GDM_GREETER_LOGIN_WINDOW_GET_PRIVATE (login_window);
         login_window->priv->timed_login_enabled = FALSE;
         login_window->priv->dialog_mode = MODE_UNDEFINED;
-        login_window->priv->message_queue = g_queue_new ();
-        login_window->priv->message_queue_empty_reset_dialog_mode = MODE_UNDEFINED;
+        login_window->priv->next_mode = MODE_UNDEFINED;
 
         client = gconf_client_get_default ();
         error = NULL;
@@ -1875,9 +2615,6 @@ gdm_greeter_login_window_finalize (GObject *object)
                 g_object_unref (login_window->priv->client);
         }
 
-        purge_message_queue (login_window);
-        g_queue_free (login_window->priv->message_queue);
-
         G_OBJECT_CLASS (gdm_greeter_login_window_parent_class)->finalize (object);
 }
 
diff --git a/gui/simple-greeter/gdm-greeter-login-window.h b/gui/simple-greeter/gdm-greeter-login-window.h
index f461c8a..cbefaad 100644
--- a/gui/simple-greeter/gdm-greeter-login-window.h
+++ b/gui/simple-greeter/gdm-greeter-login-window.h
@@ -23,6 +23,7 @@
 #define __GDM_GREETER_LOGIN_WINDOW_H
 
 #include <glib-object.h>
+#include "gdm-login-extension.h"
 
 G_BEGIN_DECLS
 
@@ -46,12 +47,17 @@ typedef struct
         GtkWindowClass   parent_class;
 
         /* signals */
+        void (* start_conversation)          (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name);
         void (* begin_auto_login)            (GdmGreeterLoginWindow *login_window,
                                               const char            *username);
-        void (* begin_verification)          (GdmGreeterLoginWindow *login_window);
+        void (* begin_verification)          (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name);
         void (* begin_verification_for_user) (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name,
                                               const char            *username);
         void (* query_answer)                (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name,
                                               const char            *text);
         void (* user_selected)               (GdmGreeterLoginWindow *login_window,
                                               const char            *text);
@@ -67,23 +73,38 @@ GtkWidget *         gdm_greeter_login_window_new                (gboolean displa
 
 
 gboolean            gdm_greeter_login_window_reset              (GdmGreeterLoginWindow *login_window);
-gboolean            gdm_greeter_login_window_authentication_failed (GdmGreeterLoginWindow *login_window);
-gboolean            gdm_greeter_login_window_ready              (GdmGreeterLoginWindow *login_window);
+gboolean            gdm_greeter_login_window_ready              (GdmGreeterLoginWindow *login_window,
+                                                                 const char            *service_name);
+gboolean            gdm_greeter_login_window_conversation_stopped (GdmGreeterLoginWindow *login_window,
+                                                                   const char            *service_name);
 gboolean            gdm_greeter_login_window_info_query         (GdmGreeterLoginWindow *login_window,
+                                                                 const char *service_name,
                                                                  const char *text);
 gboolean            gdm_greeter_login_window_secret_info_query  (GdmGreeterLoginWindow *login_window,
+                                                                 const char *service_name,
                                                                  const char *text);
 gboolean            gdm_greeter_login_window_info               (GdmGreeterLoginWindow *login_window,
+                                                                 const char *service_name,
                                                                  const char *text);
 gboolean            gdm_greeter_login_window_problem            (GdmGreeterLoginWindow *login_window,
+                                                                 const char *service_name,
                                                                  const char *text);
 void                gdm_greeter_login_window_set_default_session_name (GdmGreeterLoginWindow *login_window,
                                                                        const char *text);
 
+gboolean            gdm_greeter_login_window_service_unavailable (GdmGreeterLoginWindow *login_window,
+                                                                  const char *service_name);
+
 void               gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_window,
                                                                  const char            *username,
                                                                  int                    delay);
-void               gdm_greeter_login_window_session_opened      (GdmGreeterLoginWindow *login_window);
+void               gdm_greeter_login_window_session_opened      (GdmGreeterLoginWindow *login_window,
+                                                                 const char            *service_name);
+
+void               gdm_greeter_login_window_add_extension (GdmGreeterLoginWindow *login_window,
+                                                           GdmLoginExtension     *extension);
+void               gdm_greeter_login_window_remove_extension (GdmGreeterLoginWindow *login_window,
+                                                              GdmLoginExtension     *extension);
 
 G_END_DECLS
 
diff --git a/gui/simple-greeter/gdm-greeter-login-window.ui b/gui/simple-greeter/gdm-greeter-login-window.ui
index 4f6bed4..2ab29cb 100644
--- a/gui/simple-greeter/gdm-greeter-login-window.ui
+++ b/gui/simple-greeter/gdm-greeter-login-window.ui
@@ -158,69 +158,40 @@
                     <child>
                       <object class="GtkVBox" id="selection-box">
                         <property name="visible">True</property>
-                        <property name="spacing">10</property>
+                        <property name="spacing">2</property>
                         <child>
-                          <object class="GdmUserChooserWidget" id="user-chooser">
-                            <property name="visible">False</property>
-                          </object>
-                          <packing>
-                            <property name="expand">True</property>
-                            <property name="fill">True</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkHBox" id="auth-input-box">
+                          <object class="GtkAlignment" id="task-list-alignment">
                             <property name="visible">True</property>
-                            <property name="spacing">6</property>
+                            <property name="xalign">1.0</property>
+                            <property name="xscale">0.0</property>
                             <child>
-                              <object class="GtkLabel" id="auth-prompt-label">
-                                <property name="visible">True</property>
-
-                                <accessibility>
-                                  <relation type="label-for" target="auth-prompt-entry"/>
-                                </accessibility>
+                              <object class="GdmExtensionList" id="extension-list">
+                                <property name="visible">False</property>
                               </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">False</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="auth-prompt-entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
-                                <property name="activates_default">True</property>
-                                <accessibility>
-                                  <relation type="labelled-by" target="auth-prompt-label"/>
-                                </accessibility>
-                              </object>
-                              <packing>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <placeholder/>
                             </child>
                           </object>
                           <packing>
                             <property name="expand">False</property>
                             <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GdmUserChooserWidget" id="user-chooser">
+                            <property name="visible">False</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
                             <property name="position">1</property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkHBox" id="auth-message-box">
+                          <object class="GtkHBox" id="auth-page-box">
                             <property name="visible">True</property>
+                            <property name="border_width">10</property>
                             <child>
-                              <object class="GtkLabel" id="auth-message-label">
-                                <property name="visible">True</property>
-                              </object>
-                              <packing>
-                                <property name="position">0</property>
-                              </packing>
+                              <placeholder/>
                             </child>
                           </object>
                           <packing>
diff --git a/gui/simple-greeter/gdm-greeter-session.c b/gui/simple-greeter/gdm-greeter-session.c
index d939f47..af41a54 100644
--- a/gui/simple-greeter/gdm-greeter-session.c
+++ b/gui/simple-greeter/gdm-greeter-session.c
@@ -75,7 +75,7 @@ on_info (GdmGreeterClient  *client,
 {
         g_debug ("GdmGreeterSession: Info: %s", text);
 
-        gdm_greeter_login_window_info (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), text);
+        gdm_greeter_login_window_info (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, text);
 }
 
 static void
@@ -86,7 +86,17 @@ on_problem (GdmGreeterClient  *client,
 {
         g_debug ("GdmGreeterSession: Problem: %s", text);
 
-        gdm_greeter_login_window_problem (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), text);
+        gdm_greeter_login_window_problem (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, text);
+}
+
+static void
+on_service_unavailable (GdmGreeterClient  *client,
+                        const char        *service_name,
+                        GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Service Unavailable: %s", service_name);
+
+        gdm_greeter_login_window_service_unavailable (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name);
 }
 
 static void
@@ -96,40 +106,30 @@ on_ready (GdmGreeterClient  *client,
 {
         g_debug ("GdmGreeterSession: Ready");
 
-        gdm_greeter_login_window_ready (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
+        gdm_greeter_login_window_ready (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window),
+                                        service_name);
 }
 
 static void
-on_reset (GdmGreeterClient  *client,
-          GdmGreeterSession *session)
+on_conversation_stopped (GdmGreeterClient  *client,
+                         const char        *service_name,
+                         GdmGreeterSession *session)
 {
-        g_debug ("GdmGreeterSession: Reset");
-
-        session->priv->num_tries = 0;
+        g_debug ("GdmGreeterSession: Conversation '%s' stopped", service_name);
 
-        gdm_greeter_login_window_reset (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
+        gdm_greeter_login_window_conversation_stopped (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window),
+                                                       service_name);
 }
 
 static void
-on_authentication_failed (GdmGreeterClient  *client,
-                          GdmGreeterSession *session)
+on_reset (GdmGreeterClient  *client,
+          GdmGreeterSession *session)
 {
-        g_debug ("GdmGreeterSession: Authentication failed");
-
-        session->priv->num_tries++;
-
-        if (session->priv->num_tries < MAX_LOGIN_TRIES) {
-                g_debug ("GdmGreeterSession: Retrying login (%d)",
-                         session->priv->num_tries);
+        g_debug ("GdmGreeterSession: Reset");
 
-                gdm_greeter_login_window_authentication_failed (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
-        } else {
-                g_debug ("GdmGreeterSession: Maximum number of login tries exceeded (%d) - resetting",
-                         session->priv->num_tries - 1);
-                session->priv->num_tries = 0;
+        session->priv->num_tries = 0;
 
-                gdm_greeter_login_window_reset (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
-        }
+        gdm_greeter_login_window_reset (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
 }
 
 static void
@@ -181,10 +181,11 @@ on_timed_login_requested (GdmGreeterClient  *client,
 
 static void
 on_session_opened (GdmGreeterClient  *client,
+                   const char        *service_name,
                    GdmGreeterSession *session)
 {
         g_debug ("GdmGreeterSession: session opened");
-        gdm_greeter_login_window_session_opened (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
+        gdm_greeter_login_window_session_opened (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name);
 }
 
 static void
@@ -195,7 +196,7 @@ on_info_query (GdmGreeterClient  *client,
 {
         g_debug ("GdmGreeterSession: Info query: %s", text);
 
-        gdm_greeter_login_window_info_query (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), text);
+        gdm_greeter_login_window_info_query (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, text);
 }
 
 static void
@@ -206,10 +207,18 @@ on_secret_info_query (GdmGreeterClient  *client,
 {
         g_debug ("GdmGreeterSession: Secret info query: %s", text);
 
-        gdm_greeter_login_window_secret_info_query (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), text);
+        gdm_greeter_login_window_secret_info_query (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, text);
 }
 
 static void
+on_start_conversation (GdmGreeterLoginWindow *login_window,
+                       const char            *service_name,
+                       GdmGreeterSession     *session)
+{
+        gdm_greeter_client_call_start_conversation (session->priv->client,
+                                                    service_name);
+}
+static void
 on_begin_auto_login (GdmGreeterLoginWindow *login_window,
                      const char            *username,
                      GdmGreeterSession     *session)
@@ -220,29 +229,32 @@ on_begin_auto_login (GdmGreeterLoginWindow *login_window,
 
 static void
 on_begin_verification (GdmGreeterLoginWindow *login_window,
+                       const char            *service_name,
                        GdmGreeterSession     *session)
 {
         gdm_greeter_client_call_begin_verification (session->priv->client,
-                                                    "gdm");
+                                                    service_name);
 }
 
 static void
 on_begin_verification_for_user (GdmGreeterLoginWindow *login_window,
+                                const char            *service_name,
                                 const char            *username,
                                 GdmGreeterSession     *session)
 {
         gdm_greeter_client_call_begin_verification_for_user (session->priv->client,
-                                                             "gdm",
+                                                             service_name,
                                                              username);
 }
 
 static void
 on_query_answer (GdmGreeterLoginWindow *login_window,
+                 const char            *service_name,
                  const char            *text,
                  GdmGreeterSession     *session)
 {
         gdm_greeter_client_call_answer_query (session->priv->client,
-                                              "gdm",
+                                              service_name,
                                               text);
 }
 
@@ -270,7 +282,6 @@ on_cancelled (GdmGreeterLoginWindow *login_window,
               GdmGreeterSession     *session)
 {
         gdm_greeter_client_call_cancel (session->priv->client);
-        gdm_greeter_client_call_start_conversation (session->priv->client, "gdm");
 }
 
 static void
@@ -281,9 +292,10 @@ on_disconnected (GdmGreeterSession     *session)
 
 static void
 on_start_session (GdmGreeterLoginWindow *login_window,
+                  const char            *service_name,
                   GdmGreeterSession     *session)
 {
-        gdm_greeter_client_call_start_session_when_ready (session->priv->client, "gdm", TRUE);
+        gdm_greeter_client_call_start_session_when_ready (session->priv->client, service_name, TRUE);
 }
 
 static int
@@ -356,6 +368,40 @@ toggle_panel (GdmGreeterSession *session,
         gdm_profile_end (NULL);
 }
 
+
+static void
+load_login_plugins (GdmGreeterSession *session)
+{
+        GList *extensions, *node;
+        GIOExtensionPoint *extension_point;
+
+        g_debug ("GdmGreeterSession: loading plugins");
+
+        extension_point = g_io_extension_point_register (GDM_LOGIN_EXTENSION_POINT_NAME);
+        g_io_extension_point_set_required_type (extension_point,
+                                                GDM_TYPE_LOGIN_EXTENSION);
+
+        g_io_modules_load_all_in_directory (GDM_SIMPLE_GREETER_PLUGINS_DIR);
+
+        extensions = g_io_extension_point_get_extensions (extension_point);
+        for (node = extensions; node != NULL; node = node->next) {
+                GIOExtension *extension;
+                GdmLoginExtension *login_extension;
+
+                extension = (GIOExtension *) node->data;
+
+                g_debug ("GdmGreeterSession: adding extension '%s'",
+                         g_io_extension_get_name (extension));
+
+                login_extension = g_object_new (g_io_extension_get_type (extension), NULL);
+
+                gdm_greeter_login_window_add_extension (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window),
+                                                        login_extension);
+        }
+
+        g_debug ("GdmGreeterSession: done loading plugins");
+}
+
 static void
 toggle_login_window (GdmGreeterSession *session,
                      gboolean           enabled)
@@ -368,7 +414,10 @@ toggle_login_window (GdmGreeterSession *session,
                 is_local = gdm_greeter_client_get_display_is_local (session->priv->client);
                 g_debug ("GdmGreeterSession: Starting a login window local:%d", is_local);
                 session->priv->login_window = gdm_greeter_login_window_new (is_local);
-
+                g_signal_connect (session->priv->login_window,
+                                  "start-conversation",
+                                  G_CALLBACK (on_start_conversation),
+                                  session);
                 g_signal_connect (session->priv->login_window,
                                   "begin-auto-login",
                                   G_CALLBACK (on_begin_auto_login),
@@ -402,6 +451,8 @@ toggle_login_window (GdmGreeterSession *session,
                                   G_CALLBACK (on_start_session),
                                   session);
                 gtk_widget_show (session->priv->login_window);
+
+                load_login_plugins (session);
         } else {
                 gtk_widget_destroy (session->priv->login_window);
                 session->priv->login_window = NULL;
@@ -424,8 +475,6 @@ gdm_greeter_session_start (GdmGreeterSession *session,
         toggle_panel (session, TRUE);
         toggle_login_window (session, TRUE);
 
-        gdm_greeter_client_call_start_conversation (session->priv->client, "gdm");
-
         gdm_profile_end (NULL);
 
         return res;
@@ -559,16 +608,20 @@ gdm_greeter_session_init (GdmGreeterSession *session)
                           G_CALLBACK (on_problem),
                           session);
         g_signal_connect (session->priv->client,
+                          "service-unavailable",
+                          G_CALLBACK (on_service_unavailable),
+                          session);
+        g_signal_connect (session->priv->client,
                           "ready",
                           G_CALLBACK (on_ready),
                           session);
         g_signal_connect (session->priv->client,
-                          "reset",
-                          G_CALLBACK (on_reset),
+                          "conversation-stopped",
+                          G_CALLBACK (on_conversation_stopped),
                           session);
         g_signal_connect (session->priv->client,
-                          "authentication-failed",
-                          G_CALLBACK (on_authentication_failed),
+                          "reset",
+                          G_CALLBACK (on_reset),
                           session);
         g_signal_connect (session->priv->client,
                           "selected-user-changed",
diff --git a/gui/simple-greeter/gdm-user-chooser-widget.c b/gui/simple-greeter/gdm-user-chooser-widget.c
index 0f73cc5..60ed160 100644
--- a/gui/simple-greeter/gdm-user-chooser-widget.c
+++ b/gui/simple-greeter/gdm-user-chooser-widget.c
@@ -654,9 +654,30 @@ gdm_user_chooser_widget_set_show_user_auto (GdmUserChooserWidget *widget,
 char *
 gdm_user_chooser_widget_get_chosen_user_name (GdmUserChooserWidget *widget)
 {
+        char *active_item_id;
+        gboolean isnt_user;
+
         g_return_val_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget), NULL);
 
-        return gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (widget));
+        active_item_id = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (widget));
+        if (active_item_id == NULL) {
+                g_debug ("GdmUserChooserWidget: no active item in list");
+                return NULL;
+        }
+
+        gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (widget), active_item_id,
+                                        NULL, NULL, NULL, NULL, NULL,
+                                        &isnt_user);
+
+        if (isnt_user) {
+                g_debug ("GdmUserChooserWidget: active item '%s' isn't a user", active_item_id);
+                g_free (active_item_id);
+                return NULL;
+        }
+
+        g_debug ("GdmUserChooserWidget: active item '%s' is a user", active_item_id);
+
+        return active_item_id;
 }
 
 void
diff --git a/gui/simple-greeter/libgdmsimplegreeter/Makefile.am b/gui/simple-greeter/libgdmsimplegreeter/Makefile.am
new file mode 100644
index 0000000..07b55af
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/Makefile.am
@@ -0,0 +1,43 @@
+NULL =
+
+AM_CPPFLAGS = \
+	-I.					\
+	-I..					\
+	-I$(top_srcdir)/common			\
+	-DBINDIR=\"$(bindir)\"			\
+	-DDATADIR=\"$(datadir)\"		\
+	-DLIBDIR=\"$(libdir)\"			\
+	-DLIBEXECDIR=\"$(libexecdir)\"		\
+	-DLOGDIR=\"$(logdir)\"			\
+	-DPIXMAPDIR=\"$(pixmapdir)\"		\
+	-DSBINDIR=\"$(sbindir)\"		\
+	$(GTK_CFLAGS)				\
+	$(NULL)
+
+lib_LTLIBRARIES = 			\
+	libgdmsimplegreeter.la		\
+	$(NULL)
+
+libgdmsimplegreeter_la_SOURCES =		\
+	gdm-login-extension.h			\
+	gdm-login-extension.c			\
+	$(NULL)
+
+libgdmsimplegreeter_la_LIBADD =			\
+	$(GTK_LIBS)				\
+	$(top_builddir)/common/libgdmcommon.la	\
+	$(NULL)
+
+libgdmsimplegreeter_la_LDFLAGS = 		\
+	-version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \
+	-no-undefined				\
+	$(NULL)
+
+headersdir = $(includedir)/gdm/simple-greeter
+headers_HEADERS = gdm-login-extension.h
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = gdmsimplegreeter.pc
+
+EXTRA_DIST = gdmsimplegreeter.pc
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.c b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.c
new file mode 100644
index 0000000..ae620e3
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ *
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gdm-login-extension.h"
+#include "gdm-marshal.h"
+
+enum {
+        ENABLED,
+        DISABLED,
+        ANSWER,
+        USER_CHOSEN,
+        CANCEL,
+        MESSAGE_QUEUE_EMPTY,
+        NUMBER_OF_SIGNALS
+};
+
+static guint signals [NUMBER_OF_SIGNALS] = { 0, };
+
+static void gdm_login_extension_class_init (gpointer g_iface);
+
+GType
+gdm_login_extension_get_type (void)
+{
+        static GType login_extension_type = 0;
+
+        if (!login_extension_type) {
+                login_extension_type = g_type_register_static_simple (G_TYPE_INTERFACE,
+                                                           "GdmLoginExtension",
+                                                           sizeof (GdmLoginExtensionIface),
+                                                           (GClassInitFunc) gdm_login_extension_class_init,
+                                                           0, NULL, 0);
+
+                g_type_interface_add_prerequisite (login_extension_type, G_TYPE_OBJECT);
+        }
+
+        return login_extension_type;
+}
+
+static void
+gdm_login_extension_class_init (gpointer g_iface)
+{
+        GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+
+        signals [ENABLED] =
+                g_signal_new ("enabled",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, enabled),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+        signals [DISABLED] =
+                g_signal_new ("disabled",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, disabled),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+        signals [ANSWER] =
+                g_signal_new ("answer",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, answer),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+        signals [USER_CHOSEN] =
+                g_signal_new ("user-chosen",
+                              iface_type,
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, user_chosen),
+                              NULL,
+                              NULL,
+                              gdm_marshal_BOOLEAN__STRING,
+                              G_TYPE_BOOLEAN,
+                              1, G_TYPE_STRING);
+        signals [CANCEL] =
+                g_signal_new ("cancel",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, cancel),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [MESSAGE_QUEUE_EMPTY] =
+                g_signal_new ("message-queue-empty",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, message_queue_empty),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+}
+
+GIcon *
+gdm_login_extension_get_icon (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_icon (extension);
+}
+
+char *
+gdm_login_extension_get_description (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_description (extension);
+}
+
+char *
+gdm_login_extension_get_name (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_name (extension);
+}
+
+void
+gdm_login_extension_set_enabled (GdmLoginExtension *extension,
+                                   gboolean             should_enable)
+{
+        g_object_set_data (G_OBJECT (extension),
+                           "gdm-greeter-extension-is-disabled",
+                           GINT_TO_POINTER (!should_enable));
+
+        if (should_enable) {
+                g_signal_emit (G_OBJECT (extension), signals [ENABLED], 0);
+        } else {
+                g_signal_emit (G_OBJECT (extension), signals [DISABLED], 0);
+        }
+}
+
+gboolean
+gdm_login_extension_is_enabled (GdmLoginExtension *extension)
+{
+        return !g_object_get_data (G_OBJECT (extension), "gdm-greeter-extension-is-disabled");
+}
+
+gboolean
+gdm_login_extension_is_choosable (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->is_choosable (extension);
+}
+
+gboolean
+gdm_login_extension_is_visible (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->is_visible (extension);
+}
+
+void
+gdm_login_extension_queue_message (GdmLoginExtension   *extension,
+                                     GdmServiceMessageType  type,
+                                     const char            *message)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->queue_message (extension,
+                                                                    type,
+                                                                    message);
+}
+
+void
+gdm_login_extension_ask_question (GdmLoginExtension *extension,
+                                    const char          *message)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->ask_question (extension,
+                                                                   message);
+}
+
+void
+gdm_login_extension_ask_secret (GdmLoginExtension *extension,
+                                  const char          *message)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->ask_secret (extension,
+                                                                 message);
+
+}
+
+void
+gdm_login_extension_reset (GdmLoginExtension *extension)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->reset (extension);
+}
+
+void
+gdm_login_extension_set_ready (GdmLoginExtension *extension)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->set_ready (extension);
+}
+
+gboolean
+gdm_login_extension_focus (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->focus (extension);
+}
+
+char *
+gdm_login_extension_get_service_name (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_service_name (extension);
+
+}
+
+gboolean
+gdm_login_extension_has_queued_messages (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->has_queued_messages (extension);
+}
+
+GtkWidget *
+gdm_login_extension_get_page (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_page (extension);
+}
+
+GtkActionGroup *
+gdm_login_extension_get_actions (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_actions (extension);
+}
+
+void
+_gdm_login_extension_emit_answer (GdmLoginExtension *extension,
+                                  const char        *answer)
+{
+        g_signal_emit (extension, signals [ANSWER], 0, answer);
+
+}
+
+void
+_gdm_login_extension_emit_cancel (GdmLoginExtension *extension)
+{
+        g_signal_emit (extension, signals [CANCEL], 0);
+}
+
+gboolean
+_gdm_login_extension_emit_choose_user (GdmLoginExtension *extension,
+                                     const char          *username)
+{
+        gboolean was_chosen;
+
+        was_chosen = FALSE;
+
+        g_signal_emit (extension, signals [USER_CHOSEN], 0, username, &was_chosen);
+
+        return was_chosen;
+}
+
+void
+_gdm_login_extension_emit_message_queue_empty (GdmLoginExtension *extension)
+{
+        g_signal_emit (extension, signals [MESSAGE_QUEUE_EMPTY], 0);
+
+}
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h
new file mode 100644
index 0000000..7f56de1
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2009 Red Hat, Inc.  *
+ *
+ * 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Ray Strode
+ */
+
+#ifndef __GDM_LOGIN_EXTENSION_H
+#define __GDM_LOGIN_EXTENSION_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_LOGIN_EXTENSION         (gdm_login_extension_get_type ())
+#define GDM_LOGIN_EXTENSION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_LOGIN_EXTENSION, GdmLoginExtension))
+#define GDM_LOGIN_EXTENSION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_LOGIN_EXTENSION, GdmLoginExtensionClass))
+#define GDM_IS_LOGIN_EXTENSION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_LOGIN_EXTENSION))
+#define GDM_LOGIN_EXTENSION_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GDM_TYPE_LOGIN_EXTENSION, GdmLoginExtensionIface))
+
+#define GDM_LOGIN_EXTENSION_POINT_NAME "gdm-login"
+#define GDM_LOGIN_EXTENSION_DEFAULT_ACTION "default-action"
+#define GDM_LOGIN_EXTENSION_OTHER_USER "__other"
+
+typedef struct _GdmLoginExtension      GdmLoginExtension;
+typedef struct _GdmLoginExtensionIface GdmLoginExtensionIface;
+
+typedef enum {
+        GDM_SERVICE_MESSAGE_TYPE_INFO,
+        GDM_SERVICE_MESSAGE_TYPE_PROBLEM
+} GdmServiceMessageType;
+
+struct _GdmLoginExtensionIface
+{
+        GTypeInterface base_iface;
+
+        /* methods */
+        GIcon *         (* get_icon)             (GdmLoginExtension   *extension);
+        char *          (* get_description)      (GdmLoginExtension   *extension);
+        char *          (* get_name)             (GdmLoginExtension   *extension);
+
+        gboolean        (* is_choosable)         (GdmLoginExtension   *extension);
+        gboolean        (* is_visible)           (GdmLoginExtension   *extension);
+
+        void            (* queue_message)        (GdmLoginExtension   *extension,
+                                                  GdmServiceMessageType  type,
+                                                  const char            *message);
+        void            (* ask_question)         (GdmLoginExtension   *extension,
+                                                  const char            *message);
+        void            (* ask_secret)           (GdmLoginExtension   *extension,
+                                                  const char            *message);
+        void             (* reset)               (GdmLoginExtension   *extension);
+        void             (* set_ready)           (GdmLoginExtension   *extension);
+        char *           (* get_service_name)    (GdmLoginExtension   *extension);
+        GtkWidget *      (* get_page)            (GdmLoginExtension   *extension);
+        GtkActionGroup * (* get_actions)         (GdmLoginExtension   *extension);
+        void             (* request_answer)      (GdmLoginExtension   *extension);
+        gboolean         (* has_queued_messages) (GdmLoginExtension   *extension);
+        gboolean         (* focus)               (GdmLoginExtension   *extension);
+
+        /* signals */
+        void            (* enabled)              (GdmLoginExtension   *extension);
+        void            (* disabled)             (GdmLoginExtension   *extension);
+        char *          (* answer)               (GdmLoginExtension   *extension);
+        void            (* cancel)               (GdmLoginExtension   *extension);
+        gboolean        (* user_chosen)          (GdmLoginExtension   *extension);
+        gboolean        (* message_queue_empty)  (GdmLoginExtension   *extension);
+};
+
+GType    gdm_login_extension_get_type        (void) G_GNUC_CONST;
+
+GIcon   *gdm_login_extension_get_icon        (GdmLoginExtension   *extension);
+char    *gdm_login_extension_get_description (GdmLoginExtension   *extension);
+char    *gdm_login_extension_get_name        (GdmLoginExtension   *extension);
+void     gdm_login_extension_set_enabled     (GdmLoginExtension   *extension,
+                                              gboolean               should_enable);
+gboolean gdm_login_extension_is_enabled      (GdmLoginExtension   *extension);
+gboolean gdm_login_extension_is_choosable    (GdmLoginExtension   *extension);
+gboolean gdm_login_extension_is_visible      (GdmLoginExtension   *extension);
+
+void   gdm_login_extension_queue_message  (GdmLoginExtension   *extension,
+                                           GdmServiceMessageType  type,
+                                           const char            *message);
+
+void   gdm_login_extension_ask_question (GdmLoginExtension   *extension,
+                                         const char            *message);
+
+void   gdm_login_extension_ask_secret   (GdmLoginExtension   *extension,
+                                         const char            *message);
+
+void     gdm_login_extension_reset        (GdmLoginExtension *extension);
+void     gdm_login_extension_set_ready    (GdmLoginExtension *extension);
+gboolean gdm_login_extension_focus        (GdmLoginExtension *extension);
+
+char     *gdm_login_extension_get_service_name    (GdmLoginExtension *extension);
+gboolean  gdm_login_extension_has_queued_messages (GdmLoginExtension *extension);
+
+GtkWidget      *gdm_login_extension_get_page    (GdmLoginExtension *extension);
+GtkActionGroup *gdm_login_extension_get_actions (GdmLoginExtension *extension);
+
+
+/* protected
+ */
+void      _gdm_login_extension_emit_answer           (GdmLoginExtension *extension,
+                                                      const char          *answer);
+void      _gdm_login_extension_emit_cancel           (GdmLoginExtension *extension);
+gboolean  _gdm_login_extension_emit_choose_user      (GdmLoginExtension *extension,
+                                                      const char          *username);
+
+void      _gdm_login_extension_emit_message_queue_empty (GdmLoginExtension *extension);
+
+
+G_END_DECLS
+#endif /* __GDM_LOGIN_EXTENSION_H */
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc b/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc
new file mode 100644
index 0000000..eadd70b
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc
@@ -0,0 +1,11 @@
+prefix=/usr
+exec_prefix=${prefix}
+libdir=/usr/lib64
+includedir=${prefix}/include
+pluginsdir=/usr/lib64/gdm/simple-greeter/plugins
+
+Name: GDM Simple Greeter
+Description: Library for GDM Simple Greeter Plugins
+Version: 3.0.1
+Libs: -L${libdir} -lgdmsimplegreeter
+Cflags: -I${includedir}/gdm/simple-greeter
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in b/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in
new file mode 100644
index 0000000..a429d99
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in
@@ -0,0 +1,11 @@
+prefix= prefix@
+exec_prefix= exec_prefix@
+libdir= libdir@
+includedir= includedir@
+pluginsdir= GDM_SIMPLE_GREETER_PLUGINS_DIR@
+
+Name: GDM Simple Greeter
+Description: Library for GDM Simple Greeter Plugins
+Version: @VERSION@
+Libs: -L${libdir} -lgdmsimplegreeter
+Cflags: -I${includedir}/gdm/simple-greeter
diff --git a/gui/simple-greeter/plugins/Makefile.am b/gui/simple-greeter/plugins/Makefile.am
new file mode 100644
index 0000000..c0390db
--- /dev/null
+++ b/gui/simple-greeter/plugins/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = password
diff --git a/gui/simple-greeter/plugins/password/Makefile.am b/gui/simple-greeter/plugins/password/Makefile.am
new file mode 100644
index 0000000..41272a1
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/Makefile.am
@@ -0,0 +1,51 @@
+NULL =
+PAM_SERVICE_NAME = gdm-password
+
+extensiondir = $(extensionsdatadir)/password
+extension_DATA = page.ui
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/common				\
+	-I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter	\
+	-DDMCONFDIR=\""$(dmconfdir)"\"			\
+	-DGDMCONFDIR=\"$(gdmconfdir)\"                  \
+	-DPLUGINDATADIR=\""$(extensiondir)"\"		\
+	-DGDM_PASSWORD_EXTENSION_SERVICE_NAME=\""$(PAM_SERVICE_NAME)"\"	\
+	-DSYSCONFDIR=\""$(sysconfdir)"\"		\
+	-DLIBLOCALEDIR=\""$(prefix)/lib/locale"\"	\
+	-DGNOMELOCALEDIR=\""$(datadir)/locale"\" 	\
+	-DLIBEXECDIR=\""$(libexecdir)"\" 		\
+	-DSBINDIR=\""$(sbindir)"\"		 	\
+	$(DISABLE_DEPRECATED_CFLAGS)	\
+	$(GTK_CFLAGS)					\
+	$(SIMPLE_GREETER_CFLAGS)			\
+	$(POLKIT_GNOME_CFLAGS)				\
+	$(NULL)
+
+plugindir = $(GDM_SIMPLE_GREETER_PLUGINS_DIR)
+plugin_LTLIBRARIES = libpassword.la
+
+libpassword_la_CFLAGS =			\
+	$(SIMPLE_GREETER_CFLAGS)	\
+	$(NULL)
+
+libpassword_la_LDFLAGS = -module -avoid-version -export-dynamic
+libpassword_la_LIBADD = ../../../../common/libgdmcommon.la \
+			../../libgdmsimplegreeter/libgdmsimplegreeter.la
+libpassword_la_SOURCES =				\
+			gdm-password-extension.h	\
+			gdm-password-extension.c
+
+$(PAM_SERVICE_NAME): $(PAM_SERVICE_NAME).pam
+	cp $(PAM_SERVICE_NAME).pam $(PAM_SERVICE_NAME)
+
+pamdir = $(PAM_PREFIX)/pam.d
+pam_DATA = $(PAM_SERVICE_NAME)
+
+EXTRA_DIST = $(extension_DATA) $(PAM_SERVICE_NAME).pam
+CLEANFILES = $(PAM_SERVICE_NAME)
+
+MAINTAINERCLEANFILES =                  \
+        *~                              \
+        $(PAM_SERVICE_NAME)             \
+        Makefile.in
diff --git a/gui/simple-greeter/plugins/password/gdm-password b/gui/simple-greeter/plugins/password/gdm-password
new file mode 100644
index 0000000..bac431d
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/gdm-password
@@ -0,0 +1,19 @@
+# Sample PAM file for doing password authentication.
+# Distros should replace this with what makes sense for them.
+auth        required      pam_env.so
+auth        sufficient    pam_unix.so nullok try_first_pass
+auth        requisite     pam_succeed_if.so uid >= 500 quiet
+auth        required      pam_deny.so
+
+account     required      pam_unix.so
+account     sufficient    pam_localuser.so
+account     sufficient    pam_succeed_if.so uid < 500 quiet
+account     required      pam_permit.so
+
+password    requisite     pam_cracklib.so try_first_pass retry=3 type=
+password    sufficient    pam_unix.so nullok try_first_pass use_authtok
+password    required      pam_deny.so
+
+session     optional      pam_keyinit.so revoke
+session     required      pam_limits.so
+session     required      pam_unix.so
diff --git a/gui/simple-greeter/plugins/password/gdm-password-extension.c b/gui/simple-greeter/plugins/password/gdm-password-extension.c
new file mode 100644
index 0000000..6391db1
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/gdm-password-extension.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ *
+ */
+
+#include <config.h>
+#include "gdm-password-extension.h"
+#include "gdm-login-extension.h"
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+struct _GdmPasswordExtensionPrivate
+{
+        GIcon     *icon;
+        GtkWidget *page;
+        GtkActionGroup *actions;
+        GtkAction *login_action;
+
+        GtkWidget *message_label;
+        GtkWidget *prompt_label;
+        GtkWidget *prompt_entry;
+
+        GQueue    *message_queue;
+        guint      message_timeout_id;
+
+        guint      answer_pending : 1;
+};
+
+typedef struct {
+        char                       *text;
+        GdmServiceMessageType  type;
+} QueuedMessage;
+
+static void gdm_password_extension_finalize (GObject *object);
+
+static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdmPasswordExtension,
+                         gdm_password_extension,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION,
+                                                gdm_login_extension_iface_init));
+
+static void
+set_message (GdmPasswordExtension *extension,
+             const char           *message)
+{
+        gtk_widget_show (extension->priv->message_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message);
+}
+
+static void
+free_queued_message (QueuedMessage *message)
+{
+        g_free (message->text);
+        g_slice_free (QueuedMessage, message);
+}
+
+static void
+purge_message_queue (GdmPasswordExtension *extension)
+{
+        if (extension->priv->message_timeout_id) {
+                g_source_remove (extension->priv->message_timeout_id);
+                extension->priv->message_timeout_id = 0;
+        }
+        g_queue_foreach (extension->priv->message_queue,
+                         (GFunc) free_queued_message,
+                         NULL);
+        g_queue_clear (extension->priv->message_queue);
+}
+
+static gboolean
+dequeue_message (GdmPasswordExtension *extension)
+{
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                int duration;
+                gboolean needs_beep;
+
+                QueuedMessage *message;
+                message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue);
+
+                switch (message->type) {
+                        case GDM_SERVICE_MESSAGE_TYPE_INFO:
+                                needs_beep = FALSE;
+                                break;
+                        case GDM_SERVICE_MESSAGE_TYPE_PROBLEM:
+                                needs_beep = TRUE;
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                }
+
+                set_message (extension, message->text);
+
+                duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000;
+                duration = CLAMP (duration, 400, 3000);
+
+                extension->priv->message_timeout_id = g_timeout_add (duration,
+                                                                     (GSourceFunc) dequeue_message,
+                                                                     extension);
+                if (needs_beep) {
+                        gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension->priv->page)));
+                }
+
+                free_queued_message (message);
+        } else {
+                extension->priv->message_timeout_id = 0;
+
+                _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension));
+        }
+
+        return FALSE;
+}
+
+static void
+gdm_password_extension_queue_message (GdmLoginExtension   *login_extension,
+                                      GdmServiceMessageType  type,
+                                      const char            *text)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+
+        QueuedMessage *message = g_slice_new (QueuedMessage);
+
+        message->text = g_strdup (text);
+        message->type = type;
+
+        g_queue_push_tail (extension->priv->message_queue, message);
+
+        if (extension->priv->message_timeout_id == 0) {
+                dequeue_message (extension);
+        }
+}
+
+static void
+gdm_password_extension_ask_question (GdmLoginExtension *login_extension,
+                                     const char          *message)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+
+        gtk_action_set_sensitive (extension->priv->login_action, TRUE);
+}
+
+static void
+gdm_password_extension_ask_secret (GdmLoginExtension *login_extension,
+                                   const char          *message)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+
+        gtk_action_set_sensitive (extension->priv->login_action, TRUE);
+}
+
+static void
+gdm_password_extension_reset (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        gtk_widget_hide (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        extension->priv->answer_pending = FALSE;
+
+        set_message (extension, "");
+        purge_message_queue (extension);
+
+        gdm_login_extension_set_enabled (login_extension, FALSE);
+}
+
+static void
+gdm_password_extension_set_ready (GdmLoginExtension *extension)
+{
+        gdm_login_extension_set_enabled (extension, TRUE);
+}
+
+static char *
+gdm_password_extension_get_service_name (GdmLoginExtension *extension)
+{
+        return g_strdup (GDM_PASSWORD_EXTENSION_SERVICE_NAME);
+}
+
+static GtkWidget *
+gdm_password_extension_get_page (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        return extension->priv->page;
+}
+
+static GtkActionGroup *
+gdm_password_extension_get_actions (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        return g_object_ref (extension->priv->actions);
+}
+
+static void
+request_answer (GdmPasswordExtension *extension)
+{
+        const char *text;
+
+        if (!extension->priv->answer_pending) {
+                _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), NULL);
+                return;
+        }
+
+        extension->priv->answer_pending = FALSE;
+        text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry));
+        _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), text);
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_widget_hide (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+}
+
+static gboolean
+gdm_password_extension_focus (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        if (!extension->priv->answer_pending) {
+                _gdm_login_extension_emit_answer (login_extension, NULL);
+                return FALSE;
+        }
+
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        return TRUE;
+}
+
+static gboolean
+gdm_password_extension_has_queued_messages (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+
+        if (extension->priv->message_timeout_id != 0) {
+                return TRUE;
+        }
+
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static GIcon *
+gdm_password_extension_get_icon (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        return g_object_ref (extension->priv->icon);
+}
+
+static char *
+gdm_password_extension_get_name (GdmLoginExtension *login_extension)
+{
+        return g_strdup (_("Password Authentication"));
+}
+
+static char *
+gdm_password_extension_get_description (GdmLoginExtension *login_extension)
+{
+        return g_strdup (_("Log into session with username and password"));
+}
+
+static gboolean
+gdm_password_extension_is_choosable (GdmLoginExtension *login_extension)
+{
+        return FALSE;
+}
+
+static gboolean
+gdm_password_extension_is_visible (GdmLoginExtension *login_extension)
+{
+        return TRUE;
+}
+
+static void
+gdm_login_extension_iface_init (GdmLoginExtensionIface *iface)
+{
+        iface->get_icon = gdm_password_extension_get_icon;
+        iface->get_description = gdm_password_extension_get_description;
+        iface->get_name = gdm_password_extension_get_name;
+        iface->is_choosable = gdm_password_extension_is_choosable;
+        iface->is_visible = gdm_password_extension_is_visible;
+        iface->queue_message = gdm_password_extension_queue_message;
+        iface->ask_question = gdm_password_extension_ask_question;
+        iface->ask_secret = gdm_password_extension_ask_secret;
+        iface->reset = gdm_password_extension_reset;
+        iface->set_ready = gdm_password_extension_set_ready;
+        iface->get_service_name = gdm_password_extension_get_service_name;
+        iface->get_page = gdm_password_extension_get_page;
+        iface->get_actions = gdm_password_extension_get_actions;
+        iface->focus = gdm_password_extension_focus;
+        iface->has_queued_messages = gdm_password_extension_has_queued_messages;
+}
+
+static void
+gdm_password_extension_class_init (GdmPasswordExtensionClass *extension_class)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (extension_class);
+
+        object_class->finalize = gdm_password_extension_finalize;
+
+        g_type_class_add_private (extension_class,
+                                  sizeof (GdmPasswordExtensionPrivate));
+}
+
+static void
+gdm_password_extension_finalize (GObject *object)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (object);
+
+        purge_message_queue (extension);
+}
+
+static void
+on_activate_log_in (GdmPasswordExtension *extension,
+                    GtkAction            *action)
+{
+        request_answer (extension);
+        gtk_action_set_sensitive (action, FALSE);
+}
+
+static void
+create_page (GdmPasswordExtension *extension)
+{
+        GtkBuilder *builder;
+        GObject *object;
+        GError *error;
+
+        builder = gtk_builder_new ();
+
+        error = NULL;
+        gtk_builder_add_from_file (builder,
+                                   PLUGINDATADIR "/page.ui",
+                                   &error);
+
+        if (error != NULL) {
+                g_warning ("Could not load UI file: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        object = gtk_builder_get_object (builder, "page");
+        g_object_ref (object);
+
+        extension->priv->page = GTK_WIDGET (object);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-label");
+        g_object_ref (object);
+        extension->priv->prompt_label = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_label);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-entry");
+        g_object_ref (object);
+        extension->priv->prompt_entry = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_entry);
+
+        object = gtk_builder_get_object (builder, "auth-message-label");
+        g_object_ref (object);
+        extension->priv->message_label = GTK_WIDGET (object);
+        gtk_widget_show (extension->priv->message_label);
+
+        g_object_unref (builder);
+}
+
+static void
+create_actions (GdmPasswordExtension *extension)
+{
+        GtkAction *action;
+
+        extension->priv->actions = gtk_action_group_new (GDM_PASSWORD_EXTENSION_NAME);
+
+        action = gtk_action_new (GDM_LOGIN_EXTENSION_DEFAULT_ACTION,
+                                 _("Log In"), NULL, NULL);
+        g_signal_connect_swapped (action, "activate",
+                                  G_CALLBACK (on_activate_log_in), extension);
+        g_object_set (G_OBJECT (action), "icon-name", "go-home", NULL);
+        gtk_action_group_add_action (extension->priv->actions,
+                                     action);
+
+        extension->priv->login_action = action;
+}
+
+static void
+gdm_password_extension_init (GdmPasswordExtension *extension)
+{
+        extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension,
+                                                       GDM_TYPE_PASSWORD_EXTENSION,
+                                                       GdmPasswordExtensionPrivate);
+
+        extension->priv->icon = g_themed_icon_new ("dialog-password");
+        create_page (extension);
+        create_actions (extension);
+
+        extension->priv->message_queue = g_queue_new ();
+
+        gdm_password_extension_reset (GDM_LOGIN_EXTENSION (extension));
+}
+
+void
+g_io_module_load (GIOModule *module)
+{
+        g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME,
+                                        GDM_TYPE_PASSWORD_EXTENSION,
+                                        GDM_PASSWORD_EXTENSION_NAME,
+                                        G_MAXINT);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+}
diff --git a/gui/simple-greeter/plugins/password/gdm-password-extension.h b/gui/simple-greeter/plugins/password/gdm-password-extension.h
new file mode 100644
index 0000000..2b0a701
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/gdm-password-extension.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * 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 2, 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_PASSWORD_EXTENSION_H
+#define __GDM_PASSWORD_EXTENSION_H
+
+#include <glib-object.h>
+#include "gdm-login-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_PASSWORD_EXTENSION (gdm_password_extension_get_type ())
+#define GDM_PASSWORD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_PASSWORD_EXTENSION, GdmPasswordExtension))
+#define GDM_PASSWORD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_PASSWORD_EXTENSION, GdmPasswordExtensionClass))
+#define GDM_IS_PASSWORD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_PASSWORD_EXTENSION))
+#define GDM_IS_PASSWORD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_PASSWORD_EXTENSION))
+#define GDM_PASSWORD_EXTENSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_PASSWORD_EXTENSION, GdmPasswordExtensionClass))
+
+#define GDM_PASSWORD_EXTENSION_NAME "gdm-password-extension"
+
+typedef struct _GdmPasswordExtensionPrivate GdmPasswordExtensionPrivate;
+
+typedef struct
+{
+        GObject                  parent;
+        GdmPasswordExtensionPrivate *priv;
+} GdmPasswordExtension;
+
+typedef struct
+{
+        GObjectClass parent_class;
+} GdmPasswordExtensionClass;
+
+GType                 gdm_password_extension_get_type      (void);
+
+G_END_DECLS
+
+#endif /* GDM_PASSWORD_EXTENSION_H */
diff --git a/gui/simple-greeter/plugins/password/gdm-password.pam b/gui/simple-greeter/plugins/password/gdm-password.pam
new file mode 100644
index 0000000..bac431d
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/gdm-password.pam
@@ -0,0 +1,19 @@
+# Sample PAM file for doing password authentication.
+# Distros should replace this with what makes sense for them.
+auth        required      pam_env.so
+auth        sufficient    pam_unix.so nullok try_first_pass
+auth        requisite     pam_succeed_if.so uid >= 500 quiet
+auth        required      pam_deny.so
+
+account     required      pam_unix.so
+account     sufficient    pam_localuser.so
+account     sufficient    pam_succeed_if.so uid < 500 quiet
+account     required      pam_permit.so
+
+password    requisite     pam_cracklib.so try_first_pass retry=3 type=
+password    sufficient    pam_unix.so nullok try_first_pass use_authtok
+password    required      pam_deny.so
+
+session     optional      pam_keyinit.so revoke
+session     required      pam_limits.so
+session     required      pam_unix.so
diff --git a/gui/simple-greeter/plugins/password/page.ui b/gui/simple-greeter/plugins/password/page.ui
new file mode 100644
index 0000000..8fa5c7b
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/page.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.14"/>
+    <object class="GtkVBox" id="page">
+      <property name="visible">True</property>
+      <property name="orientation">vertical</property>
+      <child>
+        <object class="GtkHBox" id="auth-input-box">
+          <property name="visible">True</property>
+          <property name="spacing">6</property>
+          <child>
+            <object class="GtkLabel" id="auth-prompt-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="expand">False</property>
+              <property name="fill">False</property>
+              <property name="position">0</property>
+            </packing>
+          </child>
+          <child>
+            <object class="GtkEntry" id="auth-prompt-entry">
+              <property name="visible">True</property>
+              <property name="can_focus">True</property>
+              <property name="activates_default">True</property>
+            </object>
+            <packing>
+              <property name="position">1</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">0</property>
+        </packing>
+      </child>
+      <child>
+        <object class="GtkHBox" id="auth-message-box">
+          <property name="visible">True</property>
+          <child>
+            <object class="GtkLabel" id="auth-message-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="position">0</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">1</property>
+        </packing>
+      </child>
+    </object>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d2f59a7..d4d5a51 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -81,6 +81,7 @@ gui/simple-greeter/gdm-simple-greeter.schemas.in
 gui/simple-greeter/gdm-timer.c
 gui/simple-greeter/gdm-user-chooser-widget.c
 gui/simple-greeter/greeter-main.c
+gui/simple-greeter/plugins/password/gdm-password-extension.c
 utils/gdmflexiserver.c
 utils/gdm-screenshot.c
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]