[gdm/multi-stack: 17/35] Add a plugin based extension system to greeter



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

    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                                       |   44 +
 gui/simple-greeter/Makefile.am                     |   19 +
 gui/simple-greeter/gdm-greeter-client.c            |   21 +
 gui/simple-greeter/gdm-greeter-client.h            |    2 +
 gui/simple-greeter/gdm-greeter-login-window.c      | 1061 +++++++++++++++++---
 gui/simple-greeter/gdm-greeter-login-window.h      |   36 +-
 gui/simple-greeter/gdm-greeter-login-window.ui     |   67 +-
 gui/simple-greeter/gdm-greeter-plugin.c            |  254 +++++
 gui/simple-greeter/gdm-greeter-plugin.h            |   61 ++
 gui/simple-greeter/gdm-greeter-session.c           |  161 +++-
 gui/simple-greeter/gdm-plugin-manager.c            |  478 +++++++++
 gui/simple-greeter/gdm-plugin-manager.h            |   66 ++
 gui/simple-greeter/gdm-task-list.c                 |  385 +++++++
 gui/simple-greeter/gdm-task-list.h                 |   85 ++
 gui/simple-greeter/gdm-user-chooser-widget.c       |   23 +-
 gui/simple-greeter/libgdmsimplegreeter/Makefile.am |   48 +
 .../libgdmsimplegreeter/gdm-conversation.c         |  186 ++++
 .../libgdmsimplegreeter/gdm-conversation.h         |   93 ++
 .../libgdmsimplegreeter/gdm-greeter-extension.c    |   93 ++
 .../libgdmsimplegreeter/gdm-greeter-extension.h    |   55 +
 gui/simple-greeter/libgdmsimplegreeter/gdm-task.c  |  129 +++
 gui/simple-greeter/libgdmsimplegreeter/gdm-task.h  |   66 ++
 .../libgdmsimplegreeter/gdmsimplegreeter.pc.in     |   11 +
 gui/simple-greeter/plugins/Makefile.am             |    1 +
 gui/simple-greeter/plugins/password/Makefile.am    |   53 +
 .../plugins/password/gdm-password-extension.c      |  328 ++++++
 .../plugins/password/gdm-password-extension.h      |   56 +
 .../plugins/password/gdm-password.pam              |   19 +
 gui/simple-greeter/plugins/password/page.ui        |   57 ++
 gui/simple-greeter/plugins/password/plugin.c       |   40 +
 po/POTFILES.in                                     |    1 +
 32 files changed, 3745 insertions(+), 255 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 3a12767..8c8b74d 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)
@@ -197,6 +213,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 ---------------------------------------------------------------------------
 
@@ -1273,6 +1298,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
@@ -1401,6 +1441,10 @@ docs/Makefile
 gui/Makefile
 gui/simple-greeter/Makefile
 gui/simple-greeter/libnotificationarea/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
 gui/user-switch-applet/Makefile
 utils/Makefile
diff --git a/gui/simple-greeter/Makefile.am b/gui/simple-greeter/Makefile.am
index 6f7f3ee..b987de4 100644
--- a/gui/simple-greeter/Makefile.am
+++ b/gui/simple-greeter/Makefile.am
@@ -2,12 +2,15 @@ NULL =
 
 SUBDIRS = 				\
 	libnotificationarea		\
+	libgdmsimplegreeter		\
+	plugins				\
 	$(NULL)
 
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/common				\
 	-I$(top_builddir)/common			\
 	-I$(top_srcdir)/gui/simple-greeter/libnotificationarea	\
+	-I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter	\
 	-DDMCONFDIR=\""$(dmconfdir)"\"			\
 	-DGDMCONFDIR=\"$(gdmconfdir)\"                  \
 	-DDATADIR=\""$(datadir)"\"		 	\
@@ -20,6 +23,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)			\
@@ -85,10 +89,17 @@ test_greeter_login_window_SOURCES = 	\
 	gdm-user-chooser-widget.c	\
 	gdm-user-chooser-dialog.h	\
 	gdm-user-chooser-dialog.c	\
+	gdm-task-list.h			\
+	gdm-task-list.c			\
+	gdm-plugin-manager.h		\
+	gdm-plugin-manager.c		\
+	gdm-greeter-plugin.h		\
+	gdm-greeter-plugin.c		\
 	$(NULL)
 
 test_greeter_login_window_LDADD =	\
 	$(top_builddir)/common/libgdmcommon.la	\
+	$(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la	\
 	libgdmuser.la			\
 	$(COMMON_LIBS)			\
 	$(SIMPLE_GREETER_LIBS)		\
@@ -139,6 +150,7 @@ test_greeter_panel_SOURCES = 	\
 test_greeter_panel_LDADD =	\
 	$(top_builddir)/common/libgdmcommon.la	\
 	$(top_builddir)/gui/simple-greeter/libnotificationarea/libnotificationarea.la	\
+	$(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la	\
 	$(SIMPLE_GREETER_LIBS)		\
 	$(GTK_LIBS)			\
 	$(GCONF_LIBS)			\
@@ -314,18 +326,25 @@ gdm_simple_greeter_SOURCES =  		\
 	gdm-language-chooser-dialog.c	\
 	gdm-language-option-widget.h	\
 	gdm-language-option-widget.c	\
+	gdm-plugin-manager.h		\
+	gdm-plugin-manager.c		\
 	gdm-sessions.h			\
 	gdm-sessions.c			\
 	gdm-session-option-widget.h	\
 	gdm-session-option-widget.c	\
+	gdm-greeter-plugin.h		\
+	gdm-greeter-plugin.c		\
 	gdm-user-chooser-widget.h	\
 	gdm-user-chooser-widget.c	\
+	gdm-task-list.h			\
+	gdm-task-list.c			\
 	$(NULL)
 
 gdm_simple_greeter_LDADD = 		\
 	$(top_builddir)/common/libgdmcommon.la	\
 	libgdmuser.la			\
 	$(top_builddir)/gui/simple-greeter/libnotificationarea/libnotificationarea.la	\
+	$(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la	\
 	$(COMMON_LIBS)			\
 	$(EXTRA_GREETER_LIBS)   	\
 	$(SIMPLE_GREETER_LIBS)		\
diff --git a/gui/simple-greeter/gdm-greeter-client.c b/gui/simple-greeter/gdm-greeter-client.c
index ad3ec7e..ef6fb43 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,
@@ -279,6 +280,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)
 {
@@ -782,6 +790,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")) {
@@ -1034,6 +1044,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 917ca9c..3fd62d4 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 1d66c73..c01b9cb 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,12 +53,16 @@
 #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-task-list.h"
 
 #ifdef HAVE_PAM
 #include <security/pam_appl.h>
@@ -103,13 +110,15 @@ struct GdmGreeterLoginWindowPrivate
 {
         GtkBuilder      *builder;
         GtkWidget       *user_chooser;
+        GtkWidget       *conversation_list;
         GtkWidget       *auth_banner_label;
         GtkWidget       *current_button;
-
+        GtkWidget       *auth_page_box;
         guint            display_is_local : 1;
         guint            is_interactive : 1;
         guint            user_chooser_loaded : 1;
         GConfClient     *client;
+        GList           *tasks_to_enable;
 
         gboolean         banner_message_enabled;
         guint            gconf_cnxn;
@@ -137,6 +146,7 @@ enum {
 };
 
 enum {
+        START_CONVERSATION,
         BEGIN_AUTO_LOGIN,
         BEGIN_VERIFICATION,
         BEGIN_VERIFICATION_FOR_USER,
@@ -161,6 +171,8 @@ static void     on_user_unchosen            (GdmUserChooserWidget *user_chooser,
 static void     switch_mode                 (GdmGreeterLoginWindow *login_window,
                                              int                    number);
 static void     update_banner_message       (GdmGreeterLoginWindow *login_window);
+static void     gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window,
+                                                                   const char            *service_name);
 
 G_DEFINE_TYPE (GdmGreeterLoginWindow, gdm_greeter_login_window, GTK_TYPE_WINDOW)
 
@@ -186,9 +198,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);
 
@@ -198,27 +207,43 @@ 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"));
+        GdmTask *task;
 
         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);
+        task = gdm_task_list_get_active_task (GDM_TASK_LIST (login_window->priv->conversation_list));
+
+        if (task != NULL && gdm_conversation_focus (GDM_CONVERSATION (task))) {
+                char *name;
+                name = gdm_task_get_name (task);
+                g_debug ("GdmGreeterLoginWindow: focusing task %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);
         }
+        g_object_unref (task);
+}
+
+static gboolean
+set_task_conversation_message (GdmTaskList *task_list,
+                               GdmTask     *task,
+                               const char  *message)
+{
+
+        gdm_conversation_set_message (GDM_CONVERSATION (task), message);
+        return FALSE;
 }
 
 static void
 set_message (GdmGreeterLoginWindow *login_window,
              const char            *text)
 {
-        GtkWidget *label;
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
 
-        label = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-message-label"));
-        gtk_label_set_text (GTK_LABEL (label), text);
+        gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                    (GdmTaskListForeachFunc)
+                                    set_task_conversation_message,
+                                    (gpointer) text);
 }
 
 static void
@@ -340,20 +365,66 @@ show_widget (GdmGreeterLoginWindow *login_window,
 }
 
 static void
-on_login_button_clicked_answer_query (GtkButton             *button,
-                                      GdmGreeterLoginWindow *login_window)
+hide_task_actions (GdmTask *task)
 {
-        GtkWidget  *entry;
-        const char *text;
+        GtkActionGroup *actions;
 
-        set_busy (login_window);
-        set_sensitive (login_window, FALSE);
+        actions = gdm_conversation_get_actions (GDM_CONVERSATION (task));
 
-        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) {
+                gtk_action_group_set_visible (actions, FALSE);
+                gtk_action_group_set_sensitive (actions, FALSE);
+                g_object_unref (actions);
+        }
+}
+
+static void
+grab_default_button_for_task (GdmTask *task)
+{
+        GtkActionGroup *actions;
+        GtkAction *action;
+        GSList    *proxies, *node;
+
+        actions = gdm_conversation_get_actions (GDM_CONVERSATION (task));
+
+        if (actions == NULL) {
+                return;
+        }
+
+        action = gtk_action_group_get_action (actions, GDM_CONVERSATION_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_CAN_DEFAULT (widget) &&
+                    GTK_WIDGET_VISIBLE (widget)) {
+                        gtk_widget_grab_default (widget);
+                        break;
+                }
+        }
 
-        _gdm_greeter_login_window_set_interactive (login_window, TRUE);
-        g_signal_emit (login_window, signals[QUERY_ANSWER], 0, text);
+}
+
+static void
+show_task_actions (GdmTask *task)
+{
+        GtkActionGroup *actions;
+
+        actions = gdm_conversation_get_actions (GDM_CONVERSATION (task));
+
+        if (actions != NULL) {
+                gtk_action_group_set_sensitive (actions, TRUE);
+                gtk_action_group_set_visible (actions, TRUE);
+                g_object_unref (actions);
+        }
 }
 
 static void
@@ -375,6 +446,7 @@ set_log_in_button_mode (GdmGreeterLoginWindow *login_window,
         GtkWidget *unlock_button;
         char      *item;
         gboolean   in_use;
+        GdmTask   *task;
 
         in_use = FALSE;
         item = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser));
@@ -416,11 +488,23 @@ set_log_in_button_mode (GdmGreeterLoginWindow *login_window,
 
         switch (mode) {
         case LOGIN_BUTTON_HIDDEN:
+                task = gdm_task_list_get_active_task (GDM_TASK_LIST (login_window->priv->conversation_list));
+                if (task != NULL) {
+                        hide_task_actions (task);
+                        g_object_unref (task);
+                }
+
                 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);
+                task = gdm_task_list_get_active_task (GDM_TASK_LIST (login_window->priv->conversation_list));
+                if (task != NULL) {
+                        show_task_actions (task);
+                        grab_default_button_for_task (task);
+                        g_object_unref (task);
+                }
+
+                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);
@@ -484,6 +568,24 @@ maybe_show_cancel_button (GdmGreeterLoginWindow *login_window)
 }
 
 static void
+update_conversation_list_visibility (GdmGreeterLoginWindow *login_window)
+{
+        int number_of_tasks;
+
+        if (login_window->priv->dialog_mode != MODE_AUTHENTICATION) {
+                gtk_widget_hide (login_window->priv->conversation_list);
+                return;
+        }
+
+        number_of_tasks = gdm_task_list_get_number_of_visible_tasks (GDM_TASK_LIST (login_window->priv->conversation_list));
+        if (number_of_tasks > 1) {
+                gtk_widget_show (login_window->priv->conversation_list);
+        } else {
+                gtk_widget_hide (login_window->priv->conversation_list);
+        }
+}
+
+static void
 switch_mode (GdmGreeterLoginWindow *login_window,
              int                    number)
 {
@@ -518,6 +620,7 @@ switch_mode (GdmGreeterLoginWindow *login_window,
         }
 
         show_widget (login_window, "auth-input-box", FALSE);
+        update_conversation_list_visibility (login_window);
         maybe_show_cancel_button (login_window);
 
         /*
@@ -548,58 +651,71 @@ switch_mode (GdmGreeterLoginWindow *login_window,
         }
 }
 
-static void
-choose_user (GdmGreeterLoginWindow *login_window,
-             const char            *user_name)
+static gboolean
+task_has_service_name (GdmTaskList *task_list,
+                       GdmTask     *task,
+                       const char  *service_name)
 {
-        guint mode;
+        char *task_service_name;
+        gboolean has_service_name;
 
-        g_assert (user_name != NULL);
+        task_service_name = gdm_conversation_get_service_name (GDM_CONVERSATION (task));
 
-        g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
-                       0, user_name);
+        has_service_name = strcmp (service_name, task_service_name) == 0;
+        g_free (task_service_name);
 
-        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);
+        return has_service_name;
+}
 
-                login_window->priv->timed_login_enabled = TRUE;
-                restart_timed_login_timeout (login_window);
+static GdmTask *
+find_task_with_service_name (GdmGreeterLoginWindow *login_window,
+                             const char            *service_name)
+{
+        GdmTask *task;
 
-                /* 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);
-        }
+        task = gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                           (GdmTaskListForeachFunc)
+                                           task_has_service_name,
+                                           (gpointer) service_name);
 
-        switch_mode (login_window, mode);
+        return task;
 }
 
-static void
-retry_login (GdmGreeterLoginWindow *login_window)
+static gboolean
+reset_task (GdmTaskList           *task_list,
+            GdmTask               *task,
+            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_task_get_name (task);
+        g_debug ("Resetting task '%s'", name);
+        g_free (name);
+
+        login_window->priv->tasks_to_enable = g_list_remove (login_window->priv->tasks_to_enable, task);
+
+        gdm_conversation_reset (GDM_CONVERSATION (task));
+        return FALSE;
+}
 
-        g_debug ("GdmGreeterLoginWindow: Retrying login for %s", user_name);
+static gboolean
+task_is_disabled (GdmTaskList *task_list,
+                  GdmTask     *task)
+{
+        return !gdm_task_is_enabled (task);
+}
 
-        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);
+static gboolean
+tasks_are_enabled (GdmGreeterLoginWindow *login_window)
+{
+        GdmTask *task;
 
-        choose_user (login_window, user_name);
+        task = gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                           (GdmTaskListForeachFunc)
+                                           task_is_disabled,
+                                           NULL);
 
-        g_free (user_name);
+        return task == NULL;
 }
 
 static gboolean
@@ -609,6 +725,10 @@ can_jump_to_authenticate (GdmGreeterLoginWindow *login_window)
 
         if (!login_window->priv->user_chooser_loaded) {
                 res = FALSE;
+        } else if (!tasks_are_enabled (login_window)) {
+                res = FALSE;
+        } else if (login_window->priv->dialog_mode == MODE_AUTHENTICATION) {
+                res = FALSE;
         } else if (login_window->priv->user_list_disabled) {
                 res = (login_window->priv->timed_login_username == NULL);
         } else {
@@ -618,11 +738,44 @@ can_jump_to_authenticate (GdmGreeterLoginWindow *login_window)
         return res;
 }
 
+static gboolean
+begin_task_verification (GdmTaskList           *task_list,
+                         GdmTask               *task,
+                         GdmGreeterLoginWindow *login_window)
+{
+        char *service_name;
+
+        if (!gdm_task_is_visible (task)) {
+                return FALSE;
+        }
+
+        service_name = gdm_conversation_get_service_name (GDM_CONVERSATION (task));
+        if (service_name != NULL) {
+                g_signal_emit (login_window, signals[BEGIN_VERIFICATION], 0, service_name);
+                g_free (service_name);
+        }
+
+        return FALSE;
+}
+
+static void
+begin_verification (GdmGreeterLoginWindow *login_window)
+{
+        gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                    (GdmTaskListForeachFunc)
+                                    begin_task_verification,
+                                    login_window);
+
+        switch_mode (login_window, MODE_AUTHENTICATION);
+
+        update_conversation_list_visibility (login_window);
+}
+
+
 static void
 reset_dialog (GdmGreeterLoginWindow *login_window,
               guint                  dialog_mode)
 {
-        GtkWidget  *entry;
         GtkWidget  *label;
         guint       mode;
 
@@ -655,11 +808,10 @@ 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);
+        gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                    (GdmTaskListForeachFunc)
+                                    reset_task,
+                                    login_window);
 
         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), "");
@@ -667,12 +819,9 @@ reset_dialog (GdmGreeterLoginWindow *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_verification (login_window);
         } else {
                 switch_mode (login_window, dialog_mode);
         }
@@ -688,89 +837,178 @@ 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 void
+do_cancel (GdmGreeterLoginWindow *login_window)
+{
+        /* need to wait for response from backend */
+        set_message (login_window, _("Cancelling..."));
+        restart_conversations (login_window);
+}
+
 gboolean
-gdm_greeter_login_window_ready (GdmGreeterLoginWindow *login_window)
+gdm_greeter_login_window_ready (GdmGreeterLoginWindow *login_window,
+                                const char            *service_name)
 {
+        GdmTask *task;
+
         g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
 
+        task = find_task_with_service_name (login_window, service_name);
+
+        if (task != NULL) {
+                if (gdm_chooser_widget_is_loaded (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser))) {
+                        gdm_conversation_set_ready (GDM_CONVERSATION (task));
+                } else {
+                        login_window->priv->tasks_to_enable = g_list_prepend (login_window->priv->tasks_to_enable, task);
+                }
+                g_object_unref (task);
+        }
+
         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));
         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_verification (login_window);
         }
 
         return TRUE;
 }
 
 gboolean
-gdm_greeter_login_window_authentication_failed (GdmGreeterLoginWindow *login_window)
+gdm_greeter_login_window_conversation_stopped (GdmGreeterLoginWindow *login_window,
+                                               const char            *service_name)
 {
+        GdmTask *task;
+
         g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
 
-        g_debug ("GdmGreeterLoginWindow: got authentication failed");
+        g_debug ("GdmGreeterLoginWindow: conversation '%s' has stopped", service_name);
+
+        /* 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);
+                return TRUE;
+        }
+
+        task = find_task_with_service_name (login_window, service_name);
+
+        if (task != NULL) {
+                gdm_conversation_reset (GDM_CONVERSATION (task));
+                g_object_unref (task);
+        }
+
+        /* If every conversation has failed, then just start over.
+         */
+        task = gdm_task_list_get_active_task (GDM_TASK_LIST (login_window->priv->conversation_list));
 
-        /* FIXME: shake? */
-        reset_dialog (login_window, MODE_AUTHENTICATION);
+        if (!gdm_task_is_enabled (task)) {
+                g_debug ("GdmGreeterLoginWindow: No conversations left, starting over");
+                restart_conversations (login_window);
+        }
+        g_object_unref (task);
+
+        update_conversation_list_visibility (login_window);
 
         return TRUE;
 }
 
+static gboolean
+restart_task_conversation (GdmTaskList           *task_list,
+                           GdmTask               *task,
+                           GdmGreeterLoginWindow *login_window)
+{
+        char *service_name;
+
+        service_name = gdm_conversation_get_service_name (GDM_CONVERSATION (task));
+        if (service_name != NULL) {
+                char *name;
+
+                name = gdm_task_get_name (task);
+                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);
+        reset_dialog (GDM_GREETER_LOGIN_WINDOW (login_window), MODE_SELECTION);
 
-        g_debug ("GdmGreeterLoginWindow: got reset");
-        reset_dialog (login_window, MODE_SELECTION);
+        gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                    (GdmTaskListForeachFunc)
+                                    restart_task_conversation,
+                                    login_window);
 
         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);
+        GdmTask *task;
 
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
         g_debug ("GdmGreeterLoginWindow: info: %s", text);
 
-        set_message (GDM_GREETER_LOGIN_WINDOW (login_window), text);
+        task = find_task_with_service_name (login_window, service_name);
+
+        if (task != NULL) {
+                gdm_conversation_set_message (GDM_CONVERSATION (task),
+                                              text);
+                show_task_actions (task);
+                g_object_unref (task);
+        }
 
         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);
+        GdmTask *task;
 
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
         g_debug ("GdmGreeterLoginWindow: problem: %s", text);
 
-        set_message (GDM_GREETER_LOGIN_WINDOW (login_window), text);
-        gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (login_window)));
+        task = find_task_with_service_name (login_window, service_name);
+
+        if (task != NULL) {
+                gdm_conversation_set_message (GDM_CONVERSATION (task),
+                                              text);
+                show_task_actions (task);
+                g_object_unref (task);
+        }
+
+        gdk_window_beep (GTK_WIDGET (login_window)->window);
 
         return TRUE;
 }
@@ -794,6 +1032,26 @@ 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)
+{
+        GdmTask *task;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+        g_debug ("GdmGreeterLoginWindow: service unavailable: %s", service_name);
+
+        task = find_task_with_service_name (login_window, service_name);
+
+        if (task != NULL) {
+                gdm_task_list_remove_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                           task);
+                g_object_unref (task);
+        }
+
+        return TRUE;
+}
+
 void
 gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_window,
                                               const char            *username,
@@ -821,11 +1079,21 @@ gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_windo
 }
 
 static void
-gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window)
+on_ready_to_start_session (GdmGreeterLoginWindow *login_window,
+                           GParamSpec            *param_spec,
+                           char                  *service_name)
+{
+        gdm_greeter_login_window_start_session_when_ready (login_window, service_name);
+        g_free (service_name);
+}
+
+static void
+gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window,
+                                                   const char            *service_name)
 {
         if (login_window->priv->is_interactive) {
                 g_debug ("GdmGreeterLoginWindow: starting session");
-                g_signal_emit (login_window, signals[START_SESSION], 0);
+                g_signal_emit (login_window, signals[START_SESSION], 0, service_name);
         } else {
                 g_debug ("GdmGreeterLoginWindow: not starting session since "
                          "user hasn't had an opportunity to pick language "
@@ -835,8 +1103,8 @@ gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_
                  */
                 login_window->priv->start_session_handler_id =
                     g_signal_connect (login_window, "notify::is-interactive",
-                                      G_CALLBACK (gdm_greeter_login_window_start_session_when_ready),
-                                      NULL);
+                                      G_CALLBACK (on_ready_to_start_session),
+                                      g_strdup (service_name));
 
                 /* FIXME: If the user wasn't asked any questions by pam but
                  * pam still authorized them (passwd -d, or the questions got
@@ -859,10 +1127,10 @@ gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_
 
 gboolean
 gdm_greeter_login_window_info_query (GdmGreeterLoginWindow *login_window,
+                                     const char            *service_name,
                                      const char            *text)
 {
-        GtkWidget  *entry;
-        GtkWidget  *label;
+        GdmTask *task;
 
         g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
 
@@ -871,15 +1139,15 @@ 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);
+        task = find_task_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 (task != NULL) {
+                gdm_conversation_ask_question (GDM_CONVERSATION (task),
+                                               text);
+                g_object_unref (task);
+        }
 
-        show_widget (login_window, "auth-input-box", TRUE);
+        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));
@@ -891,25 +1159,26 @@ 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;
+
+        GdmTask *task;
 
         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);
+        task = find_task_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 (task != NULL) {
+                gdm_conversation_ask_secret (GDM_CONVERSATION (task),
+                                             text);
+                g_object_unref (task);
+        }
 
-        show_widget (login_window, "auth-input-box", TRUE);
+        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));
@@ -920,13 +1189,16 @@ gdm_greeter_login_window_secret_info_query (GdmGreeterLoginWindow *login_window,
 }
 
 void
-gdm_greeter_login_window_user_authorized (GdmGreeterLoginWindow *login_window)
+gdm_greeter_login_window_user_authorized (GdmGreeterLoginWindow *login_window,
+                                          const char            *service_name)
 {
         g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
 
-        g_debug ("GdmGreeterLoginWindow: user now authorized");
+        g_debug ("GdmGreeterLoginWindow: user now authorized 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
@@ -997,6 +1269,49 @@ on_user_chooser_visibility_changed (GdmGreeterLoginWindow *login_window)
         update_banner_message (login_window);
 }
 
+static gboolean
+begin_task_verification_for_selected_user (GdmTaskList           *task_list,
+                                           GdmTask               *task,
+                                           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_conversation_get_service_name (GDM_CONVERSATION (task));
+        if (service_name != NULL) {
+                g_signal_emit (login_window, signals[BEGIN_VERIFICATION_FOR_USER], 0, service_name, user_name);
+                g_free (service_name);
+        }
+
+        g_free (user_name);
+        return FALSE;
+}
+
+static void
+enable_waiting_tasks (GdmGreeterLoginWindow *login_window)
+{
+        GList *node;
+
+        node = login_window->priv->tasks_to_enable;
+        while (node != NULL) {
+                GdmTask *task;
+
+                task = GDM_TASK (node->data);
+
+                gdm_conversation_set_ready (GDM_CONVERSATION (task));
+
+                node = node->next;
+        }
+
+        login_window->priv->tasks_to_enable = NULL;
+}
+
 static void
 on_users_loaded (GdmUserChooserWidget  *user_chooser,
                  GdmGreeterLoginWindow *login_window)
@@ -1010,38 +1325,153 @@ on_users_loaded (GdmUserChooserWidget  *user_chooser,
                 gtk_widget_show (login_window->priv->user_chooser);
         }
 
+        enable_waiting_tasks (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_verification (login_window);
         }
 }
 
 static void
-on_user_chosen (GdmUserChooserWidget  *user_chooser,
-                GdmGreeterLoginWindow *login_window)
+choose_user (GdmGreeterLoginWindow *login_window,
+             const char            *user_name)
+{
+        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);
+
+
+        gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                    (GdmTaskListForeachFunc)
+                                    begin_task_verification_for_selected_user,
+                                    login_window);
+
+        switch_mode (login_window, MODE_AUTHENTICATION);
+        update_conversation_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"));
+
+        switch_mode (login_window, MODE_AUTHENTICATION);
+
+        show_widget (login_window, "conversation-list", FALSE);
+        gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                    (GdmTaskListForeachFunc) reset_task,
+                                    login_window);
+}
+
+static gboolean
+reset_task_if_not_given (GdmTaskList *task_list,
+                         GdmTask     *task,
+                         GdmTask     *given_task)
+{
+        if (task == given_task) {
+                return FALSE;
+        }
+
+        gdm_conversation_reset (GDM_CONVERSATION (task));
+        return FALSE;
+}
+
+static void
+reset_every_task_but_given_task (GdmGreeterLoginWindow *login_window,
+                                 GdmTask               *task)
+{
+        gdm_task_list_foreach_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                    (GdmTaskListForeachFunc)
+                                    reset_task_if_not_given,
+                                    task);
+}
+
+static void
+begin_single_service_verification (GdmGreeterLoginWindow *login_window,
+                                   const char            *service_name)
+{
+        GdmTask *task;
+
+        task = find_task_with_service_name (login_window, service_name);
+
+        if (task == NULL) {
+                g_debug ("GdmGreeterLoginWindow: %s has no task 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);
+
+        switch_mode (login_window, MODE_AUTHENTICATION);
+        gdm_task_list_set_active_task (GDM_TASK_LIST (login_window->priv->conversation_list), task);
+
+        reset_every_task_but_given_task (login_window, task);
+        g_object_unref (task);
+
+        show_widget (login_window, "conversation-list", FALSE);
+}
+
+static void
+on_user_chooser_activated (GdmUserChooserWidget  *user_chooser,
+                           GdmGreeterLoginWindow *login_window)
 {
         char *user_name;
-        guint mode;
+        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_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
@@ -1222,11 +1652,74 @@ create_computer_info (GdmGreeterLoginWindow *login_window)
 #define INVISIBLE_CHAR_BULLET        0x2022
 #define INVISIBLE_CHAR_NONE          0
 
+static void
+on_task_activated (GdmGreeterLoginWindow *login_window,
+                   GdmTask               *task)
+{
+        GtkWidget *container;
+        char *name;
+
+        name = gdm_task_get_name (task);
+        g_debug ("GdmGreeterLoginWindow: task '%s' activated", name);
+        g_free (name);
+
+        container = g_object_get_data (G_OBJECT (task),
+                                       "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_conversation_get_page (GDM_CONVERSATION (task));
+                if (page != NULL) {
+                        gtk_container_add (GTK_CONTAINER (container), page);
+                        gtk_widget_show (page);
+                }
+                g_object_set_data (G_OBJECT (task),
+                                   "gdm-greeter-login-window-page-container",
+                                   container);
+        }
+
+        gtk_widget_show (container);
+        switch_mode (login_window, login_window->priv->dialog_mode);
+}
+
+static void
+on_task_deactivated (GdmGreeterLoginWindow *login_window,
+                     GdmTask               *task)
+{
+        GtkWidget *container;
+        char *name;
+        GtkActionGroup *actions;
+
+        name = gdm_task_get_name (task);
+        g_debug ("GdmGreeterLoginWindow: task '%s' now in background", name);
+        g_free (name);
+
+        container = g_object_get_data (G_OBJECT (task),
+                                       "gdm-greeter-login-window-page-container");
+
+        if (container != NULL) {
+                gtk_widget_hide (container);
+        }
+
+        actions = gdm_conversation_get_actions (GDM_CONVERSATION (task));
+
+        if (actions != NULL) {
+                gtk_action_group_set_sensitive (actions, FALSE);
+                gtk_action_group_set_visible (actions, FALSE);
+                g_object_unref (actions);
+        }
+}
 
 static void
 register_custom_types (GdmGreeterLoginWindow *login_window)
 {
-        GType types[] = { GDM_TYPE_USER_CHOOSER_WIDGET };
+        GType types[] = { GDM_TYPE_USER_CHOOSER_WIDGET,
+                          GDM_TYPE_TASK_LIST };
         int i;
 
         for (i = 0; i < G_N_ELEMENTS (types); i++) {
@@ -1237,7 +1730,6 @@ register_custom_types (GdmGreeterLoginWindow *login_window)
 static void
 load_theme (GdmGreeterLoginWindow *login_window)
 {
-        GtkWidget *entry;
         GtkWidget *button;
         GtkWidget *box;
         GtkWidget *image;
@@ -1290,7 +1782,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",
@@ -1302,30 +1794,30 @@ load_theme (GdmGreeterLoginWindow *login_window)
                                  G_CALLBACK (on_user_chooser_visibility_changed),
                                  login_window);
 
+        login_window->priv->conversation_list = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "task-list"));
+
+        g_signal_connect_swapped (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                  "activated",
+                                  G_CALLBACK (on_task_activated),
+                                  login_window);
+        g_signal_connect_swapped (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                  "deactivated",
+                                  G_CALLBACK (on_task_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);
-        }
+        switch_mode (login_window, MODE_SELECTION);
 
         gdm_profile_end (NULL);
 }
@@ -1481,6 +1973,15 @@ gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass)
         widget_class->key_press_event = gdm_greeter_login_window_key_press_event;
         widget_class->size_request = gdm_greeter_login_window_size_request;
 
+        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),
@@ -1497,9 +1998,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),
@@ -1507,9 +2008,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),
@@ -1517,9 +2018,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),
@@ -1557,9 +2058,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,
@@ -1612,6 +2113,246 @@ on_gconf_key_changed (GConfClient           *client,
         }
 }
 
+static void
+on_conversation_answer (GdmGreeterLoginWindow *login_window,
+                        const char            *text,
+                        GdmConversation       *conversation)
+{
+        if (text != NULL) {
+                char *service_name;
+
+                service_name = gdm_conversation_get_service_name (conversation);
+                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_conversation_cancel (GdmGreeterLoginWindow *login_window,
+                        GdmConversation       *conversation)
+{
+        do_cancel (login_window);
+}
+
+static gboolean
+on_conversation_chose_user (GdmGreeterLoginWindow *login_window,
+                            const char            *username,
+                            GdmConversation       *conversation)
+{
+        if (!gdm_chooser_widget_is_loaded (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser))) {
+                char *name;
+
+                name = gdm_task_get_name (GDM_TASK (conversation));
+                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) {
+                return FALSE;
+        }
+
+        if (gdm_task_list_set_active_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                           GDM_TASK (conversation))) {
+                gdm_user_chooser_widget_set_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser),
+                                                              username);
+        }
+
+        return TRUE;
+}
+
+void
+gdm_greeter_login_window_remove_extension (GdmGreeterLoginWindow *login_window,
+ GdmGreeterExtension *extension)
+{
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW_EXTENSION (extension));
+
+        if (!GDM_IS_CONVERSATION (extension)) {
+                return;
+        }
+}
+
+static void
+on_button_action_label_changed (GtkWidget *button)
+{
+        GtkAction *action;
+        char *text;
+
+        action = gtk_widget_get_action (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_widget_get_action (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_widget_get_action (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_action_connect_proxy (GTK_ACTION (action), button);
+
+        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_CONVERSATION_DEFAULT_ACTION) == 0) {
+                GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+        }
+
+        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,
+                                        GdmGreeterExtension *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_GREETER_LOGIN_WINDOW_EXTENSION (extension));
+
+        if (!GDM_IS_CONVERSATION (extension)) {
+                return;
+        }
+
+        name = gdm_task_get_name (GDM_TASK (extension));
+        description = gdm_task_get_description (GDM_TASK (extension));
+
+        if (!gdm_task_is_visible (GDM_TASK (extension))) {
+                g_debug ("GdmGreeterLoginWindow: new extension '%s - %s' won't be added",
+                         name, description);
+                g_free (name);
+                g_free (description);
+                return;
+        }
+
+        actions = gdm_conversation_get_actions (GDM_CONVERSATION (extension));
+
+        create_buttons_for_actions (login_window, actions);
+        hide_task_actions (GDM_TASK (extension));
+
+        g_object_unref (actions);
+
+        g_signal_connect_swapped (GDM_CONVERSATION (extension),
+                                  "answer",
+                                  G_CALLBACK (on_conversation_answer),
+                                  login_window);
+        g_signal_connect_swapped (GDM_CONVERSATION (extension),
+                                  "cancel",
+                                  G_CALLBACK (on_conversation_cancel),
+                                  login_window);
+        g_signal_connect_swapped (GDM_CONVERSATION (extension),
+                                  "user-chosen",
+                                  G_CALLBACK (on_conversation_chose_user),
+                                  login_window);
+
+        g_debug ("GdmGreeterLoginWindow: new extension '%s - %s' added",
+                name, description);
+
+        gdm_task_list_add_task (GDM_TASK_LIST (login_window->priv->conversation_list),
+                                GDM_TASK (extension));
+
+        service_name = gdm_conversation_get_service_name (GDM_CONVERSATION (extension));
+
+        if (gdm_task_is_choosable (GDM_TASK (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,
diff --git a/gui/simple-greeter/gdm-greeter-login-window.h b/gui/simple-greeter/gdm-greeter-login-window.h
index 504b075..041cbc4 100644
--- a/gui/simple-greeter/gdm-greeter-login-window.h
+++ b/gui/simple-greeter/gdm-greeter-login-window.h
@@ -23,6 +23,9 @@
 #define __GDM_GREETER_LOGIN_WINDOW_H
 
 #include <glib-object.h>
+#include "gdm-conversation.h"
+#include "gdm-task.h"
+#include "gdm-greeter-extension.h"
 
 G_BEGIN_DECLS
 
@@ -33,6 +36,8 @@ G_BEGIN_DECLS
 #define GDM_IS_GREETER_LOGIN_WINDOW_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_GREETER_LOGIN_WINDOW))
 #define GDM_GREETER_LOGIN_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_GREETER_LOGIN_WINDOW, GdmGreeterLoginWindowClass))
 
+#define GDM_IS_GREETER_LOGIN_WINDOW_EXTENSION(e) (GDM_IS_CONVERSATION(e) && GDM_IS_TASK(e))
+
 typedef struct GdmGreeterLoginWindowPrivate GdmGreeterLoginWindowPrivate;
 
 typedef struct
@@ -46,18 +51,24 @@ 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);
         void (* cancelled)                   (GdmGreeterLoginWindow *login_window);
         void (* disconnected)                (GdmGreeterLoginWindow *login_window);
-        void (* start_session)               (GdmGreeterLoginWindow *login_window);
+        void (* start_session)               (GdmGreeterLoginWindow *login_window,
+                                              const char            *sevice_name);
 
 } GdmGreeterLoginWindowClass;
 
@@ -66,21 +77,36 @@ 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);
 
+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_user_authorized     (GdmGreeterLoginWindow *login_window);
+void               gdm_greeter_login_window_user_authorized     (GdmGreeterLoginWindow *login_window,
+                                                                 const char            *service_name);
+
+void               gdm_greeter_login_window_add_extension (GdmGreeterLoginWindow *login_window,
+                    GdmGreeterExtension *extension);
+void               gdm_greeter_login_window_remove_extension (GdmGreeterLoginWindow *login_window,
+ GdmGreeterExtension *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 83375ed..35195d8 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="GdmTaskList" id="task-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-plugin.c b/gui/simple-greeter/gdm-greeter-plugin.c
new file mode 100644
index 0000000..1919aae
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-plugin.c
@@ -0,0 +1,254 @@
+/*
+ * 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 <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "gdm-greeter-extension.h"
+#include "gdm-greeter-plugin.h"
+
+#define GDM_GREETER_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_GREETER_PLUGIN, GdmGreeterPluginPrivate))
+
+enum {
+        PROP_0,
+        PROP_FILENAME,
+};
+
+enum {
+        LOADED,
+        LOAD_FAILED,
+        UNLOADED,
+        LAST_SIGNAL
+};
+
+struct _GdmGreeterPluginPrivate {
+        GObject              parent;
+
+        GModule             *module;
+        char                *filename;
+
+        GdmGreeterExtension *extension;
+};
+
+static void gdm_greeter_plugin_finalize     (GObject      *object);
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GdmGreeterPlugin, gdm_greeter_plugin, G_TYPE_OBJECT)
+
+static void
+gdm_greeter_plugin_set_property (GObject      *object,
+                                 guint         param_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+        GdmGreeterPlugin *plugin;
+
+        plugin = GDM_GREETER_PLUGIN (object);
+        switch (param_id) {
+        case PROP_FILENAME:
+                plugin->priv->filename = g_strdup (g_value_get_string (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_greeter_plugin_get_property (GObject    *object,
+                                 guint       param_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+        GdmGreeterPlugin *plugin;
+
+        plugin = GDM_GREETER_PLUGIN (object);
+
+        switch (param_id) {
+        case PROP_FILENAME:
+                g_value_set_string (value, plugin->priv->filename);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_greeter_plugin_class_init (GdmGreeterPluginClass *class)
+{
+        GObjectClass *gobject_class;
+
+        gobject_class = G_OBJECT_CLASS (class);
+
+        gobject_class->set_property = gdm_greeter_plugin_set_property;
+        gobject_class->get_property = gdm_greeter_plugin_get_property;
+        gobject_class->finalize = gdm_greeter_plugin_finalize;
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_FILENAME,
+                                         g_param_spec_string ("filename",
+                                                              "Filename",
+                                                              "The full path to the plugin.",
+                                                              NULL,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+        signals [LOADED] =
+                g_signal_new ("loaded",
+                              G_TYPE_FROM_CLASS (class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterPluginClass, loaded),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [LOAD_FAILED] =
+                g_signal_new ("load-failed",
+                              G_TYPE_FROM_CLASS (class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterPluginClass, load_failed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [UNLOADED] =
+                g_signal_new ("unloaded",
+                              G_TYPE_FROM_CLASS (class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterPluginClass, unloaded),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+
+        g_type_class_add_private (class, sizeof (GdmGreeterPluginPrivate));
+}
+
+GdmGreeterPlugin *
+gdm_greeter_plugin_new (const char *filename)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_GREETER_PLUGIN,
+                               "filename", filename, NULL);
+
+        return GDM_GREETER_PLUGIN (object);
+}
+
+void
+gdm_greeter_plugin_load (GdmGreeterPlugin *plugin)
+{
+        GModule *module;
+        GdmGreeterExtension *extension;
+        union {
+                gpointer symbol;
+                GdmGreeterPluginGetExtensionFunc invoke;
+        } get_extension;
+
+
+        module = g_module_open (plugin->priv->filename, G_MODULE_BIND_LOCAL);
+
+        if (module == NULL) {
+                g_warning ("plugin %s couldn't be opened: %s",
+                           plugin->priv->filename,
+                           g_module_error ());
+                g_signal_emit (plugin, signals [LOAD_FAILED], 0);
+                return;
+        }
+
+        if (!g_module_symbol (module,
+                              "gdm_greeter_plugin_get_extension",
+                              &get_extension.symbol) ||
+            !get_extension.symbol) {
+                g_warning ("plugin %s lacks gdm_greeter_plugin_get_extension()",
+                           plugin->priv->filename);
+                g_module_close (module);
+                g_signal_emit (plugin, signals [LOAD_FAILED], 0);
+                return;
+        }
+
+        extension = get_extension.invoke ();
+
+        if (!extension) {
+                g_warning ("plugin %s didn't return extension when asked",
+                           plugin->priv->filename);
+                g_module_close (module);
+                g_signal_emit (plugin, signals [LOAD_FAILED], 0);
+        }
+
+        if (!GDM_IS_GREETER_EXTENSION (extension)) {
+                g_warning ("plugin %s returned bogus extension when asked",
+                           plugin->priv->filename);
+                g_module_close (module);
+                g_signal_emit (plugin, signals [LOAD_FAILED], 0);
+        }
+
+        plugin->priv->module = module;
+        plugin->priv->extension = extension;
+
+        g_signal_emit (plugin, signals [LOADED], 0);
+}
+
+void
+gdm_greeter_plugin_unload (GdmGreeterPlugin *plugin)
+{
+        if (plugin->priv->extension != NULL) {
+                g_object_unref (plugin->priv->extension);
+                plugin->priv->extension = NULL;
+        }
+
+        if (plugin->priv->module != NULL) {
+                g_module_close (plugin->priv->module);
+                plugin->priv->module = NULL;
+        }
+}
+
+const char *
+gdm_greeter_plugin_get_filename (GdmGreeterPlugin *plugin)
+{
+        return plugin->priv->filename;
+}
+
+GdmGreeterExtension *
+gdm_greeter_plugin_get_extension (GdmGreeterPlugin *plugin)
+{
+        return g_object_ref (plugin->priv->extension);
+}
+
+static void
+gdm_greeter_plugin_init (GdmGreeterPlugin *plugin)
+{
+        plugin->priv = GDM_GREETER_PLUGIN_GET_PRIVATE (plugin);
+}
+
+static void
+gdm_greeter_plugin_finalize (GObject *object)
+{
+        GdmGreeterPlugin *plugin;
+
+        plugin = GDM_GREETER_PLUGIN (object);
+
+        gdm_greeter_plugin_unload (plugin);
+}
diff --git a/gui/simple-greeter/gdm-greeter-plugin.h b/gui/simple-greeter/gdm-greeter-plugin.h
new file mode 100644
index 0000000..904c231
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-plugin.h
@@ -0,0 +1,61 @@
+/*
+ * 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
+ */
+
+#ifndef __GDM_GREETER_PLUGIN
+#define __GDM_GREETER_PLUGIN
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gdm-greeter-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_GREETER_PLUGIN (gdm_greeter_plugin_get_type ())
+#define GDM_GREETER_PLUGIN(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDM_TYPE_GREETER_PLUGIN, GdmGreeterPlugin))
+#define GDM_IS_GREETER_PLUGIN(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDM_TYPE_GREETER_PLUGIN))
+
+typedef struct _GdmGreeterPlugin GdmGreeterPlugin;
+typedef struct _GdmGreeterPluginPrivate GdmGreeterPluginPrivate;
+typedef struct _GdmGreeterPluginClass GdmGreeterPluginClass;
+
+struct _GdmGreeterPlugin
+{
+        GObject                parent;
+        GdmGreeterPluginPrivate *priv;
+};
+
+struct _GdmGreeterPluginClass
+{
+        GObjectClass   parent_class;
+
+        void          (* loaded)         (GdmGreeterPlugin *plugin);
+        void          (* load_failed)    (GdmGreeterPlugin *plugin);
+        void          (* unloaded)       (GdmGreeterPlugin *plugin);
+};
+
+GType             gdm_greeter_plugin_get_type (void) G_GNUC_CONST;
+GdmGreeterPlugin *gdm_greeter_plugin_new (const char *filename);
+void              gdm_greeter_plugin_load (GdmGreeterPlugin *plugin);
+void              gdm_greeter_plugin_unload (GdmGreeterPlugin *plugin);
+const char       *gdm_greeter_plugin_get_filename (GdmGreeterPlugin *plugin);
+GdmGreeterExtension *gdm_greeter_plugin_get_extension (GdmGreeterPlugin *plugin);
+
+G_END_DECLS
+
+#endif
diff --git a/gui/simple-greeter/gdm-greeter-session.c b/gui/simple-greeter/gdm-greeter-session.c
index b7e7f1c..051a5cc 100644
--- a/gui/simple-greeter/gdm-greeter-session.c
+++ b/gui/simple-greeter/gdm-greeter-session.c
@@ -39,6 +39,8 @@
 #include "gdm-greeter-login-window.h"
 #include "gdm-user-chooser-widget.h"
 
+#include "gdm-plugin-manager.h"
+
 #include "gdm-profile.h"
 
 #define GDM_GREETER_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_GREETER_SESSION, GdmGreeterSessionPrivate))
@@ -48,6 +50,7 @@
 struct GdmGreeterSessionPrivate
 {
         GdmGreeterClient      *client;
+        GdmPluginManager      *plugin_manager;
 
         GtkWidget             *login_window;
         GtkWidget             *panel;
@@ -75,7 +78,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 +89,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,7 +109,19 @@ 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_conversation_stopped (GdmGreeterClient  *client,
+                         const char        *service_name,
+                         GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Conversation '%s' stopped", service_name);
+
+        gdm_greeter_login_window_conversation_stopped (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window),
+                                                       service_name);
 }
 
 static void
@@ -112,29 +137,6 @@ on_reset (GdmGreeterClient  *client,
 }
 
 static void
-on_authentication_failed (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);
-
-                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;
-
-                gdm_greeter_panel_reset (GDM_GREETER_PANEL (session->priv->panel));
-                gdm_greeter_login_window_reset (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
-        }
-}
-
-static void
 show_or_hide_user_options (GdmGreeterSession *session,
                            const char        *username)
 {
@@ -196,10 +198,11 @@ on_timed_login_requested (GdmGreeterClient  *client,
 
 static void
 on_user_authorized (GdmGreeterClient  *client,
+                    const char        *service_name,
                     GdmGreeterSession *session)
 {
         g_debug ("GdmGreeterSession: user authorized");
-        gdm_greeter_login_window_user_authorized (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
+        gdm_greeter_login_window_user_authorized (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name);
 }
 
 static void
@@ -210,7 +213,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
@@ -221,10 +224,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)
@@ -235,29 +246,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);
 }
 
@@ -301,7 +315,6 @@ on_cancelled (GdmGreeterLoginWindow *login_window,
 {
         gdm_greeter_panel_hide_user_options (GDM_GREETER_PANEL (session->priv->panel));
         gdm_greeter_client_call_cancel (session->priv->client);
-        gdm_greeter_client_call_start_conversation (session->priv->client, "gdm");
 }
 
 static void
@@ -313,9 +326,10 @@ on_disconnected (GdmGreeterLoginWindow *login_window,
 
 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
@@ -410,7 +424,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),
@@ -466,8 +483,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;
@@ -577,6 +592,64 @@ gdm_greeter_session_event_handler (GdkEvent          *event,
 }
 
 static void
+on_plugins_loaded (GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: done loading plugins");
+}
+
+static void
+on_plugin_removed (GdmGreeterSession *session,
+                   GdmGreeterPlugin  *plugin)
+{
+        GdmGreeterExtension *extension;
+
+        extension = gdm_greeter_plugin_get_extension (plugin);
+
+        if (GDM_IS_GREETER_LOGIN_WINDOW_EXTENSION (extension)) {
+                gdm_greeter_login_window_remove_extension (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), extension);
+        }
+        g_object_unref (extension);
+}
+
+static void
+on_plugin_added (GdmGreeterSession *session,
+                 GdmGreeterPlugin  *plugin)
+{
+        GdmGreeterExtension *extension;
+
+        extension = gdm_greeter_plugin_get_extension (plugin);
+
+        if (GDM_IS_GREETER_LOGIN_WINDOW_EXTENSION (extension)) {
+                gdm_greeter_login_window_add_extension (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), extension);
+        }
+        g_object_unref (extension);
+}
+
+static void
+load_plugins (GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: loading plugins");
+
+        session->priv->plugin_manager = gdm_plugin_manager_ref_default ();
+
+        g_signal_connect_swapped (session->priv->plugin_manager,
+                                  "plugins-loaded",
+                                  G_CALLBACK (on_plugins_loaded),
+                                  session);
+
+        g_signal_connect_swapped (session->priv->plugin_manager,
+                                  "plugin-added",
+                                  G_CALLBACK (on_plugin_added),
+                                  session);
+
+        g_signal_connect_swapped (session->priv->plugin_manager,
+                                  "plugin-removed",
+                                  G_CALLBACK (on_plugin_removed),
+                                  session);
+
+}
+
+static void
 gdm_greeter_session_init (GdmGreeterSession *session)
 {
         gdm_profile_start (NULL);
@@ -601,16 +674,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",
@@ -643,6 +720,8 @@ gdm_greeter_session_init (GdmGreeterSession *session)
         gdk_event_handler_set ((GdkEventFunc) gdm_greeter_session_event_handler,
                                session, NULL);
 
+
+        load_plugins (session);
         gdm_profile_end (NULL);
 }
 
diff --git a/gui/simple-greeter/gdm-plugin-manager.c b/gui/simple-greeter/gdm-plugin-manager.c
new file mode 100644
index 0000000..49e442c
--- /dev/null
+++ b/gui/simple-greeter/gdm-plugin-manager.c
@@ -0,0 +1,478 @@
+/*
+ * 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 <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "gdm-plugin-manager.h"
+#include "gdm-greeter-extension.h"
+
+#define GDM_PLUGIN_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_PLUGIN_MANAGER, GdmPluginManagerPrivate))
+
+typedef struct
+{
+        GModule             *module;
+        char                *filename;
+        GdmGreeterExtension *extension;
+} GdmPluginManagerPlugin;
+
+typedef struct
+{
+        GdmPluginManager *manager;
+        GCancellable     *cancellable;
+} GdmPluginManagerOperation;
+
+struct GdmPluginManagerPrivate
+{
+        GHashTable      *plugins;
+
+        GFileMonitor    *plugin_dir_monitor;
+        GList           *pending_operations;
+};
+
+enum {
+        PLUGINS_LOADED,
+        PLUGIN_ADDED,
+        PLUGIN_REMOVED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void     gdm_plugin_manager_class_init (GdmPluginManagerClass *klass);
+static void     gdm_plugin_manager_init       (GdmPluginManager      *plugin_manager);
+static void     gdm_plugin_manager_finalize   (GObject             *object);
+
+static GObject *plugin_manager_object = NULL;
+
+G_DEFINE_TYPE (GdmPluginManager, gdm_plugin_manager, G_TYPE_OBJECT)
+
+static GdmPluginManagerOperation *
+start_operation (GdmPluginManager *manager)
+{
+        GdmPluginManagerOperation *operation;
+
+        operation = g_new0 (GdmPluginManagerOperation, 1);
+        operation->cancellable = g_cancellable_new ();
+        operation->manager = manager;
+
+        return operation;
+}
+
+static void
+free_operation (GdmPluginManagerOperation *operation)
+{
+        if (operation->cancellable != NULL) {
+                g_object_unref (operation->cancellable);
+                operation->cancellable = NULL;
+        }
+        g_free (operation);
+}
+
+static void
+cancel_operation (GdmPluginManagerOperation *operation)
+{
+        if (operation->cancellable != NULL &&
+            !g_cancellable_is_cancelled (operation->cancellable)) {
+                g_cancellable_cancel (operation->cancellable);
+        }
+
+        free_operation (operation);
+}
+
+static void
+gdm_plugin_manager_track_operation (GdmPluginManager          *manager,
+                                    GdmPluginManagerOperation *operation)
+{
+        manager->priv->pending_operations =
+            g_list_prepend (manager->priv->pending_operations, operation);
+}
+
+static void
+gdm_plugin_manager_untrack_operation (GdmPluginManager          *manager,
+                                     GdmPluginManagerOperation *operation)
+{
+        manager->priv->pending_operations =
+            g_list_remove (manager->priv->pending_operations, operation);
+}
+
+static void
+gdm_plugin_manager_cancel_pending_operations (GdmPluginManager *manager)
+{
+        GList *node;
+
+        node = manager->priv->pending_operations;
+        while (node != NULL) {
+                GList *next_node;
+                GdmPluginManagerOperation *operation;
+
+                operation = node->data;
+                next_node = node->next;
+
+                cancel_operation (operation);
+                manager->priv->pending_operations =
+                    g_list_delete_link (manager->priv->pending_operations,
+                                        node);
+
+                node = next_node;
+        }
+}
+
+static void
+gdm_plugin_manager_class_init (GdmPluginManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gdm_plugin_manager_finalize;
+
+        signals [PLUGINS_LOADED] =
+                g_signal_new ("plugins-loaded",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmPluginManagerClass, plugins_loaded),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [PLUGIN_ADDED] =
+                g_signal_new ("plugin-added",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmPluginManagerClass, plugin_added),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1, GDM_TYPE_GREETER_PLUGIN);
+        signals [PLUGIN_REMOVED] =
+                g_signal_new ("plugin-removed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmPluginManagerClass, plugin_removed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1, GDM_TYPE_GREETER_PLUGIN);
+
+        g_type_class_add_private (klass, sizeof (GdmPluginManagerPrivate));
+}
+
+static void
+on_plugin_loaded (GdmPluginManager *manager,
+                  GdmGreeterPlugin *plugin)
+{
+        g_debug ("GdmPluginManager: plugin '%s' loaded.",
+                 gdm_greeter_plugin_get_filename (plugin));
+        g_signal_emit (manager, signals [PLUGIN_ADDED], 0, plugin);
+}
+
+static void
+on_plugin_load_failed (GdmPluginManager *manager,
+                       GdmGreeterPlugin *plugin)
+{
+        const char *filename;
+
+        g_debug ("GdmPluginManager: plugin '%s' could not be loaded.",
+                 gdm_greeter_plugin_get_filename (plugin));
+        filename = gdm_greeter_plugin_get_filename (plugin);
+        g_hash_table_remove (manager->priv->plugins, filename);
+}
+
+static void
+on_plugin_unloaded (GdmPluginManager       *manager,
+                    GdmGreeterPlugin *plugin)
+{
+        const char *filename;
+
+        filename = gdm_greeter_plugin_get_filename (plugin);
+        g_hash_table_remove (manager->priv->plugins, filename);
+}
+
+static void
+load_plugin (GdmPluginManager *manager,
+             const char       *filename)
+{
+        GdmGreeterPlugin *plugin;
+
+        g_debug ("GdmPluginManager: loading plugin '%s'", filename);
+
+        plugin = gdm_greeter_plugin_new (filename);
+
+        g_signal_connect_swapped (plugin, "loaded",
+                                  G_CALLBACK (on_plugin_loaded),
+                                  manager);
+        g_signal_connect_swapped (plugin, "load-failed",
+                                  G_CALLBACK (on_plugin_load_failed),
+                                  manager);
+        g_signal_connect_swapped (plugin, "unloaded",
+                                  G_CALLBACK (on_plugin_unloaded),
+                                  manager);
+        g_hash_table_insert (manager->priv->plugins,
+                             g_strdup (filename), plugin);
+
+        gdm_greeter_plugin_load (plugin);
+}
+
+static void
+on_plugin_info_read (GFileEnumerator           *enumerator,
+                     GAsyncResult              *result,
+                     GdmPluginManagerOperation *operation)
+{
+        GdmPluginManager *manager;
+        GFile *plugin_dir_file;
+        GList *file_list, *node;
+        GError *error;
+
+        manager = operation->manager;
+        error = NULL;
+        file_list = g_file_enumerator_next_files_finish (enumerator,
+                                                         result, &error);
+        plugin_dir_file = g_file_enumerator_get_container (enumerator);
+        if (error != NULL) {
+                char  *plugin_dir;
+
+                plugin_dir = g_file_get_parse_name (plugin_dir_file);
+                if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                        g_debug ("GdmPluginManager: Cancelled reading plugin directory %s",
+                                 plugin_dir);
+                } else {
+                        g_warning ("GdmPluginManager: Unable to read plugin directory %s: %s",
+                                   plugin_dir, error->message);
+                }
+                g_free (plugin_dir);
+                g_error_free (error);
+                g_object_unref (plugin_dir_file);
+                gdm_plugin_manager_untrack_operation (manager, operation);
+                return;
+        }
+
+#ifndef PLUGIN_ORDERING_FIGURED_OUT
+        node = file_list;
+        while (node != NULL) {
+                GFileInfo *info;
+                GFile *file;
+                char *path;
+                GList *next_node;
+
+                next_node = node->next;
+
+                info = (GFileInfo *) node->data;
+
+                file = g_file_get_child (plugin_dir_file,
+                                         g_file_info_get_name (info));
+                path = g_file_get_path (file);
+
+                if (g_str_has_suffix (path, "password.so")) {
+                        file_list = g_list_delete_link (file_list, node);
+                        file_list = g_list_prepend (file_list, info);
+                        next_node = NULL;
+                }
+                g_free (path);
+                g_object_unref (file);
+
+                node = next_node;
+        }
+#endif
+
+        node = file_list;
+        while (node != NULL) {
+                GFileInfo *info;
+                GFile *file;
+                char *path;
+
+                info = (GFileInfo *) node->data;
+
+                file = g_file_get_child (plugin_dir_file,
+                                         g_file_info_get_name (info));
+                path = g_file_get_path (file);
+
+                if (g_str_has_suffix (path, G_MODULE_SUFFIX)) {
+                        load_plugin (manager, path);
+                }
+                g_free (path);
+                g_object_unref (file);
+
+                node = node->next;
+        }
+        g_object_unref (plugin_dir_file);
+
+        gdm_plugin_manager_untrack_operation (manager, operation);
+        g_signal_emit (manager, signals [PLUGINS_LOADED], 0);
+
+        g_list_free (file_list);
+}
+
+static void
+on_plugin_dir_opened (GFile                     *plugin_dir_file,
+                      GAsyncResult              *result,
+                      GdmPluginManagerOperation *open_operation)
+{
+        GdmPluginManager *manager;
+        GFileEnumerator *enumerator;
+        GError *error;
+        GdmPluginManagerOperation *operation;
+
+        manager = open_operation->manager;
+        gdm_plugin_manager_untrack_operation (manager, open_operation);
+
+        error = NULL;
+        enumerator = g_file_enumerate_children_finish (plugin_dir_file,
+                                                       result, &error);
+
+        if (enumerator == NULL) {
+                char *plugin_dir;
+
+                plugin_dir = g_file_get_parse_name (plugin_dir_file);
+
+                if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                        g_debug ("GdmPluginManager: Cancelled opening plugin directory %s",
+                                 plugin_dir);
+                } else {
+                        g_warning ("GdmPluginManager: Unable to open plugin directory %s: %s",
+                                   plugin_dir, error->message);
+                }
+                g_free (plugin_dir);
+                g_error_free (error);
+                return;
+        }
+
+        operation = start_operation (manager);
+
+        g_file_enumerator_next_files_async (enumerator, G_MAXINT,
+                                            G_PRIORITY_DEFAULT,
+                                            operation->cancellable,
+                                            (GAsyncReadyCallback)
+                                            on_plugin_info_read,
+                                            operation);
+
+        gdm_plugin_manager_track_operation (manager, operation);
+}
+
+static void
+load_plugins_in_dir (GdmPluginManager *manager,
+                     const char       *plugin_dir)
+{
+        GFile *plugin_dir_file;
+        GdmPluginManagerOperation *operation;
+
+        g_debug ("GdmPluginManager: loading plugins in dir '%s'", plugin_dir);
+
+        operation = start_operation (manager);
+        plugin_dir_file = g_file_new_for_path (plugin_dir);
+        g_file_enumerate_children_async (plugin_dir_file, "standard::*",
+                                         G_FILE_QUERY_INFO_NONE,
+                                         G_PRIORITY_DEFAULT,
+                                         operation->cancellable,
+                                         (GAsyncReadyCallback)
+                                         on_plugin_dir_opened,
+                                         operation);
+        g_object_unref (plugin_dir_file);
+        gdm_plugin_manager_track_operation (manager, operation);
+}
+
+static void
+on_plugin_dir_changed (GFileMonitor              *monitor,
+                       GFile                     *file,
+                       GFile                     *other_file,
+                       GFileMonitorEvent          event_type,
+                       GdmPluginManagerOperation *operation)
+{
+}
+
+static void
+watch_plugin_dir (GdmPluginManager *manager,
+                  const char       *plugin_dir)
+{
+
+        GdmPluginManagerOperation *operation;
+        GFile  *file;
+        GError *error;
+
+        operation = start_operation (manager);
+
+        file = g_file_new_for_path (plugin_dir);
+        manager->priv->plugin_dir_monitor = g_file_monitor_directory (file,
+                                                                      G_FILE_MONITOR_NONE,
+                                                                      operation->cancellable,
+                                                                      &error);
+        if (manager->priv->plugin_dir_monitor != NULL) {
+                g_signal_connect (manager->priv->plugin_dir_monitor,
+                                  "changed",
+                                  G_CALLBACK (on_plugin_dir_changed),
+                                  operation);
+                gdm_plugin_manager_track_operation (manager, operation);
+        } else {
+                g_warning ("Unable to monitor %s: %s",
+                           plugin_dir, error->message);
+                g_error_free (error);
+                free_operation (operation);
+        }
+        g_object_unref (file);
+}
+
+static void
+gdm_plugin_manager_init (GdmPluginManager *manager)
+{
+        manager->priv = GDM_PLUGIN_MANAGER_GET_PRIVATE (manager);
+
+        manager->priv->plugins = g_hash_table_new_full (g_str_hash,
+                                                        g_str_equal,
+                                                        g_free,
+                                                        g_object_unref);
+        watch_plugin_dir (manager, GDM_SIMPLE_GREETER_PLUGINS_DIR);
+        load_plugins_in_dir (manager, GDM_SIMPLE_GREETER_PLUGINS_DIR);
+}
+
+static void
+gdm_plugin_manager_finalize (GObject *object)
+{
+        GdmPluginManager *manager;
+
+        manager = GDM_PLUGIN_MANAGER (object);
+
+        g_hash_table_destroy (manager->priv->plugins);
+        g_file_monitor_cancel (manager->priv->plugin_dir_monitor);
+
+        gdm_plugin_manager_cancel_pending_operations (manager);
+
+        G_OBJECT_CLASS (gdm_plugin_manager_parent_class)->finalize (object);
+}
+
+GdmPluginManager *
+gdm_plugin_manager_ref_default (void)
+{
+        if (plugin_manager_object != NULL) {
+                g_object_ref (plugin_manager_object);
+        } else {
+                plugin_manager_object = g_object_new (GDM_TYPE_PLUGIN_MANAGER, NULL);
+                g_object_add_weak_pointer (plugin_manager_object,
+                                           (gpointer *) &plugin_manager_object);
+        }
+
+        return GDM_PLUGIN_MANAGER (plugin_manager_object);
+}
+
+GdmGreeterPlugin *
+gdm_plugin_manager_get_plugin (GdmPluginManager *manager,
+                               const char       *name)
+{
+        return g_hash_table_lookup (manager->priv->plugins, name);
+}
diff --git a/gui/simple-greeter/gdm-plugin-manager.h b/gui/simple-greeter/gdm-plugin-manager.h
new file mode 100644
index 0000000..f181140
--- /dev/null
+++ b/gui/simple-greeter/gdm-plugin-manager.h
@@ -0,0 +1,66 @@
+/*
+ * 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_PLUGIN_MANAGER_H
+#define __GDM_PLUGIN_MANAGER_H
+
+#include <glib-object.h>
+
+#include "gdm-greeter-plugin.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_PLUGIN_MANAGER         (gdm_plugin_manager_get_type ())
+#define GDM_PLUGIN_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_PLUGIN_MANAGER, GdmPluginManager))
+#define GDM_PLUGIN_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_PLUGIN_MANAGER, GdmPluginManagerClass))
+#define GDM_IS_PLUGIN_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_PLUGIN_MANAGER))
+#define GDM_IS_PLUGIN_MANAGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_PLUGIN_MANAGER))
+#define GDM_PLUGIN_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_PLUGIN_MANAGER, GdmPluginManagerClass))
+
+typedef struct GdmPluginManagerPrivate GdmPluginManagerPrivate;
+
+typedef struct
+{
+        GObject                parent;
+        GdmPluginManagerPrivate *priv;
+} GdmPluginManager;
+
+typedef struct
+{
+        GObjectClass   parent_class;
+
+        void          (* plugins_loaded)              (GdmPluginManager *plugin_manager);
+        void          (* plugin_added)                (GdmPluginManager *plugin_manager,
+                                                       GdmGreeterPlugin *plugin);
+        void          (* plugin_removed)              (GdmPluginManager *plugin_manager,
+                                                       GdmGreeterPlugin *plugin);
+} GdmPluginManagerClass;
+
+GType             gdm_plugin_manager_get_type              (void);
+
+GdmPluginManager *gdm_plugin_manager_ref_default           (void);
+
+GdmGreeterPlugin *gdm_plugin_manager_get_plugin            (GdmPluginManager *plugin,
+                                                            const char       *name);
+
+G_END_DECLS
+
+#endif /* __GDM_PLUGIN_MANAGER_H */
diff --git a/gui/simple-greeter/gdm-task-list.c b/gui/simple-greeter/gdm-task-list.c
new file mode 100644
index 0000000..a7f8c74
--- /dev/null
+++ b/gui/simple-greeter/gdm-task-list.c
@@ -0,0 +1,385 @@
+/*
+ * 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-task-list.h"
+
+#define GDM_TASK_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_TASK_LIST, GdmTaskListPrivate))
+
+struct GdmTaskListPrivate
+{
+        GtkWidget *box;
+        GList     *tasks;
+};
+
+enum {
+        ACTIVATED = 0,
+        DEACTIVATED,
+        NUMBER_OF_SIGNALS
+};
+
+static guint    signals[NUMBER_OF_SIGNALS];
+
+static void     gdm_task_list_class_init  (GdmTaskListClass *klass);
+static void     gdm_task_list_init        (GdmTaskList      *task_list);
+static void     gdm_task_list_finalize    (GObject          *object);
+
+G_DEFINE_TYPE (GdmTaskList, gdm_task_list, GTK_TYPE_ALIGNMENT);
+
+static void
+on_task_toggled (GdmTaskList    *widget,
+                 GtkRadioButton *button)
+{
+        GdmTask *task;
+
+        task = g_object_get_data (G_OBJECT (button), "gdm-task");
+
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
+
+                GList     *task_node;
+
+                /* Sort the list such that the tasks the user clicks last end
+                 * up first.  This doesn't change the order in which the tasks
+                 * appear in the UI, but will affect which tasks we implicitly
+                 * activate if the currently active task gets disabled.
+                 */
+                task_node = g_list_find (widget->priv->tasks, task);
+                if (task_node != NULL) {
+                        widget->priv->tasks = g_list_delete_link (widget->priv->tasks, task_node);
+                        widget->priv->tasks = g_list_prepend (widget->priv->tasks,
+                                                              task);
+                }
+
+                g_signal_emit (widget, signals[ACTIVATED], 0, task);
+        } else {
+                g_signal_emit (widget, signals[DEACTIVATED], 0, task);
+        }
+}
+
+GdmTask *
+gdm_task_list_foreach_task (GdmTaskList           *task_list,
+                            GdmTaskListForeachFunc  search_func,
+                            gpointer               data)
+{
+        GList *node;
+
+        for (node = task_list->priv->tasks; node != NULL; node = node->next) {
+                GdmTask *task;
+
+                task = node->data;
+
+                if (search_func (task_list, task, data)) {
+                        return g_object_ref (task);
+                }
+        }
+
+        return NULL;
+}
+
+static void
+on_task_enabled (GdmTaskList *task_list,
+                 GdmTask     *task)
+{
+        GtkWidget *button;
+
+        button = g_object_get_data (G_OBJECT (task), "gdm-task-list-button");
+
+        gtk_widget_set_sensitive (button, TRUE);
+}
+
+static void
+activate_first_available_task (GdmTaskList *task_list)
+{
+        GList *node;
+
+        node = task_list->priv->tasks;
+        while (node != NULL) {
+                GdmTask   *task;
+
+                task = GDM_TASK (node->data);
+
+                if (gdm_task_list_set_active_task (task_list, task)) {
+                        break;
+                }
+
+                node = node->next;
+        }
+
+}
+
+static void
+on_task_disabled (GdmTaskList *task_list,
+                  GdmTask     *task)
+{
+        GtkWidget *button;
+        gboolean   was_active;
+
+        button = g_object_get_data (G_OBJECT (task), "gdm-task-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_task (task_list);
+        }
+}
+
+void
+gdm_task_list_add_task (GdmTaskList *task_list,
+                        GdmTask     *task)
+{
+        GtkWidget *image;
+        GtkWidget *button;
+        GIcon     *icon;
+        char      *description;
+
+        if (task_list->priv->tasks == NULL) {
+                button = gtk_radio_button_new (NULL);
+        } else {
+                GdmTask *previous_task;
+                GtkRadioButton *previous_button;
+
+                previous_task = GDM_TASK (task_list->priv->tasks->data);
+                previous_button = GTK_RADIO_BUTTON (g_object_get_data (G_OBJECT (previous_task), "gdm-task-list-button"));
+                button = gtk_radio_button_new_from_widget (previous_button);
+        }
+        g_object_set_data (G_OBJECT (task), "gdm-task-list-button", button);
+
+        g_object_set (G_OBJECT (button), "draw-indicator", FALSE, NULL);
+        g_object_set_data (G_OBJECT (button), "gdm-task", task);
+        g_signal_connect_swapped (button, "toggled",
+                                  G_CALLBACK (on_task_toggled),
+                                  task_list);
+
+        gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
+        gtk_widget_set_sensitive (button, gdm_task_is_enabled (task));
+
+        g_signal_connect_swapped (G_OBJECT (task), "enabled",
+                                  G_CALLBACK (on_task_enabled),
+                                  task_list);
+
+        g_signal_connect_swapped (G_OBJECT (task), "disabled",
+                                  G_CALLBACK (on_task_disabled),
+                                  task_list);
+
+        icon = gdm_task_get_icon (task);
+        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_task_get_description (task);
+        gtk_widget_set_tooltip_text (button, description);
+        g_free (description);
+        gtk_widget_show (button);
+
+        gtk_container_add (GTK_CONTAINER (task_list->priv->box), button);
+        task_list->priv->tasks = g_list_append (task_list->priv->tasks,
+                                                g_object_ref (task));
+
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
+                g_signal_emit (task_list, signals[ACTIVATED], 0, task);
+        }
+}
+
+void
+gdm_task_list_remove_task (GdmTaskList *task_list,
+                           GdmTask     *task)
+{
+        GtkWidget *button;
+
+        task_list->priv->tasks = g_list_remove (task_list->priv->tasks, task);
+
+        button = g_object_get_data (G_OBJECT (task), "gdm-task-list-button");
+
+        if (button != NULL) {
+            g_signal_handlers_disconnect_by_func (G_OBJECT (task),
+                                                  G_CALLBACK (on_task_enabled),
+                                                  task_list);
+            g_signal_handlers_disconnect_by_func (G_OBJECT (task),
+                                                  G_CALLBACK (on_task_disabled),
+                                                  task_list);
+            gtk_widget_destroy (button);
+            g_object_set_data (G_OBJECT (task), "gdm-task-list-button", NULL);
+        }
+
+        g_object_unref (task);
+
+        activate_first_available_task (task_list);
+}
+
+static void
+gdm_task_list_class_init (GdmTaskListClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gdm_task_list_finalize;
+
+        signals [ACTIVATED] = g_signal_new ("activated",
+                                            G_TYPE_FROM_CLASS (object_class),
+                                            G_SIGNAL_RUN_FIRST,
+                                            G_STRUCT_OFFSET (GdmTaskListClass, 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 (GdmTaskListClass, deactivated),
+                                            NULL,
+                                            NULL,
+                                            g_cclosure_marshal_VOID__OBJECT,
+                                            G_TYPE_NONE,
+                                            1, G_TYPE_OBJECT);
+
+        g_type_class_add_private (klass, sizeof (GdmTaskListPrivate));
+}
+
+static void
+gdm_task_list_init (GdmTaskList *widget)
+{
+        widget->priv = GDM_TASK_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_task_list_finalize (GObject *object)
+{
+        GdmTaskList *widget;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_TASK_LIST (object));
+
+        widget = GDM_TASK_LIST (object);
+
+        g_list_foreach (widget->priv->tasks, (GFunc) g_object_unref, NULL);
+        g_list_free (widget->priv->tasks);
+
+        G_OBJECT_CLASS (gdm_task_list_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_task_list_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_TASK_LIST, NULL);
+
+        return GTK_WIDGET (object);
+}
+
+gboolean
+gdm_task_list_task_is_active (GdmTaskList *task_list,
+                              GdmTask     *task)
+{
+        GtkWidget *button;
+
+        button = g_object_get_data (G_OBJECT (task), "gdm-task-list-button");
+
+        return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+}
+
+GdmTask *
+gdm_task_list_get_active_task (GdmTaskList *widget)
+{
+        return gdm_task_list_foreach_task (widget,
+                                        (GdmTaskListForeachFunc)
+                                        gdm_task_list_task_is_active,
+                                        NULL);
+}
+
+gboolean
+gdm_task_list_set_active_task (GdmTaskList *widget,
+                               GdmTask     *task)
+{
+        GtkWidget *button;
+        gboolean   was_sensitive;
+        gboolean   was_activated;
+
+        if (!gdm_task_is_visible (task)) {
+                return FALSE;
+        }
+
+        was_sensitive = GTK_WIDGET_SENSITIVE (widget);
+        gtk_widget_set_sensitive (GTK_WIDGET (widget), TRUE);
+
+        button = GTK_WIDGET (g_object_get_data (G_OBJECT (task),
+                             "gdm-task-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;
+}
+
+int
+gdm_task_list_get_number_of_tasks (GdmTaskList *widget)
+{
+        return g_list_length (widget->priv->tasks);
+}
+
+int
+gdm_task_list_get_number_of_visible_tasks (GdmTaskList *widget)
+{
+        GList *node;
+        int number_of_visible_tasks;
+
+        number_of_visible_tasks = 0;
+        for (node = widget->priv->tasks; node != NULL; node = node->next) {
+                GdmTask *task;
+
+                task = node->data;
+
+                if (gdm_task_is_enabled (task) && gdm_task_is_visible (task)) {
+                        number_of_visible_tasks++;
+                }
+        }
+
+        return number_of_visible_tasks;
+}
diff --git a/gui/simple-greeter/gdm-task-list.h b/gui/simple-greeter/gdm-task-list.h
new file mode 100644
index 0000000..1c15168
--- /dev/null
+++ b/gui/simple-greeter/gdm-task-list.h
@@ -0,0 +1,85 @@
+/*
+ * 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_TASK_LIST_H
+#define __GDM_TASK_LIST_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gtk/gtkalignment.h>
+
+#include "gdm-task.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_TASK_LIST         (gdm_task_list_get_type ())
+#define GDM_TASK_LIST(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_TASK_LIST, GdmTaskList))
+#define GDM_TASK_LIST_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_TASK_LIST, GdmTaskListClass))
+#define GDM_IS_TASK_LIST(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_TASK_LIST))
+#define GDM_IS_TASK_LIST_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_TASK_LIST))
+#define GDM_TASK_LIST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_TASK_LIST, GdmTaskListClass))
+
+typedef struct GdmTaskListPrivate GdmTaskListPrivate;
+typedef struct _GdmTaskList GdmTaskList;
+
+typedef gboolean (* GdmTaskListForeachFunc) (GdmTaskList *task_list,
+                                            GdmTask     *task,
+                                            gpointer     data);
+
+struct _GdmTaskList
+{
+        GtkAlignment             parent;
+        GdmTaskListPrivate *priv;
+};
+
+typedef struct
+{
+        GtkAlignmentClass       parent_class;
+
+        void (* deactivated)      (GdmTaskList *widget,
+                                   GdmTask     *task);
+        void (* activated)      (GdmTaskList *widget,
+                                 GdmTask     *task);
+} GdmTaskListClass;
+
+GType       gdm_task_list_get_type               (void);
+GtkWidget * gdm_task_list_new                    (void);
+
+
+gboolean    gdm_task_list_task_is_active (GdmTaskList *task_list,
+                                          GdmTask     *task);
+GdmTask *   gdm_task_list_get_active_task (GdmTaskList *widget);
+gboolean    gdm_task_list_set_active_task (GdmTaskList *widget,
+                                           GdmTask     *task);
+GdmTask *   gdm_task_list_foreach_task (GdmTaskList           *widget,
+                                     GdmTaskListForeachFunc  foreach_func,
+                                     gpointer               data);
+void        gdm_task_list_add_task        (GdmTaskList *widget,
+                                           GdmTask     *task);
+
+void        gdm_task_list_remove_task        (GdmTaskList *widget,
+                                              GdmTask     *task);
+
+int         gdm_task_list_get_number_of_tasks (GdmTaskList *widget);
+
+int         gdm_task_list_get_number_of_visible_tasks (GdmTaskList *widget);
+G_END_DECLS
+
+#endif /* __GDM_TASK_LIST_H */
diff --git a/gui/simple-greeter/gdm-user-chooser-widget.c b/gui/simple-greeter/gdm-user-chooser-widget.c
index 19a7922..daa55fc 100644
--- a/gui/simple-greeter/gdm-user-chooser-widget.c
+++ b/gui/simple-greeter/gdm-user-chooser-widget.c
@@ -371,9 +371,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..0d7a0bd
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/Makefile.am
@@ -0,0 +1,48 @@
+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-task.h				\
+	gdm-task.c				\
+	gdm-conversation.h			\
+	gdm-conversation.c			\
+	gdm-greeter-extension.h			\
+	gdm-greeter-extension.c			\
+	$(NULL)
+
+libgdmsimplegreeter_la_LIBADD =			\
+	$(GTK_LIBS)				\
+	$(top_builddir)/common/libgdmcommon.la	\
+	$(NULL)
+
+libgdmsimplegreeter_la_LDFLAGS = 		\
+	-export-symbols-regex '^[^_].*'		\
+	-version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \
+	-no-undefined				\
+	$(NULL)
+
+headersdir = $(includedir)/gdm/simple-greeter
+headers_HEADERS = gdm-greeter-extension.h
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = gdmsimplegreeter.pc
+
+EXTRA_DIST = gdmsimplegreeter.pc
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-conversation.c b/gui/simple-greeter/libgdmsimplegreeter/gdm-conversation.c
new file mode 100644
index 0000000..ee763ef
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-conversation.c
@@ -0,0 +1,186 @@
+/*
+ * 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 <gtk/gtk.h>
+
+#include "gdm-conversation.h"
+#include "gdm-marshal.h"
+#include "gdm-task.h"
+
+enum {
+        ANSWER,
+        USER_CHOSEN,
+        CANCEL,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void gdm_conversation_class_init (gpointer g_iface);
+
+GType
+gdm_conversation_get_type (void)
+{
+        static GType type = 0;
+
+        if (!type) {
+                type = g_type_register_static_simple (G_TYPE_INTERFACE,
+                                                      "GdmConversation",
+                                                      sizeof (GdmConversationIface),
+                                                      (GClassInitFunc) gdm_conversation_class_init,
+                                                      0, NULL, 0);
+
+                g_type_interface_add_prerequisite (type, G_TYPE_OBJECT);
+                g_type_interface_add_prerequisite (type, GDM_TYPE_TASK);
+        }
+
+        return type;
+}
+
+static void
+gdm_conversation_class_init (gpointer g_iface)
+{
+        GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+
+        signals [ANSWER] =
+                g_signal_new ("answer",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmConversationIface, 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 (GdmConversationIface, 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 (GdmConversationIface, cancel),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+}
+
+void
+gdm_conversation_set_message  (GdmConversation   *conversation,
+                               const char        *message)
+{
+        GDM_CONVERSATION_GET_IFACE (conversation)->set_message (conversation, message);
+}
+
+void
+gdm_conversation_ask_question (GdmConversation   *conversation,
+                               const char        *message)
+{
+        GDM_CONVERSATION_GET_IFACE (conversation)->ask_question (conversation, message);
+}
+
+void
+gdm_conversation_ask_secret (GdmConversation   *conversation,
+                             const char        *message)
+{
+        GDM_CONVERSATION_GET_IFACE (conversation)->ask_secret (conversation, message);
+}
+
+void
+gdm_conversation_reset (GdmConversation *conversation)
+{
+        return GDM_CONVERSATION_GET_IFACE (conversation)->reset (conversation);
+}
+
+void
+gdm_conversation_set_ready (GdmConversation *conversation)
+{
+        return GDM_CONVERSATION_GET_IFACE (conversation)->set_ready (conversation);
+}
+
+char *
+gdm_conversation_get_service_name (GdmConversation   *conversation)
+{
+        return GDM_CONVERSATION_GET_IFACE (conversation)->get_service_name (conversation);
+}
+
+GtkWidget *
+gdm_conversation_get_page (GdmConversation *conversation)
+{
+        return GDM_CONVERSATION_GET_IFACE (conversation)->get_page (conversation);
+}
+
+GtkActionGroup *
+gdm_conversation_get_actions (GdmConversation *conversation)
+{
+        return GDM_CONVERSATION_GET_IFACE (conversation)->get_actions (conversation);
+}
+
+gboolean
+gdm_conversation_focus (GdmConversation *conversation)
+{
+        return GDM_CONVERSATION_GET_IFACE (conversation)->focus (conversation);
+}
+
+void
+gdm_conversation_request_answer (GdmConversation *conversation)
+{
+        return GDM_CONVERSATION_GET_IFACE (conversation)->request_answer (conversation);
+}
+
+/* protected
+ */
+void
+gdm_conversation_answer (GdmConversation   *conversation,
+                         const char        *answer)
+{
+        g_signal_emit (conversation, signals [ANSWER], 0, answer);
+}
+
+void
+gdm_conversation_cancel (GdmConversation   *conversation)
+{
+        g_signal_emit (conversation, signals [CANCEL], 0);
+}
+
+gboolean
+gdm_conversation_choose_user (GdmConversation *conversation,
+                              const char      *username)
+{
+        gboolean was_chosen;
+
+        was_chosen = FALSE;
+
+        g_signal_emit (conversation, signals [USER_CHOSEN], 0, username, &was_chosen);
+
+        return was_chosen;
+}
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-conversation.h b/gui/simple-greeter/libgdmsimplegreeter/gdm-conversation.h
new file mode 100644
index 0000000..b37b21e
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-conversation.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 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_CONVERSATION_H
+#define __GDM_CONVERSATION_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_CONVERSATION         (gdm_conversation_get_type ())
+#define GDM_CONVERSATION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_CONVERSATION, GdmConversation))
+#define GDM_CONVERSATION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_CONVERSATION, GdmConversationIface))
+#define GDM_IS_CONVERSATION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_CONVERSATION))
+#define GDM_CONVERSATION_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GDM_TYPE_CONVERSATION, GdmConversationIface))
+
+#define GDM_CONVERSATION_DEFAULT_ACTION "default-action"
+#define GDM_CONVERSATION_OTHER_USER "__other"
+
+typedef struct _GdmConversation      GdmConversation;
+typedef struct _GdmConversationIface GdmConversationIface;
+
+struct _GdmConversationIface
+{
+        GTypeInterface base_iface;
+
+        /* methods */
+        void   (* set_message)  (GdmConversation *conversation,
+                                 const char      *message);
+        void   (* ask_question) (GdmConversation *conversation,
+                                 const char      *message);
+        void   (* ask_secret)   (GdmConversation *conversation,
+                                 const char      *message);
+        void   (* reset)        (GdmConversation *conversation);
+        void   (* set_ready)    (GdmConversation *conversation);
+        char * (* get_service_name)  (GdmConversation *conversation);
+        GtkWidget * (* get_page) (GdmConversation *conversation);
+        GtkActionGroup * (* get_actions) (GdmConversation *conversation);
+        void   (* request_answer)    (GdmConversation *conversation);
+        gboolean   (* focus)    (GdmConversation *conversation);
+
+        /* signals */
+        char * (* answer)       (GdmConversation *conversation);
+        void   (* cancel)       (GdmConversation *conversation);
+        gboolean  (* user_chosen)  (GdmConversation *conversation);
+};
+
+GType  gdm_conversation_get_type     (void) G_GNUC_CONST;
+
+void   gdm_conversation_set_message  (GdmConversation   *conversation,
+                                      const char        *message);
+void   gdm_conversation_ask_question (GdmConversation   *conversation,
+                                      const char        *message);
+void   gdm_conversation_ask_secret   (GdmConversation   *conversation,
+                                      const char        *message);
+void   gdm_conversation_reset        (GdmConversation   *converastion);
+void   gdm_conversation_set_ready    (GdmConversation   *converastion);
+char  *gdm_conversation_get_service_name   (GdmConversation   *conversation);
+GtkWidget *gdm_conversation_get_page       (GdmConversation   *conversation);
+GtkActionGroup *gdm_conversation_get_actions (GdmConversation   *conversation);
+void   gdm_conversation_request_answer       (GdmConversation   *conversation);
+gboolean   gdm_conversation_focus    (GdmConversation *conversation);
+
+/* protected
+ */
+void   gdm_conversation_answer (GdmConversation   *conversation,
+                                const char        *answer);
+void   gdm_conversation_cancel (GdmConversation   *conversation);
+gboolean  gdm_conversation_choose_user (GdmConversation   *conversation,
+                                        const char        *username);
+
+G_END_DECLS
+
+#endif /* __GDM_CONVERSATION_H */
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-greeter-extension.c b/gui/simple-greeter/libgdmsimplegreeter/gdm-greeter-extension.c
new file mode 100644
index 0000000..a71d3f7
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-greeter-extension.c
@@ -0,0 +1,93 @@
+/*
+ * 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-greeter-extension.h"
+
+enum {
+        LOADED,
+        LOAD_FAILED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void gdm_greeter_extension_class_init (gpointer g_iface);
+
+GType
+gdm_greeter_extension_get_type (void)
+{
+        static GType greeter_extension_type = 0;
+
+        if (!greeter_extension_type) {
+                greeter_extension_type = g_type_register_static_simple (G_TYPE_INTERFACE,
+                                                           "GdmGreeterExtension",
+                                                           sizeof (GdmGreeterExtensionIface),
+                                                           (GClassInitFunc) gdm_greeter_extension_class_init,
+                                                           0, NULL, 0);
+
+                g_type_interface_add_prerequisite (greeter_extension_type, G_TYPE_OBJECT);
+        }
+
+        return greeter_extension_type;
+}
+
+static void
+gdm_greeter_extension_class_init (gpointer g_iface)
+{
+        GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+
+        signals [LOADED] =
+                g_signal_new ("loaded",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmGreeterExtensionIface, loaded),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+
+        signals [LOADED] =
+                g_signal_new ("load_failed",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmGreeterExtensionIface, load_failed),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__POINTER,
+                              G_TYPE_NONE,
+                              1, G_TYPE_POINTER);
+}
+
+void
+gdm_greeter_extension_loaded (GdmGreeterExtension *extension)
+{
+        g_signal_emit (extension, signals [LOADED], 0);
+}
+
+void
+gdm_greeter_extension_load_failed (GdmGreeterExtension *extension,
+                                   GError              *error)
+{
+        g_signal_emit (extension, signals [LOAD_FAILED], 0, error);
+}
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-greeter-extension.h b/gui/simple-greeter/libgdmsimplegreeter/gdm-greeter-extension.h
new file mode 100644
index 0000000..283ba0e
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-greeter-extension.h
@@ -0,0 +1,55 @@
+/*
+ * 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_GREETER_EXTENSION_H
+#define __GDM_GREETER_EXTENSION_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_GREETER_EXTENSION         (gdm_greeter_extension_get_type ())
+#define GDM_GREETER_EXTENSION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_GREETER_EXTENSION, GdmGreeterExtension))
+#define GDM_GREETER_EXTENSION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_GREETER_EXTENSION, GdmGreeterExtensionClass))
+#define GDM_IS_GREETER_EXTENSION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_GREETER_EXTENSION))
+#define GDM_GREETER_EXTENSION_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GDM_TYPE_GREETER_EXTENSION, GdmGreeterExtensionIface))
+
+typedef struct _GdmGreeterExtension      GdmGreeterExtension;
+typedef struct _GdmGreeterExtensionIface GdmGreeterExtensionIface;
+
+struct _GdmGreeterExtensionIface
+{
+        GTypeInterface base_iface;
+
+        void (* loaded) (GdmGreeterExtension *extension);
+        void (* load_failed) (GdmGreeterExtension *extension,
+                              GError              *error);
+};
+
+GType gdm_greeter_extension_get_type (void) G_GNUC_CONST;
+
+void gdm_greeter_extension_loaded      (GdmGreeterExtension *extension);
+void gdm_greeter_extension_load_failed (GdmGreeterExtension *extension,
+                                        GError              *error);
+
+typedef GdmGreeterExtension * (* GdmGreeterPluginGetExtensionFunc) (void);
+
+G_END_DECLS
+#endif /* __GDM_GREETER_EXTENSION_H */
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-task.c b/gui/simple-greeter/libgdmsimplegreeter/gdm-task.c
new file mode 100644
index 0000000..858b1ef
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-task.c
@@ -0,0 +1,129 @@
+/*
+ * 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-task.h"
+
+enum {
+        ENABLED,
+        DISABLED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+static void gdm_task_class_init (gpointer g_iface);
+
+GType
+gdm_task_get_type (void)
+{
+        static GType task_type = 0;
+
+        if (!task_type) {
+                task_type = g_type_register_static_simple (G_TYPE_INTERFACE,
+                                                           "GdmTask",
+                                                           sizeof (GdmTaskIface),
+                                                           (GClassInitFunc) gdm_task_class_init,
+                                                           0, NULL, 0);
+
+                g_type_interface_add_prerequisite (task_type, G_TYPE_OBJECT);
+        }
+
+        return task_type;
+}
+
+GIcon *
+gdm_task_get_icon (GdmTask *task)
+{
+        return GDM_TASK_GET_IFACE (task)->get_icon (task);
+}
+
+char *
+gdm_task_get_description (GdmTask *task)
+{
+        return GDM_TASK_GET_IFACE (task)->get_description (task);
+}
+
+char *
+gdm_task_get_name (GdmTask *task)
+{
+        return GDM_TASK_GET_IFACE (task)->get_name (task);
+}
+
+void
+gdm_task_set_enabled (GdmTask   *task,
+                      gboolean   should_enable)
+{
+        g_object_set_data (G_OBJECT (task), "gdm-task-is-disabled", GINT_TO_POINTER (!should_enable));
+
+        if (should_enable) {
+                g_signal_emit (G_OBJECT (task), signals [ENABLED], 0);
+        } else {
+                g_signal_emit (G_OBJECT (task), signals [DISABLED], 0);
+        }
+}
+
+gboolean
+gdm_task_is_enabled (GdmTask   *task)
+{
+        return !g_object_get_data (G_OBJECT (task), "gdm-task-is-disabled");
+}
+
+gboolean
+gdm_task_is_choosable (GdmTask *task)
+{
+        return GDM_TASK_GET_IFACE (task)->is_choosable (task);
+}
+
+gboolean
+gdm_task_is_visible (GdmTask *task)
+{
+        return GDM_TASK_GET_IFACE (task)->is_visible (task);
+}
+
+static void
+gdm_task_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 (GdmTaskIface, 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 (GdmTaskIface, disabled),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+}
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-task.h b/gui/simple-greeter/libgdmsimplegreeter/gdm-task.h
new file mode 100644
index 0000000..51e2b0a
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-task.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 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_TASK_H
+#define __GDM_TASK_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_TASK         (gdm_task_get_type ())
+#define GDM_TASK(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_TASK, GdmTask))
+#define GDM_TASK_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_TASK, GdmTaskIface))
+#define GDM_IS_TASK(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_TASK))
+#define GDM_TASK_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GDM_TYPE_TASK, GdmTaskIface))
+
+typedef struct _GdmTask      GdmTask;
+typedef struct _GdmTaskIface GdmTaskIface;
+
+struct _GdmTaskIface
+{
+        GTypeInterface base_iface;
+
+        /* methods */
+        GIcon * (* get_icon)        (GdmTask   *task);
+        char *  (* get_description) (GdmTask   *task);
+        char *  (* get_name)        (GdmTask   *task);
+        gboolean  (* is_choosable)    (GdmTask   *task);
+        gboolean  (* is_visible)    (GdmTask   *task);
+        /* signals */
+        void (* enabled) (GdmTask *task);
+        void (* disabled) (GdmTask *task);
+};
+
+GType  gdm_task_get_type        (void) G_GNUC_CONST;
+
+GIcon *gdm_task_get_icon        (GdmTask   *task);
+char  *gdm_task_get_description (GdmTask   *task);
+char  *gdm_task_get_name        (GdmTask   *task);
+void   gdm_task_set_enabled     (GdmTask   *task,
+                                 gboolean   should_enable);
+gboolean   gdm_task_is_enabled     (GdmTask   *task);
+gboolean   gdm_task_is_choosable   (GdmTask   *task);
+gboolean   gdm_task_is_visible   (GdmTask   *task);
+G_END_DECLS
+
+#endif /* __GDM_TASK_H */
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..764904d
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/Makefile.am
@@ -0,0 +1,53 @@
+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/libnotificationarea	\
+	-I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter	\
+	-DDMCONFDIR=\""$(dmconfdir)"\"			\
+	-DGDMCONFDIR=\"$(gdmconfdir)\"                  \
+	-DPLUGINDATADIR=\""$(extensiondir)"\"		\
+	-DPAMSERVICENAME=\""$(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 = password.la
+
+password_la_CFLAGS =			\
+	$(SIMPLE_GREETER_CFLAGS)	\
+	$(NULL)
+
+password_la_LDFLAGS = -module -avoid-version -export-dynamic
+password_la_LIBADD = ../../../../common/libgdmcommon.la \
+			../../libgdmsimplegreeter/libgdmsimplegreeter.la
+password_la_SOURCES =				\
+			gdm-password-extension.h	\
+			gdm-password-extension.c	\
+			plugin.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-extension.c b/gui/simple-greeter/plugins/password/gdm-password-extension.c
new file mode 100644
index 0000000..255283e
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/gdm-password-extension.c
@@ -0,0 +1,328 @@
+/*
+ * 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-conversation.h"
+#include "gdm-task.h"
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+struct _GdmPasswordExtensionPrivate
+{
+        GIcon     *icon;
+        GtkWidget *page;
+        GtkActionGroup *actions;
+
+        GtkWidget *message_label;
+        GtkWidget *prompt_label;
+        GtkWidget *prompt_entry;
+
+        guint      answer_pending : 1;
+};
+
+static void gdm_password_extension_finalize (GObject *object);
+
+static void gdm_task_iface_init (GdmTaskIface *iface);
+static void gdm_conversation_iface_init (GdmConversationIface *iface);
+static void gdm_greeter_extension_iface_init (GdmGreeterExtensionIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdmPasswordExtension,
+                         gdm_password_extension,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDM_TYPE_GREETER_EXTENSION,
+                                                gdm_greeter_extension_iface_init)
+                         G_IMPLEMENT_INTERFACE (GDM_TYPE_TASK,
+                                                gdm_task_iface_init)
+                         G_IMPLEMENT_INTERFACE (GDM_TYPE_CONVERSATION,
+                                                gdm_conversation_iface_init));
+
+static void
+gdm_password_extension_set_message (GdmConversation *conversation,
+                                    const char *message)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (conversation);
+        gtk_widget_show (extension->priv->message_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message);
+}
+
+static void
+gdm_password_extension_ask_question (GdmConversation *conversation,
+                                     const char      *message)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (conversation);
+        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;
+}
+
+static void
+gdm_password_extension_ask_secret (GdmConversation *conversation,
+                                   const char      *message)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (conversation);
+        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;
+}
+
+static void
+gdm_password_extension_reset (GdmConversation *conversation)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (conversation);
+        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;
+
+        gdm_task_set_enabled (GDM_TASK (conversation), FALSE);
+}
+
+static void
+gdm_password_extension_set_ready (GdmConversation *conversation)
+{
+        gdm_task_set_enabled (GDM_TASK (conversation), TRUE);
+}
+
+char *
+gdm_password_extension_get_service_name (GdmConversation *conversation)
+{
+        return g_strdup (PAMSERVICENAME);
+}
+
+GtkWidget *
+gdm_password_extension_get_page (GdmConversation *conversation)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (conversation);
+        return extension->priv->page;
+}
+
+GtkActionGroup *
+gdm_password_extension_get_actions (GdmConversation *conversation)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (conversation);
+        return g_object_ref (extension->priv->actions);
+}
+
+void
+gdm_password_extension_request_answer (GdmConversation *conversation)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (conversation);
+        const char *text;
+
+        if (!extension->priv->answer_pending) {
+                gdm_conversation_answer (conversation, NULL);
+                return;
+        }
+
+        extension->priv->answer_pending = FALSE;
+        text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry));
+        gdm_conversation_answer (conversation, 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), "");
+}
+
+gboolean
+gdm_password_extension_focus (GdmConversation *conversation)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (conversation);
+        if (!extension->priv->answer_pending) {
+                gdm_conversation_answer (conversation, NULL);
+                return FALSE;
+        }
+
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        return TRUE;
+}
+
+GIcon *
+gdm_password_extension_get_icon (GdmTask *task)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (task);
+        return g_object_ref (extension->priv->icon);
+}
+
+char *
+gdm_password_extension_get_name (GdmTask *task)
+{
+        return g_strdup (_("Password Authentication"));
+}
+
+char *
+gdm_password_extension_get_description (GdmTask *task)
+{
+        return g_strdup (_("Log into session with username and password"));
+}
+
+gboolean
+gdm_password_extension_is_choosable (GdmTask *task)
+{
+        return FALSE;
+}
+
+gboolean
+gdm_password_extension_is_visible (GdmTask *task)
+{
+        return TRUE;
+}
+
+static void
+gdm_task_iface_init (GdmTaskIface *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;
+}
+
+static void
+gdm_conversation_iface_init (GdmConversationIface *iface)
+{
+        iface->set_message = gdm_password_extension_set_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->request_answer = gdm_password_extension_request_answer;
+        iface->focus = gdm_password_extension_focus;
+}
+
+static void
+gdm_greeter_extension_iface_init (GdmGreeterExtensionIface *iface)
+{
+}
+
+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)
+{
+}
+
+static void
+on_activate_log_in (GdmPasswordExtension *extension)
+{
+        gdm_password_extension_request_answer (GDM_CONVERSATION (extension));
+}
+
+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");
+
+        action = gtk_action_new (GDM_CONVERSATION_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);
+}
+
+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);
+
+        gdm_password_extension_reset (GDM_CONVERSATION (extension));
+}
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..99fe17b
--- /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-greeter-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))
+
+typedef struct _GdmPasswordExtensionPrivate GdmPasswordExtensionPrivate;
+
+typedef struct
+{
+        GObject                  parent;
+        GdmPasswordExtensionPrivate *priv;
+} GdmPasswordExtension;
+
+typedef struct
+{
+        GObjectClass parent_class;
+} GdmPasswordExtensionClass;
+
+GType                 gdm_password_extension_get_type      (void);
+
+GdmPasswordExtension *gdm_password_extension_new       (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/gui/simple-greeter/plugins/password/plugin.c b/gui/simple-greeter/plugins/password/plugin.c
new file mode 100644
index 0000000..9b87c67
--- /dev/null
+++ b/gui/simple-greeter/plugins/password/plugin.c
@@ -0,0 +1,40 @@
+/*
+ * 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 "gdm-password-extension.h"
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+GdmGreeterExtension *
+gdm_greeter_plugin_get_extension (void)
+{
+        static GObject *extension;
+
+        if (extension != NULL) {
+                g_object_ref (extension);
+        } else {
+                extension = g_object_new (GDM_TYPE_PASSWORD_EXTENSION, NULL);
+                g_object_add_weak_pointer (extension, (gpointer *) &extension);
+        }
+
+        return GDM_GREETER_EXTENSION (extension);
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1fccb90..7c344c9 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -84,6 +84,7 @@ gui/simple-greeter/gdm-timer.c
 gui/simple-greeter/gdm-user.c
 gui/simple-greeter/gdm-user-chooser-widget.c
 gui/simple-greeter/greeter-main.c
+gui/simple-greeter/plugins/password/gdm-password-extension.c
 gui/user-switch-applet/applet.c
 gui/user-switch-applet/gdm-entry-menu-item.c
 gui/user-switch-applet/GNOME_FastUserSwitchApplet.server.in.in



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