[gnome-initial-setup/wip/enterprise] wip enterprise



commit a67353774a17137d2f6b4d7a6d8bca8fbe4f5487
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Wed Jun 13 22:06:09 2012 -0400

    wip enterprise

 .gitignore                                         |    2 +
 configure.ac                                       |   17 +
 gnome-initial-setup/pages/account/Makefile.am      |   32 +-
 .../pages/account/gis-account-page.c               |  580 +++++++++++++-
 .../pages/account/gis-account-page.ui              |  202 +++++-
 .../pages/account/org.freedesktop.realmd.xml       |  666 ++++++++++++++++
 .../pages/account/um-realm-manager.c               |  828 ++++++++++++++++++++
 .../pages/account/um-realm-manager.h               |  106 +++
 8 files changed, 2382 insertions(+), 51 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 4d4a1a6..c6e8da8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,8 @@ gnome-initial-setup/setup_resources.c
 gnome-initial-setup/gnome-initial-setup
 gnome-initial-setup/gnome-initial-setup-copy-worker
 
+gnome-initial-setup/pages/account/um-realm-generated.c
+gnome-initial-setup/pages/account/um-realm-generated.h
 gnome-initial-setup/pages/location/timedated.c
 gnome-initial-setup/pages/location/timedated.h
 gnome-initial-setup/pages/language/languages/list-languages
diff --git a/configure.ac b/configure.ac
index ca8acb1..f5fb6c6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -49,6 +49,23 @@ if test x$have_clutter = xyes; then
   AC_DEFINE(HAVE_CLUTTER, 1, [Build with Clutter support?])
 fi
 
+# Kerberos kerberos support
+AC_PATH_PROG(KRB5_CONFIG, krb5-config, no)
+if test "$KRB5_CONFIG" = "no"; then
+  AC_MSG_ERROR([krb5-config executable not found in your path - should be installed with the kerberos libraries])
+fi
+
+AC_MSG_CHECKING(for krb5 libraries and flags)
+KRB5_CFLAGS="`$KRB5_CONFIG --cflags`"
+KRB5_LIBS="`$KRB5_CONFIG --libs`"
+AC_MSG_RESULT($KRB5_CFLAGS $KRB5_LIBS)
+
+AC_SUBST(KRB5_CFLAGS)
+AC_SUBST(KRB5_LIBS)
+
+INITIAL_SETUP_CFLAGS="$INITIAL_SETUP_CFLAGS $KRB5_CFLAGS"
+INITIAL_SETUP_LIBS="$INITIAL_SETUP_LIBS $KRB5_LIBS"
+
 AC_CHECK_HEADER([pwquality.h], [have_pwquality=yes], [have_pwquality=no])
 AM_CONDITIONAL(HAVE_PWQUALITY, [test x$have_pwquality = xyes])
 if test x$have_pwquality = xyes ; then
diff --git a/gnome-initial-setup/pages/account/Makefile.am b/gnome-initial-setup/pages/account/Makefile.am
index 3616b1c..61c7890 100644
--- a/gnome-initial-setup/pages/account/Makefile.am
+++ b/gnome-initial-setup/pages/account/Makefile.am
@@ -1,4 +1,6 @@
 
+NULL =
+
 if HAVE_PWQUALITY
 pw_utils_sources = pw-utils-pwquality.c pw-utils.h
 else
@@ -7,12 +9,30 @@ endif
 
 noinst_LTLIBRARIES = libgisaccount.la
 
-libgisaccount_la_SOURCES =	\
-	$(pw_utils_sources) \
-	um-utils.c um-utils.h \
-	um-photo-dialog.c um-photo-dialog.h \
-	gis-account-page.c gis-account-page.h
+BUILT_SOURCES =
+
+um-realm-generated.c: um-realm-generated.h
+um-realm-generated.h: $(srcdir)/org.freedesktop.realmd.xml
+	$(AM_V_GEN) gdbus-codegen \
+		--interface-prefix org.freedesktop.realmd \
+		--generate-c-code um-realm-generated \
+		--c-namespace UmRealm $< \
+		--c-generate-object-manager \
+		--annotate "org.freedesktop.realmd.Realm" org.gtk.GDBus.C.Name Common
+BUILT_SOURCES += um-realm-generated.c um-realm-generated.h
+
+libgisaccount_la_SOURCES =				\
+	$(pw_utils_sources)				\
+	$(BUILT_SOURCES)				\
+	gis-account-page.c gis-account-page.h		\
+	um-photo-dialog.c um-photo-dialog.h		\
+	um-realm-manager.c um-realm-manager.h		\
+	um-utils.c um-utils.h				\
+	$(NULL)
 
 libgisaccount_la_CFLAGS = $(INITIAL_SETUP_CFLAGS) -I "$(srcdir)/../.." -DHAVE_CHEESE
-libgisaccount_la_LIBADD = $(INITIAL_SETUP_LIBS)
+libgisaccount_la_LIBADD = $(INITIAL_SETUP_LIBS) -lcrypt
 libgisaccount_la_LDFLAGS = -export_dynamic -avoid-version -module -no-undefined
+
+EXTRA_DIST =	\
+	org.freedesktop.realmd.xml
diff --git a/gnome-initial-setup/pages/account/gis-account-page.c b/gnome-initial-setup/pages/account/gis-account-page.c
index 1d33cec..297d982 100644
--- a/gnome-initial-setup/pages/account/gis-account-page.c
+++ b/gnome-initial-setup/pages/account/gis-account-page.c
@@ -10,6 +10,7 @@
 
 #include <act/act-user-manager.h>
 
+#include "um-realm-manager.h"
 #include "um-utils.h"
 #include "um-photo-dialog.h"
 #include "pw-utils.h"
@@ -23,6 +24,12 @@
 
 typedef struct _AccountData AccountData;
 
+typedef enum {
+  UM_LOCAL,
+  UM_ENTERPRISE,
+  NUM_MODES,
+} UmAccountMode;
+
 struct _AccountData {
   SetupData *setup;
   GtkWidget *widget;
@@ -31,6 +38,8 @@ struct _AccountData {
   ActUser *act_user;
   ActUserManager *act_client;
 
+  UmAccountMode mode;
+
   gboolean valid_name;
   gboolean valid_username;
   gboolean valid_password;
@@ -43,18 +52,152 @@ struct _AccountData {
   GtkWidget *photo_dialog;
   GdkPixbuf *avatar_pixbuf;
   gchar *avatar_filename;
+
+  gboolean has_enterprise;
+  guint realmd_watch;
+  UmRealmManager *realm_manager;
+  gboolean domain_chosen;
 };
 
 static void
-update_account_page_status (AccountData *data)
+show_error_dialog (AccountData *data,
+                   const gchar *message,
+                   GError *error)
 {
-  gboolean complete;
+  GtkWidget *dialog;
+
+  dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (data->widget)),
+                                   GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                                   GTK_MESSAGE_ERROR,
+                                   GTK_BUTTONS_CLOSE,
+                                   "%s", message);
+
+  if (error != NULL) {
+    g_dbus_error_strip_remote_error (error);
+    gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+                                              "%s", error->message);
+  }
+
+  g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+  gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+clear_account_page (AccountData *data)
+{
+  GtkWidget *fullname_entry;
+  GtkWidget *username_combo;
+  GtkWidget *password_check;
+  GtkWidget *admin_check;
+  GtkWidget *password_entry;
+  GtkWidget *confirm_entry;
+  gboolean need_password;
 
-  complete = data->valid_name && data->valid_username &&
+  fullname_entry = WID("account-fullname-entry");
+  username_combo = WID("account-username-combo");
+  password_check = WID("account-password-check");
+  admin_check = WID("account-admin-check");
+  password_entry = WID("account-password-entry");
+  confirm_entry = WID("account-confirm-entry");
+
+  data->valid_name = FALSE;
+  data->valid_username = FALSE;
+  data->valid_password = TRUE;
+  data->password_mode = ACT_USER_PASSWORD_MODE_NONE;
+  data->account_type = ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR;
+  data->user_data_unsaved = FALSE;
+
+  need_password = data->password_mode != ACT_USER_PASSWORD_MODE_NONE;
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (password_check), need_password);
+  gtk_widget_set_sensitive (password_entry, need_password);
+  gtk_widget_set_sensitive (confirm_entry, need_password);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (admin_check), data->account_type == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR);
+
+  gtk_entry_set_text (GTK_ENTRY (fullname_entry), "");
+  gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (username_combo))));
+  gtk_entry_set_text (GTK_ENTRY (password_entry), "");
+  gtk_entry_set_text (GTK_ENTRY (confirm_entry), "");
+}
+
+static gboolean
+local_validate (AccountData *data)
+{
+  return data->valid_name && data->valid_username &&
     (data->valid_password ||
      data->password_mode == ACT_USER_PASSWORD_MODE_NONE);
+}
+
+static gboolean
+enterprise_validate (AccountData *data)
+{
+  const gchar *name;
+  gboolean valid_name;
+  gboolean valid_domain;
+  GtkTreeIter iter;
+  GtkComboBox *domain = OBJ(GtkComboBox*, "enterprise-domain");
+
+  name = gtk_entry_get_text (OBJ(GtkEntry*, "enterprise-login"));
+  valid_name = is_valid_name (name);
+
+  if (gtk_combo_box_get_active_iter (domain, &iter)) {
+    gtk_tree_model_get (gtk_combo_box_get_model (domain),
+                        &iter, 0, &name, -1);
+  } else {
+    name = gtk_entry_get_text (OBJ(GtkEntry*, "enterprise-domain-entry"));
+  }
+
+  valid_domain = is_valid_name (name);
+  return valid_name && valid_domain;
+}
+
+static gboolean
+page_validate (AccountData *data)
+{
+  switch (data->mode) {
+  case UM_LOCAL:
+    return local_validate (data);
+  case UM_ENTERPRISE:
+    return enterprise_validate (data);
+  default:
+    g_assert_not_reached ();
+  }
+}
+
+static void
+update_account_page_status (AccountData *data)
+{
+  gis_assistant_set_page_complete (gis_get_assistant (data->setup), data->widget, page_validate (data));
+}
+
+static void
+set_mode (AccountData   *data,
+          UmAccountMode  mode)
+{
+  if (data->mode == mode)
+    return;
 
-  gis_assistant_set_page_complete (gis_get_assistant (data->setup), data->widget, complete);
+  data->mode = mode;
+  gtk_widget_set_visible (WID ("local-area"), (mode == UM_LOCAL));
+  gtk_widget_set_visible (WID ("enterprise-area"), (mode == UM_ENTERPRISE));
+  gtk_toggle_button_set_active (OBJ (GtkToggleButton *, "local-button"), (mode == UM_LOCAL));
+  gtk_toggle_button_set_active (OBJ (GtkToggleButton *, "enterprise-button"), (mode == UM_ENTERPRISE));
+
+  update_account_page_status (data);
+}
+
+static void
+set_has_enterprise (AccountData *data,
+                    gboolean     has_enterprise)
+{
+  if (data->has_enterprise == has_enterprise)
+    return;
+
+  data->has_enterprise = has_enterprise;
+  gtk_widget_set_visible (WID ("account-mode"), has_enterprise);
+
+  if (!has_enterprise)
+    set_mode (data, UM_LOCAL);
 }
 
 static void
@@ -267,7 +410,7 @@ set_user_avatar (AccountData *data)
   if (!gdk_pixbuf_save_to_stream (data->avatar_pixbuf, stream, "png", NULL, &error, NULL))
     goto out;
 
-  act_user_set_icon_file (data->act_user, g_file_get_path (file)); 
+  act_user_set_icon_file (data->act_user, g_file_get_path (file));
 
  out:
   if (error != NULL) {
@@ -314,45 +457,7 @@ save_when_loaded (ActUser *user, GParamSpec *pspec, AccountData *data)
 }
 
 static void
-clear_account_page (AccountData *data)
-{
-  GtkWidget *fullname_entry;
-  GtkWidget *username_combo;
-  GtkWidget *password_check;
-  GtkWidget *admin_check;
-  GtkWidget *password_entry;
-  GtkWidget *confirm_entry;
-  gboolean need_password;
-
-  fullname_entry = WID("account-fullname-entry");
-  username_combo = WID("account-username-combo");
-  password_check = WID("account-password-check");
-  admin_check = WID("account-admin-check");
-  password_entry = WID("account-password-entry");
-  confirm_entry = WID("account-confirm-entry");
-
-  data->valid_name = FALSE;
-  data->valid_username = FALSE;
-  data->valid_password = TRUE;
-  data->password_mode = ACT_USER_PASSWORD_MODE_NONE;
-  data->account_type = ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR;
-  data->user_data_unsaved = FALSE;
-
-  need_password = data->password_mode != ACT_USER_PASSWORD_MODE_NONE;
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (password_check), need_password);
-  gtk_widget_set_sensitive (password_entry, need_password);
-  gtk_widget_set_sensitive (confirm_entry, need_password);
-
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (admin_check), data->account_type == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR);
-
-  gtk_entry_set_text (GTK_ENTRY (fullname_entry), "");
-  gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (username_combo))));
-  gtk_entry_set_text (GTK_ENTRY (password_entry), "");
-  gtk_entry_set_text (GTK_ENTRY (confirm_entry), "");
-}
-
-static void
-save_account_data (AccountData *data)
+local_create_user (AccountData *data)
 {
   const gchar *realname;
   const gchar *username;
@@ -406,6 +511,240 @@ save_account_data (AccountData *data)
 }
 
 static void
+on_permit_user_login (GObject *source,
+                      GAsyncResult *result,
+                      gpointer user_data)
+{
+  AccountData *data = user_data;
+  UmRealmCommon *common;
+  GError *error = NULL;
+  gchar *login;
+
+  common = UM_REALM_COMMON (source);
+  um_realm_common_call_change_login_policy_finish (common, result, &error);
+  if (error == NULL) {
+
+    /*
+     * Now tell the account service about this user. The account service
+     * should also lookup information about this via the realm and make
+     * sure all that is functional.
+     */
+    login = um_realm_calculate_login (common, gtk_entry_get_text (OBJ (GtkEntry*, "enterprise-login")));
+    g_return_if_fail (login != NULL);
+
+    g_debug ("Caching remote user: %s", login);
+
+    data->act_user = act_user_manager_cache_user (data->act_client, login, NULL);
+
+    g_free (login);
+  } else {
+    show_error_dialog (data, _("Failed to register account"), error);
+    g_message ("Couldn't permit logins on account: %s", error->message);
+    g_error_free (error);
+  }
+}
+
+static void
+enterprise_permit_user_login (AccountData *data, UmRealmObject *realm)
+{
+  UmRealmCommon *common;
+  gchar *login;
+  const gchar *add[2];
+  const gchar *remove[1];
+  GVariant *options;
+
+  common = um_realm_object_get_common (realm);
+
+  login = um_realm_calculate_login (common, gtk_entry_get_text (OBJ (GtkEntry *, "enterprise-login")));
+  g_return_if_fail (login != NULL);
+
+  add[0] = login;
+  add[1] = NULL;
+  remove[0] = NULL;
+
+  g_debug ("Permitting login for: %s", login);
+  options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
+
+  um_realm_common_call_change_login_policy (common, "",
+                                            add, remove, options,
+                                            NULL,
+                                            on_permit_user_login,
+                                            data);
+
+  g_object_unref (common);
+  g_free (login);
+}
+
+static void
+on_realm_joined (GObject *source,
+                 GAsyncResult *result,
+                 gpointer user_data)
+{
+  AccountData *data = user_data;
+  UmRealmObject *realm = UM_REALM_OBJECT (source);
+  GError *error = NULL;
+
+  um_realm_join_finish (realm, result, &error);
+
+  /* Yay, joined the domain, register the user locally */
+  if (error == NULL) {
+    g_debug ("Joining realm completed successfully");
+    enterprise_permit_user_login (data, realm);
+
+    /* Credential failure while joining domain, prompt for admin creds */
+  } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN) ||
+             g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD)) {
+    g_debug ("Joining realm failed due to credentials");
+
+    /* XXX */
+    /* join_show_prompt (self, error); */
+
+    /* Other failure */
+  } else {
+    show_error_dialog (data, _("Failed to join domain"), error);
+    g_message ("Failed to join the domain: %s", error->message);
+  }
+
+  g_clear_error (&error);
+}
+
+static void
+on_realm_login (GObject *source,
+                GAsyncResult *result,
+                gpointer user_data)
+{
+  AccountData *data = user_data;
+  UmRealmObject *realm = UM_REALM_OBJECT (source);
+  GError *error = NULL;
+  GBytes *creds;
+
+  um_realm_login_finish (result, &creds, &error);
+  if (error == NULL) {
+
+    /* Already joined to the domain, just register this user */
+    if (um_realm_is_configured (realm)) {
+      g_debug ("Already joined to this realm");
+      enterprise_permit_user_login (data, realm);
+
+      /* Join the domain, try using the user's creds */
+    } else if (!um_realm_join_as_user (realm,
+                                       gtk_entry_get_text (OBJ (GtkEntry *, "enterprise-login")),
+                                       gtk_entry_get_text (OBJ (GtkEntry *, "enterprise-password")),
+                                       creds, NULL,
+                                       on_realm_joined,
+                                       data)) {
+
+      /* If we can't do user auth, try to authenticate as admin */
+      g_debug ("Cannot join with user credentials");
+
+      /* XXX: creds */
+      /* join_show_prompt (self, NULL); */
+    }
+
+    g_bytes_unref (creds);
+
+    /* A problem with the user's login name or password */
+  } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN)) {
+    g_debug ("Problem with the user's login: %s", error->message);
+    set_entry_validation_error (OBJ (GtkEntry *, "enterprise-login"), error->message);
+    gtk_widget_grab_focus (WID ("enterprise-login"));
+
+  } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD)) {
+    g_debug ("Problem with the user's password: %s", error->message);
+    set_entry_validation_error (OBJ (GtkEntry *, "enterprise-password"), error->message);
+    gtk_widget_grab_focus (WID ("enterprise-password"));
+
+    /* Other login failure */
+  } else {
+    show_error_dialog (data, _("Failed to log into domain"), error);
+    g_message ("Couldn't log in as user: %s", error->message);
+  }
+
+  g_clear_error (&error);
+}
+
+static void
+enterprise_check_login (AccountData *data, UmRealmObject *realm)
+{
+  g_assert (realm);
+
+  um_realm_login (realm,
+                  gtk_entry_get_text (OBJ (GtkEntry *, "enterprise-login")),
+                  gtk_entry_get_text (OBJ (GtkEntry *, "enterprise-password")),
+                  NULL,
+                  on_realm_login,
+                  data);
+}
+
+static void
+on_realm_discover_input (GObject *source,
+                         GAsyncResult *result,
+                         gpointer user_data)
+{
+  AccountData *data = user_data;
+  GError *error = NULL;
+  GList *realms;
+
+  realms = um_realm_manager_discover_finish (data->realm_manager,
+                                             result, &error);
+
+  /* Found a realm, log user into domain */
+  if (error == NULL) {
+    UmRealmObject *realm;
+    g_assert (realms != NULL);
+    realm = g_object_ref (realms->data);
+    enterprise_check_login (data, realm);
+    g_list_free_full (realms, g_object_unref);
+
+  } else {
+    /* The domain is likely invalid */
+    g_dbus_error_strip_remote_error (error);
+    g_message ("Couldn't discover domain: %s", error->message);
+    gtk_widget_grab_focus (WID ("enterprise-domain-entry"));
+    set_entry_validation_error (OBJ (GtkEntry*, "enterprise-domain-entry"), error->message);
+    g_error_free (error);
+  }
+}
+
+static void
+enterprise_add_user (AccountData *data)
+{
+  UmRealmObject *realm;
+  GtkTreeIter iter;
+  GtkComboBox *domain = OBJ(GtkComboBox*, "enterprise-domain");
+
+  /* Already know about this realm, try to login as user */
+  if (gtk_combo_box_get_active_iter (domain, &iter)) {
+    gtk_tree_model_get (gtk_combo_box_get_model (domain),
+                        &iter, 1, &realm, -1);
+    enterprise_check_login (data, realm);
+
+    /* Something the user typed, we need to discover realm */
+  } else {
+    um_realm_manager_discover (data->realm_manager,
+                               gtk_entry_get_text (OBJ (GtkEntry*, "enterprise-domain-entry")),
+                               NULL,
+                               on_realm_discover_input,
+                               data);
+  }
+}
+
+static void
+save_account_data (AccountData *data)
+{
+  switch (data->mode) {
+  case UM_LOCAL:
+    local_create_user (data);
+    break;
+  case UM_ENTERPRISE:
+    enterprise_add_user (data);
+    break;
+  default:
+    g_assert_not_reached ();
+  }
+}
+
+static void
 avatar_callback (GdkPixbuf   *pixbuf,
                  const gchar *filename,
                  gpointer     user_data)
@@ -439,6 +778,139 @@ avatar_callback (GdkPixbuf   *pixbuf,
 }
 
 static void
+enterprise_add_realm (AccountData *data,
+                      UmRealmObject *realm)
+{
+  GtkTreeIter iter;
+  UmRealmCommon *common;
+  GtkComboBox *domain = OBJ(GtkComboBox*, "enterprise-domain");
+  GtkListStore *model = OBJ(GtkListStore*, "enterprise-realms-model");
+
+  g_debug ("Adding new realm to drop down: %s",
+           g_dbus_object_get_object_path (G_DBUS_OBJECT (realm)));
+
+  common = um_realm_object_get_common (realm);
+
+  gtk_list_store_append (model, &iter);
+  gtk_list_store_set (model, &iter,
+                      0, um_realm_common_get_name (common),
+                      1, realm,
+                      -1);
+
+  if (!data->domain_chosen && um_realm_is_configured (realm))
+    gtk_combo_box_set_active_iter (domain, &iter);
+
+  g_object_unref (common);
+}
+
+static void
+on_manager_realm_added (UmRealmManager  *manager,
+                        UmRealmObject   *realm,
+                        gpointer         user_data)
+{
+  AccountData *data = user_data;
+  enterprise_add_realm (data, realm);
+}
+
+static void
+on_realm_manager_created (GObject *source,
+                          GAsyncResult *result,
+                          gpointer user_data)
+{
+  AccountData *data = user_data;
+  GError *error = NULL;
+  GList *realms, *l;
+
+  g_clear_object (&data->realm_manager);
+  data->realm_manager = um_realm_manager_new_finish (result, &error);
+
+  if (error != NULL) {
+    g_warning ("Couldn't contact realmd service: %s", error->message);
+    g_error_free (error);
+    return;
+  }
+
+  /* Lookup all the realm objects */
+  realms = um_realm_manager_get_realms (data->realm_manager);
+  for (l = realms; l != NULL; l = g_list_next (l))
+    enterprise_add_realm (data, l->data);
+
+  g_list_free (realms);
+  g_signal_connect (data->realm_manager, "realm-added",
+                    G_CALLBACK (on_manager_realm_added), data);
+
+  /* When no realms try to discover a sensible default, triggers realm-added signal */
+  um_realm_manager_discover (data->realm_manager, "", NULL, NULL, NULL);
+  set_has_enterprise (data, TRUE);
+}
+
+static void
+on_realmd_appeared (GDBusConnection *connection,
+                    const gchar *name,
+                    const gchar *name_owner,
+                    gpointer user_data)
+{
+  AccountData *data = user_data;
+  um_realm_manager_new (NULL, on_realm_manager_created, data);
+}
+
+static void
+on_realmd_disappeared (GDBusConnection *unused1,
+                       const gchar *unused2,
+                       gpointer user_data)
+{
+  AccountData *data = user_data;
+
+  if (data->realm_manager != NULL) {
+    g_signal_handlers_disconnect_by_func (data->realm_manager,
+                                          on_manager_realm_added,
+                                          data);
+    g_clear_object (&data->realm_manager);
+  }
+
+  set_has_enterprise (data, FALSE);
+}
+
+static void
+on_domain_changed (GtkComboBox *widget,
+                   gpointer user_data)
+{
+  AccountData *data = user_data;
+  data->domain_chosen = TRUE;
+  update_account_page_status (data);
+  clear_entry_validation_error (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (widget))));
+}
+
+static void
+on_entry_changed (GtkEditable *editable,
+                  gpointer user_data)
+{
+  AccountData *data = user_data;
+  update_account_page_status (data);
+  clear_entry_validation_error (GTK_ENTRY (editable));
+}
+
+static void
+on_local_toggle (GtkToggleButton *toggle,
+                 gpointer         user_data)
+{
+  AccountData *data = user_data;
+  if (gtk_toggle_button_get_active (toggle)) {
+    set_mode (data, UM_LOCAL);
+  }
+}
+
+static void
+on_enterprise_toggle (GtkToggleButton *toggle,
+                      gpointer         user_data)
+{
+  AccountData *data = user_data;
+  if (gtk_toggle_button_get_active (toggle)) {
+    set_mode (data, UM_ENTERPRISE);
+  }
+}
+
+static void
 next_page_cb (GisAssistant *assistant, GtkWidget *page, AccountData *data)
 {
   if (page == data->widget)
@@ -463,6 +935,11 @@ gis_prepare_account_page (SetupData *setup)
 
   gtk_widget_show (data->widget);
 
+  data->realmd_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM, "org.freedesktop.realmd",
+                                         G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
+                                         on_realmd_appeared, on_realmd_disappeared,
+                                         data, NULL);
+
   fullname_entry = WID("account-fullname-entry");
   username_combo = WID("account-username-combo");
   password_check = WID("account-password-check");
@@ -489,6 +966,15 @@ gis_prepare_account_page (SetupData *setup)
   g_signal_connect_after (confirm_entry, "focus-out-event",
                           G_CALLBACK (confirm_entry_focus_out), data);
 
+  g_signal_connect (WID("enterprise-domain"), "changed",
+                    G_CALLBACK (on_domain_changed), data);
+  g_signal_connect (WID("enterprise-login"), "changed",
+                    G_CALLBACK (on_entry_changed), data);
+  g_signal_connect (WID("local-button"), "toggled",
+                    G_CALLBACK (on_local_toggle), data);
+  g_signal_connect (WID("enterprise-button"), "toggled",
+                    G_CALLBACK (on_enterprise_toggle), data);
+
   data->act_client = act_user_manager_get_default ();
 
   gis_assistant_add_page (assistant, data->widget);
@@ -498,4 +984,10 @@ gis_prepare_account_page (SetupData *setup)
 
   clear_account_page (data);
   update_account_page_status (data);
+
+  data->has_enterprise = FALSE;
+
+  /* force a refresh by setting to an invalid value */
+  data->mode = NUM_MODES;
+  set_mode (data, UM_LOCAL);
 }
diff --git a/gnome-initial-setup/pages/account/gis-account-page.ui b/gnome-initial-setup/pages/account/gis-account-page.ui
index d06d22e..8c42dc9 100644
--- a/gnome-initial-setup/pages/account/gis-account-page.ui
+++ b/gnome-initial-setup/pages/account/gis-account-page.ui
@@ -6,6 +6,12 @@
       <column type="gchararray"/>
     </columns>
   </object>
+  <object class="GtkListStore" id="enterprise-realms-model">
+    <columns>
+      <column type="gchararray"/>
+      <column type="GObject"/>
+    </columns>
+  </object>
   <object class="GtkBox" id="account-page">
     <property name="name">account-page</property>
     <property name="visible">True</property>
@@ -25,8 +31,52 @@
         </attributes>
       </object>
     </child>
+
+    <child>
+      <object class="GtkBox" id="account-mode">
+        <property name="visible">False</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <style>
+          <class name="linked"/>
+        </style>
+        <child>
+          <object class="GtkToggleButton" id="local-button">
+            <property name="label" translatable="yes">_Local Account</property>
+            <property name="use_action_appearance">False</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_action_appearance">False</property>
+            <property name="focus_on_click">False</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkToggleButton" id="enterprise-button">
+            <property name="label" translatable="yes">_Enterprise Login</property>
+            <property name="use_action_appearance">False</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_action_appearance">False</property>
+            <property name="focus_on_click">False</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+
     <child>
-      <object class="GtkBox" id="local-account-content-area">
+      <object class="GtkBox" id="local-area">
         <property name="visible">True</property>
         <property name="margin">12</property>
         <child>
@@ -213,5 +263,155 @@
         </child>
       </object>
     </child>
+
+    <child>
+      <object class="GtkGrid" id="enterprise-area">
+        <property name="visible">False</property>
+        <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+        <property name="row_spacing">10</property>
+        <property name="column_spacing">10</property>
+        <child>
+          <object class="GtkLabel" id="label4">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_top">5</property>
+            <property name="xalign">1</property>
+            <property name="yalign">0</property>
+            <property name="label" translatable="yes">_Domain</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">enterprise-domain</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label8">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">1</property>
+            <property name="label" translatable="yes">_Login Name</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">enterprise-login</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">1</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label9">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">1</property>
+            <property name="label" translatable="yes">_Password</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">enterprise-password</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">2</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">3</property>
+            <child>
+              <object class="GtkComboBox" id="enterprise-domain">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">True</property>
+                <property name="has_entry">True</property>
+                <property name="entry_text_column">0</property>
+                <property name="model">enterprise-realms-model</property>
+                <child internal-child="entry">
+                  <object class="GtkEntry" id="enterprise-domain-entry">
+                    <property name="can_focus">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label10">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Tip: Enterprise domain or realm name</property>
+                <attributes>
+                  <attribute name="scale" value="0.8"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="enterprise-login">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hexpand">True</property>
+            <property name="invisible_char">â</property>
+            <property name="invisible_char_set">True</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">1</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="enterprise-password">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hexpand">True</property>
+            <property name="visibility">False</property>
+            <property name="invisible_char">â</property>
+            <property name="activates_default">True</property>
+            <property name="invisible_char_set">True</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">2</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+
   </object>
 </interface>
diff --git a/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml b/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml
new file mode 100644
index 0000000..316213a
--- /dev/null
+++ b/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml
@@ -0,0 +1,666 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<node name="/">
+
+	<!--
+	  org.freedesktop.realmd.Provider:
+	  @short_description: a realm provider
+
+	  Various realm providers represent different software implementations
+	  that provide access to realms or domains.
+
+	  This interface is implemented by individual providers, but is
+	  aggregated globally at the system bus name
+	  <literal>org.freedesktop.realmd</literal>
+	  with the object path <literal>/org/freedesktop/realmd</literal>
+	-->
+	<interface name="org.freedesktop.realmd.Provider">
+
+		<!--
+		  Name: the name of the provider
+
+		  The name of the provider. This is not normally displayed
+		  to the user, but may be useful for diagnostics or debugging.
+		-->
+		<property name="Name" type="s" access="read"/>
+
+		<!--
+		  Version: the version of the provider
+
+		  The version of the provider. This is not normally used in
+		  logic, but may be useful for diagnostics or debugging.
+		-->
+		<property name="Version" type="s" access="read"/>
+
+		<!--
+		  Realms: a list of realms
+
+		  A list of known, enrolled or discovered realms. All realms
+		  that this provider knows about are listed here. As realms
+		  are discovered they are added to this list.
+
+		  Each realm is represented by the DBus object path of the
+		  realm object.
+		-->
+		<property name="Realms" type="ao" access="read"/>
+
+		<!--
+		  Discover:
+		  @string: an input string to discover realms for
+		  @options: options for the discovery operation
+		  @relevance: the relevance of the returned results
+		  @realm: a list of realms discovered
+
+		  Discover realms for the given string. The input @string is
+		  usually a domain or realm name, perhaps typed by a user. If
+		  an empty string is provided the realm provider should try to
+		  discover a default realm if possible (eg: from DHCP).
+
+		  @options can contain, but is not limited to, the following values:
+		  <itemizedlist>
+		    <listitem><para><literal>operation</literal>: a string
+		      identifier chosen by the client, which can then later be
+		      passed to org.freedesktop.realmd.Service.Cancel() in order
+		      to cancel the operation</para></listitem>
+		  </itemizedlist>
+
+		  The @relevance returned can be used to rank results from
+		  different discover calls to different providers. Implementors
+		  should return a positive number if the provider highly
+		  recommends that the realms be handled by this provider,
+		  or a zero if it can possibly handle the realms. Negative
+		  should be returned if no realms are found.
+
+		  This method does not return an error when no realms are
+		  discovered. It simply returns an @realm list.
+
+		  To see diagnostic information about the discovery process
+		  connect to the org.freedesktop.realmd.Service::Diagnostics
+		  signal.
+
+		  This method requires authorization for the PolicyKit action
+		  called <literal>org.freedesktop.realmd.discover-realm</literal>.
+
+		  In addition to common DBus error results, this method may
+		  return:
+		  <itemizedlist>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+		      may be returned if the discovery could not be run for some reason.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+		      returned if the operation was cancelled.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+		      returned if the calling client is not permitted to perform a discovery
+		      operation.</para></listitem>
+		  </itemizedlist>
+		-->
+		<method name="Discover">
+			<arg name="string" type="s" direction="in"/>
+			<arg name="options" type="a{sv}" direction="in"/>
+			<arg name="relevance" type="i" direction="out"/>
+			<arg name="realm" type="ao" direction="out"/>
+		</method>
+
+	</interface>
+
+	<!--
+	  org.freedesktop.realmd.Service:
+	  @short_description: the realmd service
+
+	  Global calls for managing the realmd service. Usually you'll want
+	  to use #org.freedesktop.realmd.Provider instead.
+
+	  This interface is implemented by the realmd service, and is always
+	  available at the object path <literal>/org/freedesktop/realmd</literal>
+
+	  The service also implements the
+	  <literal>org.freedesktop.DBus.ObjectManager</literal> interface which
+	  makes it easy to retrieve all realmd objects and properties in one go.
+	-->
+	<interface name="org.freedesktop.realmd.Service">
+
+		<!--
+		  Cancel:
+		  @operation: the operation to cancel
+
+		  Cancel a realmd operation. To be able to cancel an operation
+		  pass a uniquely chosen <literal>operation</literal> string
+		  identifier as an option in the methods <literal>options</literal>
+		  argument.
+
+		  These operation string identifiers should be unique per client
+		  calling the realmd service.
+
+		  It is not guaranteed that the service can or will cancel the
+		  operation. For example the operation may have already completed
+		  by the time this method is handled. The caller of the operation
+		  method will receive a
+		  <literal>org.freedesktop.realmd.Error.Cancelled</literal>
+		  if the operation was cancelled.
+		-->
+		<method name="Cancel">
+			<arg name="operation" type="s" direction="in"/>
+		</method>
+
+		<!--
+		  SetLocale:
+		  @locale: the locale for the client
+
+		  Set the language @locale for the client. This locale is used
+		  for error messages. The locale is used until the next time
+		  this method is called, the client disconnects, or the client
+		  calls #org.freedesktop.realmd.Service.Release().
+		-->
+		<method name="SetLocale">
+			<arg name="locale" type="s" direction="in"/>
+		</method>
+
+		<!--
+		  Diagnostics:
+		  @data: diagnostic data
+		  @operation: the operation this data resulted from
+
+		  This signal is fired when diagnostics result from an operation
+		  in the provider or one of its realms.
+
+		  It is not guaranteed that this signal is emitted once per line.
+		  More than one line may be contained in @data, or a partial
+		  line. New line characters are embedded in @data.
+
+		  This signal is sent explicitly to the client which invoked
+		  operation method. In order to tell which operation this
+		  diagnostic data results from, pass a unique
+		  <literal>operation</literal> string identifier in the
+		  <literal>options</literal> argument of the operation method.
+		  That same identifier will be passed back via the @operation
+		  argument of this signal.
+		-->
+		<signal name="Diagnostics">
+			<arg name="data" type="s"/>
+			<arg name="operation" type="s"/>
+		</signal>
+
+		<!--
+		  Release:
+
+		  Normally realmd waits until all clients have disconnected
+		  before exiting itself, sometime later. For long lived clients
+		  they can call this method to allow the realmd service to quit.
+		  This is an optimization. The daemon will not exit immediately.
+		  It is safe to call this multiple times.
+		-->
+		<method name="Release">
+			<!-- no arguments -->
+		</method>
+
+	</interface>
+
+	<!--
+	  org.freedesktop.realmd.Realm:
+	  @short_description: a realm
+
+	  Represents one realm.
+
+	  Contains generic information about a realm, and useful properties for
+	  introspecting what kind of realm this is and how to work with
+	  the realm.
+
+	  Use #org.freedesktop.realmd.Provider:Realms or
+	  #org.freedesktop.realmd.Provider.Discover() to get access to some
+	  kerberos realm objects.
+
+	  Realms will always implement additional interfaces, such as
+	  #org.freedesktop.realmd.Kerberos.  Do not assume that all realms
+	  implement that kerberos interface. Use the
+	  #org.freedesktop.realmd.Realm:SupportedInterfaces property to see
+	  which interfaces are set.
+
+	  Different realms support various ways to configure them on the
+	  system. Use the #org.freedesktop.realmd.Realm:Configured property
+	  to determine if a realm is configured. If it is configured the
+	  property will be set to the interface of the mechanism that was
+	  used to configure it.
+
+	  To configure a realm, look in the
+	  #org.freedesktop.realmd.Realm:SupportedInterfaces property for a
+	  recognized purpose specific interface that can be used for
+	  configuration, such as the
+	  #org.freedesktop.realmd.KerberosMembership interface and its
+	  #org.freedesktop.realmd.KerberosMembership.Join() method.
+
+	  To deconfigure a realm from the current system, you can use the
+	  #org.freedesktop.realmd.Realm.Deconfigure() method. In additon some
+	  of the configuration specific interfaces provide methods to
+	  deconfigure a realm in a specific way, such as
+	  #org.freedesktop.realmd.KerberosMembership.Leave() method.
+
+	  The various properties are guaranteed to have been updated before
+	  the operation methods return, if they change state.
+	-->
+	<interface name="org.freedesktop.realmd.Realm">
+
+		<!--
+		  Name: the realm name
+
+		  This is the name of the realm, appropriate for display to
+		  end users where necessary.
+		-->
+		<property name="Name" type="s" access="read"/>
+
+		<!--
+		  Configured: whether this domain is configured and how
+
+		  If this property is an empty string, then the realm is not
+		  configured. Otherwise the realm is configured, and contains
+		  a string which is the interface that represents how it was
+		  configured, for example #org.freedesktop.realmd.KerberosMembership.
+		-->
+		<property name="Configured" type="s" access="read"/>
+
+		<!--
+		  Deconfigure: deconfigure this realm
+
+		  Deconfigure this realm from the local machine with standard
+		  default behavior.
+
+		  The behavior of this method depends on the which configuration
+		  interface is present in the
+		  #org.freedesktop.realmd.Realm.Configured property. It does not
+		  always delete membership accounts in the realm, but just
+		  reconfigures the local machine so it no longer is configured
+		  for the given realm. In some cases the implementation may try
+		  to update membership accounts, but this is not guaranteed.
+
+		  Various configuration interfaces may support more specific ways
+		  to deconfigure a realm in a specific way, such as the
+		  #org.freedesktop.realmd.KerberosMembership.Leave() method.
+
+		  @options can contain, but is not limited to, the following values:
+		  <itemizedlist>
+		    <listitem><para><literal>operation</literal>: a string
+		      identifier chosen by the client, which can then later be
+		      passed to org.freedesktop.realmd.Service.Cancel() in order
+		      to cancel the operation</para></listitem>
+		  </itemizedlist>
+
+		  This method requires authorization for the PolicyKit action
+		  called <literal>org.freedesktop.realmd.deconfigure-realm</literal>.
+
+		  In addition to common DBus error results, this method may return:
+		  <itemizedlist>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+		      may be returned if the deconfigure failed for a generic reason.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+		      returned if the operation was cancelled.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+		      returned if the calling client is not permitted to deconfigure a
+		      realm.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>:
+		      returned if this realm is not configured on the machine.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
+		      returned if the service is currently performing another operation like
+		      join or leave.</para></listitem>
+		  </itemizedlist>
+		-->
+		<method name="Deconfigure">
+			<arg name="options" type="a{sv}" direction="in"/>
+		</method>
+
+		<!--
+		  SupportedInterfaces:
+
+		  Additional supported interfaces of this realm. This includes
+		  interfaces that contain more information about the realm,
+		  such as #org.freedesktop.realmd.Kerberos and interfaces
+		  which contain methods for configuring a realm, such as
+		  #org.freedesktop.realmd.KerberosMembership.
+		-->
+		<property name="SupportedInterfaces" type="as" access="read"/>
+
+		<!--
+		  Details: informational details about the realm
+
+		  Informational details about the realm. The following values
+		  should be present:
+		  <itemizedlist>
+		    <listitem><para><literal>server-software</literal>:
+		      identifier of the software running on the server (eg:
+		      <literal>active-directory</literal>).</para></listitem>
+		    <listitem><para><literal>client-software</literal>:
+		      identifier of the software running on the client (eg:
+		      <literal>sssd</literal>).</para></listitem>
+		  </itemizedlist>
+		-->
+		<property name="Details" type="a(ss)" access="read"/>
+
+		<!--
+		  LoginFormats: supported formats for login names
+
+		  Supported formats for login to this realm. This is only
+		  relevant once the realm has been enrolled. The formats
+		  will contain a <literal>%U</literal> in the string, which
+		  indicate where the user name should be placed. The formats
+		  may contain a <literal>%D</literal> in the string which
+		  indicate where a domain name should be placed.
+
+		  The first format in the list is the preferred format for
+		  login names.
+		-->
+		<property name="LoginFormats" type="as" access="read"/>
+
+		<!--
+		  LoginPolicy: the policy for logins using this realm
+
+		  The policy for logging into this computer using this realm.
+
+		  The policy can be changed using the
+		  #org.freedesktop.realmd.Realm.ChangeLoginPolicy() method.
+
+		  The following policies are predefined. Not all providers
+		  support all these policies and there may be provider specific
+		  policies or multiple policies represented in the string:
+		  <itemizedlist>
+		    <listitem><para><literal>allow-any-login</literal>: allow
+		      login by any authenticated user present in this
+		      realm.</para></listitem>
+		    <listitem><para><literal>allow-permitted-logins</literal>:
+		      only allow the logins permitted in the
+		      #org.freedesktop.realmd.Realm:PermittedLogins
+		      property.</para></listitem>
+		    <listitem><para><literal>deny-any-login</literal>:
+		      don't allow any logins via authenticated users of this
+		      realm.</para></listitem>
+		  </itemizedlist>
+		-->
+		<property name="LoginPolicy" type="s" access="read"/>
+
+		<!--
+		  PermittedLogins: the permitted login names
+
+		  The list of permitted authenticated users allowed to login
+		  into this computer. This is only relevant if the
+		  #org.freedesktop.realmd.Realm:LoginPolicy property
+		  contains the <literal>allow-permitted-logins</literal>
+		  string.
+		-->
+		<property name="PermittedLogins" type="as" access="read"/>
+
+		<!--
+		  ChangeLoginPolicy:
+		  @login_policy: the new login policy, or an empty string
+		  @permitted_add: a list of logins to permit
+		  @permitted_remove: a list of logins to not permit
+		  @options: options for this operation
+
+		  Change the login policy and/or permitted logins for this realm.
+
+		  Not all realms support the all the various login policies. An
+		  error will be returned if the new login policy is not supported.
+		  You may specify an empty string for the @login_policy argument
+		  which will cause no change in the policy itself. If the policy
+		  is changed, it will be reflected in the
+		  #org.freedesktop.realmd.Realm:LoginPolicy property.
+
+		  The @permitted_add and @permitted_remove arguments represent
+		  lists of login names that should be added and removed from
+		  the #org.freedesktop.realmd.Kerberos:PermittedLogins property.
+
+		  @options can contain, but is not limited to, the following values:
+		  <itemizedlist>
+		    <listitem><para><literal>operation</literal>: a string
+		      identifier chosen by the client, which can then later be
+		      passed to org.freedesktop.realmd.Service.Cancel() in order
+		      to cancel the operation</para></listitem>
+		  </itemizedlist>
+
+		  This method requires authorization for the PolicyKit action
+		  called <literal>org.freedesktop.realmd.login-policy</literal>.
+
+		  In addition to common DBus error results, this method may return:
+		  <itemizedlist>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+		      may be returned if the policy change failed for a generic reason.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+		      returned if the operation was cancelled.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+		      returned if the calling client is not permitted to change login policy
+		      operation.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>:
+		      returned if the realm is not configured.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
+		      returned if the service is currently performing another operation like
+		      join or leave.</para></listitem>
+		  </itemizedlist>
+		-->
+		<method name="ChangeLoginPolicy">
+			<arg name="login_policy" type="s" direction="in"/>
+			<arg name="permitted_add" type="as" direction="in"/>
+			<arg name="permitted_remove" type="as" direction="in"/>
+			<arg name="options" type="a{sv}" direction="in"/>
+		</method>
+
+	</interface>
+
+	<!--
+	  org.freedesktop.realmd.Kerberos:
+	  @short_description: a kerberos realm
+
+	  An interface that describes a kerberos realm in more detail. This
+	  is always implemented on an DBus object path that also implements
+	  the #org.freedesktop.realmd.Realm interface.
+	-->
+	<interface name="org.freedesktop.realmd.Kerberos">
+
+		<!--
+		  RealmName: the kerberos realm name
+
+		  The kerberos name for this realm. This is usually in upper
+		  case.
+		-->
+		<property name="RealmName" type="s" access="read"/>
+
+		<!--
+		  DomainName: the DNS domain name
+
+		  The DNS domain name for this realm.
+		-->
+		<property name="DomainName" type="s" access="read"/>
+
+	</interface>
+
+	<!--
+	  org.freedesktop.realmd.KerberosMembership:
+
+	  An interface used to configure this machine by joining a realm.
+
+	  It sets up a computer/host account in the realm for this machine
+	  and a keytab to track the credentials for that account.
+
+	  The various properties are guaranteed to have been updated before
+	  the operation methods return, if they change state.
+	-->
+	<interface name="org.freedesktop.realmd.KerberosMembership">
+
+		<!--
+		  SuggestedAdministrator: common administrator name
+
+		  The common administrator name for this type of realm. This
+		  can be used by clients as a hint when prompting the user for
+		  administrative authentication.
+		-->
+		<property name="SuggestedAdministrator" type="s" access="read"/>
+
+		<!--
+		  SupportedJoinCredentials: credentials supported for joining
+
+		  Various kinds of credentials that are supported when calling the
+		  #org.freedesktop.realmd.Kerberos.Join() method.
+
+		  Each credential is represented by a type, and an owner. The type
+		  denotes which kind of credential is passed to the method. The
+		  owner indicates to the client how to prompt the user or obtain
+		  the credential, and to the service how to use the credential.
+
+		  The various types are:
+		  <itemizedlist>
+		    <listitem><para><literal>ccache</literal>:
+		      the credentials should contain an array of bytes as a
+		      <literal>ay</literal> containing the data from a kerberos
+		      credential cache file.</para></listitem>
+		    <listitem><para><literal>password</literal>:
+		      the credentials should contain a pair of strings as a
+		      <literal>(ss)</literal> representing a name and
+		      password. The name may contain a realm in the standard
+		      kerberos format. If missing, it will default to this
+		      realm. The name may be empty for a computer or one time
+		      password.</para></listitem>
+		    <listitem><para><literal>automatic</literal>:
+		      the credentials should contain an empty string as a
+		      <literal>s</literal>. Using <literal>automatic</literal>
+		      indicates that default or system credentials are to be
+		      used.</para></listitem>
+		  </itemizedlist>
+
+		  The various owners are:
+		  <itemizedlist>
+		    <listitem><para><literal>administrator</literal>:
+		      the credentials belong to a kerberos user principal.
+		      The caller may use this as a hint to prompt the user
+		      for administrative credentials.</para></listitem>
+		    <listitem><para><literal>user</literal>:
+		      the credentials belong to a kerberos user principal.
+		      The caller may use this as a hint to prompt the user
+		      for his (possibly non-administrative)
+		      credentials.</para></listitem>
+		    <listitem><para><literal>computer</literal>:
+		      the credentials belong to the computer realmd is
+		      being run on.</para></listitem>
+		    <listitem><para><literal>secret</literal>:
+		      the credentials are a one time password or other secret
+		      used to join or leave the computer.</para></listitem>
+		  </itemizedlist>
+		-->
+		<property name="SupportedJoinCredentials" type="a(ss)" access="read"/>
+
+		<!--
+		  SupportedLeaveCredentials: credentials supported for leaving
+
+		  Various kinds of credentials that are supported when calling the
+		  #org.freedesktop.realmd.Kerberos.Leave() method.
+
+		  See #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials for
+		  a discussion of what the values represent.
+		-->
+		<property name="SupportedLeaveCredentials" type="a(ss)" access="read"/>
+
+		<!--
+		  Join:
+
+		  Join this machine to the realm and enroll the machine.
+
+		  If this method returns successfully then the machine will be
+		  joined to the realm. It is not necessary to restart services or the
+		  machine afterward. Relevant properties on the realm will be updated
+		  before the method returns.
+
+		  The @credentials should be set according to one of the
+		  supported credentials returned by
+		  #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials.
+		  The first string in the tuple is the type, the second string
+		  is the owner, and the variant contains the credential contents
+		  See the discussion at
+		  #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials
+		  for more information.
+
+		  @options can contain, but is not limited to, the following values:
+		  <itemizedlist>
+		    <listitem><para><literal>operation</literal>: a string
+		      identifier chosen by the client, which can then later be
+		      passed to org.freedesktop.realmd.Service.Cancel() in order
+		      to cancel the operation</para></listitem>
+		  </itemizedlist>
+
+		  This method requires authorization for the PolicyKit action
+		  called <literal>org.freedesktop.realmd.configure-realm</literal>.
+
+		  In addition to common DBus error results, this method may return:
+		  <itemizedlist>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+		      may be returned if the join failed for a generic reason.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+		      returned if the operation was cancelled.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+		      returned if the calling client is not permitted to perform an join
+		      operation.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>:
+		      returned if the credentials passed did not authenticate against the realm
+		      correctly. It is appropriate to prompt the user again.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.AlreadyEnrolled</literal>:
+		      returned if already enrolled in this realm, or another realm and enrolling
+		      in multiple realms is not supported.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
+		      returned if the service is currently performing another operation like
+		      join or leave.</para></listitem>
+		  </itemizedlist>
+		-->
+		<method name="Join">
+			<arg name="credentials" type="(ssv)" direction="in"/>
+			<arg name="options" type="a{sv}" direction="in"/>
+		</method>
+
+		<!--
+		  Leave:
+
+		  Leave the realm and unenroll the machine.
+
+		  If this method returns successfully then the machine will have
+		  left the domain and been unenrolled. It is not necessary to restart
+		  services or the machine afterward. Relevant properties on the realm
+		  will be updated before the method returns.
+
+		  The @credentials should be set according to one of the
+		  supported credentials returned by
+		  #org.freedesktop.realmd.Kerberos:SupportedUnenrollCredentials.
+		  The first string in the tuple is the type, the second string
+		  is the owner, and the variant contains the credential contents
+		  See the discussion at
+		  #org.freedesktop.realmd.Kerberos:SupportedEnrollCredentials
+		  for more information.
+
+		  @options can contain, but is not limited to, the following values:
+		  <itemizedlist>
+		    <listitem><para><literal>operation</literal>: a string
+		      identifier chosen by the client, which can then later be
+		      passed to org.freedesktop.realmd.Service.Cancel() in order
+		      to cancel the operation</para></listitem>
+		  </itemizedlist>
+
+		  This method requires authorization for the PolicyKit action
+		  called <literal>org.freedesktop.realmd.deconfigure-realm</literal>.
+
+		  In addition to common DBus error results, this method may return:
+		  <itemizedlist>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
+		      may be returned if the unenroll failed for a generic reason.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
+		      returned if the operation was cancelled.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
+		      returned if the calling client is not permitted to perform an unenroll
+		      operation.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>:
+		      returned if the credentials passed did not authenticate against the realm
+		      correctly. It is appropriate to prompt the user again.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.NotEnrolled</literal>:
+		      returned if not enrolled in this realm.</para></listitem>
+		    <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
+		      returned if the service is currently performing another operation like
+		      enroll or unenroll.</para></listitem>
+		  </itemizedlist>
+		-->
+		<method name="Leave">
+			<arg name="credentials" type="(ssv)" direction="in"/>
+			<arg name="options" type="a{sv}" direction="in"/>
+		</method>
+
+	</interface>
+
+</node>
diff --git a/gnome-initial-setup/pages/account/um-realm-manager.c b/gnome-initial-setup/pages/account/um-realm-manager.c
new file mode 100644
index 0000000..301c905
--- /dev/null
+++ b/gnome-initial-setup/pages/account/um-realm-manager.c
@@ -0,0 +1,828 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2012  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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ *             Stef Walter <stefw gnome org>
+ */
+
+#include "config.h"
+
+#include "um-realm-manager.h"
+
+#include <krb5/krb5.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+
+
+struct _UmRealmManager {
+        UmRealmObjectManagerClient parent;
+        UmRealmProvider *provider;
+        guint diagnostics_sig;
+};
+
+typedef struct {
+        UmRealmProviderProxyClass parent_class;
+} UmRealmManagerClass;
+
+enum {
+        REALM_ADDED,
+        NUM_SIGNALS,
+};
+
+static gint signals[NUM_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE (UmRealmManager, um_realm_manager, UM_REALM_TYPE_OBJECT_MANAGER_CLIENT);
+
+GQuark
+um_realm_error_get_quark (void)
+{
+        static GQuark quark = 0;
+        if (quark == 0)
+                quark = g_quark_from_static_string ("um-realm-error");
+        return quark;
+}
+
+static gboolean
+is_realm_with_kerberos_and_membership (gpointer object)
+{
+        GDBusInterface *interface;
+
+        if (!G_IS_DBUS_OBJECT (object))
+                return FALSE;
+
+        interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.Kerberos");
+        if (interface == NULL)
+                return FALSE;
+        g_object_unref (interface);
+
+        interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.KerberosMembership");
+        if (interface == NULL)
+                return FALSE;
+        g_object_unref (interface);
+
+        return TRUE;
+}
+
+static void
+on_interface_added (GDBusObjectManager *manager,
+                    GDBusObject *object,
+                    GDBusInterface *interface)
+{
+        g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (interface), G_MAXINT);
+}
+
+static void
+on_object_added (GDBusObjectManager *manager,
+                 GDBusObject *object,
+                 gpointer user_data)
+{
+        if (is_realm_with_kerberos_and_membership (object))
+                g_signal_emit (user_data, signals[REALM_ADDED], 0, object);
+}
+
+static void
+um_realm_manager_init (UmRealmManager *self)
+{
+        g_signal_connect (self, "object-added", G_CALLBACK (on_object_added), self);
+        g_signal_connect (self, "interface-added", G_CALLBACK (on_interface_added), self);
+}
+
+static void
+um_realm_manager_dispose (GObject *obj)
+{
+        UmRealmManager *self = UM_REALM_MANAGER (obj);
+        GDBusConnection *connection;
+
+        g_clear_object (&self->provider);
+
+        if (self->diagnostics_sig) {
+                connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (self));
+                if (connection != NULL)
+                        g_dbus_connection_signal_unsubscribe (connection, self->diagnostics_sig);
+                self->diagnostics_sig = 0;
+        }
+
+        G_OBJECT_CLASS (um_realm_manager_parent_class)->dispose (obj);
+}
+
+static void
+um_realm_manager_class_init (UmRealmManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->dispose = um_realm_manager_dispose;
+
+        signals[REALM_ADDED] = g_signal_new ("realm-added", UM_TYPE_REALM_MANAGER,
+                                             G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+                                             g_cclosure_marshal_generic,
+                                             G_TYPE_NONE, 1, UM_REALM_TYPE_OBJECT);
+}
+
+static void
+on_realm_diagnostics (GDBusConnection *connection,
+                      const gchar *sender_name,
+                      const gchar *object_path,
+                      const gchar *interface_name,
+                      const gchar *signal_name,
+                      GVariant *parameters,
+                      gpointer user_data)
+{
+        const gchar *message;
+        const gchar *unused;
+
+        if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ss)"))) {
+                /* Data is already formatted appropriately for stderr */
+                g_variant_get (parameters, "(&s&s)", &message, &unused);
+                g_printerr ("%s", message);
+        }
+}
+
+static gboolean
+number_at_least (const gchar *number,
+                 guint minimum)
+{
+        gchar *end;
+
+        if (strtol (number, &end, 10) < (long)minimum)
+                return FALSE;
+        if (!end || *end != '\0')
+                return FALSE;
+        return TRUE;
+}
+
+static gboolean
+version_compare (const char *version,
+                 guint req_major,
+                 guint req_minor)
+{
+        gboolean match = FALSE;
+        gchar **parts;
+
+        parts = g_strsplit (version, ".", 2);
+
+        if (parts[0] && parts[1]) {
+                match = number_at_least (parts[0], req_major) &&
+                        number_at_least (parts[1], req_minor);
+        }
+
+        g_strfreev (parts);
+        return match;
+}
+
+void
+um_realm_manager_new (GCancellable *cancellable,
+                      GAsyncReadyCallback callback,
+                      gpointer user_data)
+{
+        g_async_initable_new_async (UM_TYPE_REALM_MANAGER, G_PRIORITY_DEFAULT,
+                                    cancellable, callback, user_data,
+                                    "flags", G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+                                    "name", "org.freedesktop.realmd",
+                                    "bus-type", G_BUS_TYPE_SYSTEM,
+                                    "object-path", "/org/freedesktop/realmd",
+                                    "get-proxy-type-func", um_realm_object_manager_client_get_proxy_type,
+                                    NULL);
+}
+
+UmRealmManager *
+um_realm_manager_new_finish (GAsyncResult *result,
+                             GError **error)
+{
+        UmRealmManager *self;
+        GDBusConnection *connection;
+        GObject *source_object;
+        const gchar *version;
+        GObject *ret;
+        guint sig;
+
+        source_object = g_async_result_get_source_object (result);
+        ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, error);
+        g_object_unref (source_object);
+
+        if (ret == NULL)
+                return NULL;
+
+        self = UM_REALM_MANAGER (ret);
+        connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (self));
+
+        /*
+         * TODO: Remove this version checking. This is temporary code, so
+         * just use sync here. Shortly we'll be stabilizing the realmd
+         * interfaces.
+         */
+
+        self->provider = um_realm_provider_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE,
+                                                           "org.freedesktop.realmd",
+                                                           "/org/freedesktop/realmd",
+                                                           NULL, error);
+        if (self->provider == NULL) {
+                g_object_unref (self);
+                return NULL;
+        }
+
+        version = um_realm_provider_get_version (self->provider);
+        if (version == NULL || !version_compare (version, 0, 7)) {
+                /* No need to bother translators with this temporary message */
+                g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
+                             "realmd version should be at least 0.7");
+                g_object_unref (self);
+                return NULL;
+        }
+
+        sig = g_dbus_connection_signal_subscribe (connection,
+                                                  "org.freedesktop.realmd",
+                                                  "org.freedesktop.realmd.Service",
+                                                  "Diagnostics",
+                                                  NULL,
+                                                  NULL,
+                                                  G_DBUS_SIGNAL_FLAGS_NONE,
+                                                  on_realm_diagnostics,
+                                                  NULL,
+                                                  NULL);
+        self->diagnostics_sig = sig;
+
+        return self;
+}
+
+typedef struct {
+        GDBusObjectManager *manager;
+        GCancellable *cancellable;
+        GList *realms;
+} DiscoverClosure;
+
+static void
+discover_closure_free (gpointer data)
+{
+        DiscoverClosure *discover = data;
+        g_object_unref (discover->manager);
+        g_clear_object (&discover->cancellable);
+        g_list_free_full (discover->realms, g_object_unref);
+        g_slice_free (DiscoverClosure, discover);
+}
+
+static void
+on_provider_discover (GObject *source,
+                      GAsyncResult *result,
+                      gpointer user_data)
+{
+        GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+        DiscoverClosure *discover = g_simple_async_result_get_op_res_gpointer (async);
+        GDBusObject *object;
+        GError *error = NULL;
+        gchar **realms;
+        gint relevance;
+        gint i;
+
+        um_realm_provider_call_discover_finish (UM_REALM_PROVIDER (source), &relevance,
+                                                &realms, result, &error);
+        if (error == NULL) {
+                for (i = 0; realms[i]; i++) {
+                        object = g_dbus_object_manager_get_object (discover->manager, realms[i]);
+                        if (object == NULL)
+                                g_warning ("Realm is not in object manager: %s", realms[i]);
+                        else
+                                discover->realms = g_list_prepend (discover->realms, object);
+                }
+
+        } else {
+                g_simple_async_result_take_error (async, error);
+                g_simple_async_result_complete (async);
+        }
+
+        g_object_unref (async);
+}
+
+void
+um_realm_manager_discover (UmRealmManager *self,
+                           const gchar *input,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data)
+{
+        GSimpleAsyncResult *res;
+        DiscoverClosure *discover;
+        GVariant *options;
+
+        g_return_if_fail (UM_IS_REALM_MANAGER (self));
+        g_return_if_fail (input != NULL);
+        g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+        res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+                                         um_realm_manager_discover);
+        discover = g_slice_new0 (DiscoverClosure);
+        discover->manager = g_object_ref (self);
+        discover->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+        g_simple_async_result_set_op_res_gpointer (res, discover, discover_closure_free);
+
+	options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
+
+        um_realm_provider_call_discover (self->provider, input, options, cancellable,
+                                         on_provider_discover, g_object_ref (res));
+
+        g_object_unref (res);
+}
+
+GList *
+um_realm_manager_discover_finish (UmRealmManager *self,
+                                  GAsyncResult *result,
+                                  GError **error)
+{
+        GSimpleAsyncResult *async;
+        DiscoverClosure *discover;
+        GList *realms;
+
+        g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL);
+        g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+                              um_realm_manager_discover), NULL);
+        g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+        async = G_SIMPLE_ASYNC_RESULT (result);
+        if (g_simple_async_result_propagate_error (async, error))
+                return NULL;
+
+        discover = g_simple_async_result_get_op_res_gpointer (async);
+        if (!discover->realms) {
+                g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
+                             _("No such domain or realm found"));
+                return NULL;
+        }
+
+        realms = g_list_reverse (discover->realms);
+        discover->realms = NULL;
+        return realms;
+}
+
+GList *
+um_realm_manager_get_realms (UmRealmManager *self)
+{
+        GList *objects;
+        GList *realms = NULL;
+        GList *l;
+
+        g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL);
+
+        objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self));
+        for (l = objects; l != NULL; l = g_list_next (l)) {
+                if (is_realm_with_kerberos_and_membership (l->data))
+                        realms = g_list_prepend (realms, g_object_ref (l->data));
+        }
+
+        g_list_free_full (objects, g_object_unref);
+        return realms;
+}
+
+static void
+string_replace (GString *string,
+                const gchar *find,
+                const gchar *replace)
+{
+        const gchar *at;
+        gssize pos;
+
+        at = strstr (string->str, find);
+        if (at != NULL) {
+                pos = at - string->str;
+                g_string_erase (string, pos, strlen (find));
+                g_string_insert (string, pos, replace);
+        }
+}
+
+gchar *
+um_realm_calculate_login (UmRealmCommon *realm,
+                          const gchar *username)
+{
+        GString *string;
+        const gchar *const *formats;
+        gchar *login = NULL;
+
+        formats = um_realm_common_get_login_formats (realm);
+        if (formats[0] != NULL) {
+                string = g_string_new (formats[0]);
+                string_replace (string, "%U", username);
+                string_replace (string, "%D", um_realm_common_get_name (realm));
+                login = g_string_free (string, FALSE);
+        }
+
+        return login;
+
+}
+
+gboolean
+um_realm_is_configured (UmRealmObject *realm)
+{
+        UmRealmCommon *common;
+        const gchar *configured;
+        gboolean is;
+
+        common = um_realm_object_get_common (realm);
+        configured = um_realm_common_get_configured (common);
+        is = configured != NULL && !g_str_equal (configured, "");
+        g_object_unref (common);
+
+        return is;
+}
+
+static const gchar *
+find_supported_credentials (UmRealmKerberosMembership *membership,
+                            const gchar *owner)
+{
+        const gchar *cred_owner;
+        const gchar *cred_type;
+        GVariant *supported;
+        GVariantIter iter;
+
+        supported = um_realm_kerberos_membership_get_supported_join_credentials (membership);
+        g_return_val_if_fail (supported != NULL, NULL);
+
+        g_variant_iter_init (&iter, supported);
+        while (g_variant_iter_loop (&iter, "(&s&s)", &cred_type, &cred_owner)) {
+                if (g_str_equal (owner, cred_owner)) {
+                        if (g_str_equal (cred_type, "ccache") ||
+                            g_str_equal (cred_type, "password")) {
+                                return g_intern_string (cred_type);
+                        }
+                }
+        }
+
+        return NULL;
+}
+
+static void
+on_realm_join_complete (GObject *source,
+                        GAsyncResult *result,
+                        gpointer user_data)
+{
+	GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+	g_simple_async_result_set_op_res_gpointer (async, g_object_ref (result), g_object_unref);
+	g_simple_async_result_complete_in_idle (async);
+	g_object_unref (async);
+}
+
+static gboolean
+realm_join_as_owner (UmRealmObject *realm,
+                     const gchar *owner,
+                     const gchar *login,
+                     const gchar *password,
+                     GBytes *credentials,
+                     GCancellable *cancellable,
+                     GAsyncReadyCallback callback,
+                     gpointer user_data)
+{
+        UmRealmKerberosMembership *membership;
+        GSimpleAsyncResult *async;
+        GVariant *contents;
+        GVariant *options;
+        GVariant *creds;
+        const gchar *type;
+
+        membership = um_realm_object_get_kerberos_membership (realm);
+        g_return_val_if_fail (membership != NULL, FALSE);
+
+        type = find_supported_credentials (membership, owner);
+        if (type == NULL) {
+                g_object_unref (membership);
+                return FALSE;
+        }
+
+        async = g_simple_async_result_new (G_OBJECT (realm), callback, user_data,
+                                           realm_join_as_owner);
+
+        if (g_str_equal (type, "ccache")) {
+                contents = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
+                                                    g_bytes_get_data (credentials, NULL),
+                                                    g_bytes_get_size (credentials),
+                                                    TRUE, (GDestroyNotify)g_bytes_unref, credentials);
+
+        } else if (g_str_equal (type, "password")) {
+                contents = g_variant_new ("(ss)", login, password);
+
+        } else {
+                g_assert_not_reached ();
+        }
+
+        creds = g_variant_new ("(ssv)", type, owner, contents);
+        options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
+
+        um_realm_kerberos_membership_call_join (membership, creds, options,
+                                                cancellable, on_realm_join_complete,
+                                                g_object_ref (async));
+
+        g_object_unref (async);
+        g_object_unref (membership);
+
+        return TRUE;
+}
+
+gboolean
+um_realm_join_as_user (UmRealmObject *realm,
+                       const gchar *login,
+                       const gchar *password,
+                       GBytes *credentials,
+                       GCancellable *cancellable,
+                       GAsyncReadyCallback callback,
+                       gpointer user_data)
+{
+        g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
+        g_return_val_if_fail (credentials != NULL, FALSE);
+        g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+        g_return_val_if_fail (login != NULL, FALSE);
+        g_return_val_if_fail (password != NULL, FALSE);
+        g_return_val_if_fail (credentials != NULL, FALSE);
+
+        return realm_join_as_owner (realm, "user", login, password,
+                                    credentials, cancellable, callback, user_data);
+}
+
+gboolean
+um_realm_join_as_admin (UmRealmObject *realm,
+                        const gchar *login,
+                        const gchar *password,
+                        GBytes *credentials,
+                        GCancellable *cancellable,
+                        GAsyncReadyCallback callback,
+                        gpointer user_data)
+{
+        g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
+        g_return_val_if_fail (credentials != NULL, FALSE);
+        g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+        g_return_val_if_fail (login != NULL, FALSE);
+        g_return_val_if_fail (password != NULL, FALSE);
+        g_return_val_if_fail (credentials != NULL, FALSE);
+
+        return realm_join_as_owner (realm, "administrator", login, password, credentials,
+                                    cancellable, callback, user_data);
+}
+
+gboolean
+um_realm_join_finish (UmRealmObject *realm,
+                      GAsyncResult *result,
+                      GError **error)
+{
+        UmRealmKerberosMembership *membership;
+        GError *call_error = NULL;
+        gchar *dbus_error;
+        GAsyncResult *async;
+
+        g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
+        g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+        membership = um_realm_object_get_kerberos_membership (realm);
+        g_return_val_if_fail (membership != NULL, FALSE);
+
+        async = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+        um_realm_kerberos_membership_call_join_finish (membership, async, &call_error);
+        g_object_unref (membership);
+
+        if (call_error == NULL)
+                return TRUE;
+
+        dbus_error = g_dbus_error_get_remote_error (call_error);
+        if (dbus_error == NULL) {
+                g_propagate_error (error, call_error);
+                return FALSE;
+        }
+
+        g_dbus_error_strip_remote_error (call_error);
+
+        if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.AuthenticationFailed")) {
+                g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN,
+                             "%s", call_error->message);
+                g_error_free (call_error);
+        } else {
+                g_propagate_error (error, call_error);
+        }
+
+        g_free (dbus_error);
+        return FALSE;
+}
+
+typedef struct {
+        gchar *domain;
+        gchar *realm;
+        gchar *user;
+        gchar *password;
+        GBytes *credentials;
+} LoginClosure;
+
+static void
+login_closure_free (gpointer data)
+{
+        LoginClosure *login = data;
+        g_free (login->domain);
+        g_free (login->realm);
+        g_free (login->user);
+        g_free (login->password);
+        g_bytes_unref (login->credentials);
+        g_slice_free (LoginClosure, login);
+}
+
+static krb5_error_code
+login_perform_kinit (krb5_context k5,
+                     const gchar *realm,
+                     const gchar *login,
+                     const gchar *password,
+                     const gchar *filename)
+{
+        krb5_get_init_creds_opt *opts;
+        krb5_error_code code;
+        krb5_principal principal;
+        krb5_ccache ccache;
+        krb5_creds creds;
+        gchar *name;
+
+        name = g_strdup_printf ("%s %s", login, realm);
+        code = krb5_parse_name (k5, name, &principal);
+        g_free (name);
+
+        if (code != 0)
+                return code;
+
+        if (filename == NULL)
+                code = krb5_cc_default (k5, &ccache);
+        else
+                code = krb5_cc_resolve (k5, filename, &ccache);
+
+        if (code != 0) {
+                krb5_free_principal (k5, principal);
+                return code;
+        }
+
+        code = krb5_get_init_creds_opt_alloc (k5, &opts);
+        g_return_val_if_fail (code == 0, code);
+
+        code = krb5_get_init_creds_opt_set_out_ccache (k5, opts, ccache);
+        g_return_val_if_fail (code == 0, code);
+
+        code = krb5_get_init_creds_password (k5, &creds, principal,
+                                             (char *)password,
+                                             NULL, 0, 0, NULL, opts);
+
+        krb5_get_init_creds_opt_free (k5, opts);
+        krb5_cc_close (k5, ccache);
+        krb5_free_principal (k5, principal);
+
+        if (code == 0)
+                krb5_free_cred_contents (k5, &creds);
+
+        return code;
+}
+
+static void
+kinit_thread_func (GSimpleAsyncResult *async,
+                   GObject *object,
+                   GCancellable *cancellable)
+{
+        LoginClosure *login = g_simple_async_result_get_op_res_gpointer (async);
+        krb5_context k5 = NULL;
+        krb5_error_code code;
+        GError *error = NULL;
+        gchar *filename = NULL;
+        gchar *contents;
+        gsize length;
+        gint temp_fd;
+
+        filename = g_build_filename (g_get_user_runtime_dir (),
+                                     "um-krb5-creds.XXXXXX", NULL);
+        temp_fd = g_mkstemp_full (filename, O_RDWR, S_IRUSR | S_IWUSR);
+        if (temp_fd == -1) {
+                g_warning ("Couldn't create credential cache file: %s: %s",
+                           filename, g_strerror (errno));
+                g_free (filename);
+                filename = NULL;
+        } else {
+                close (temp_fd);
+        }
+
+        code = krb5_init_context (&k5);
+        if (code == 0) {
+                code = login_perform_kinit (k5, login->realm, login->user,
+                                            login->password, filename);
+        }
+
+        switch (code) {
+        case 0:
+                if (filename != NULL) {
+                        g_file_get_contents (filename, &contents, &length, &error);
+                        if (error == NULL) {
+                                login->credentials = g_bytes_new_take (contents, length);
+                        } else {
+                                g_warning ("Couldn't read credential cache: %s", error->message);
+                                g_error_free (error);
+                        }
+                }
+                break;
+
+        case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
+        case KRB5KDC_ERR_CLIENT_REVOKED:
+        case KRB5KDC_ERR_KEY_EXP:
+        case KRB5KDC_ERR_POLICY:
+        case KRB5KDC_ERR_ETYPE_NOSUPP:
+                g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN,
+                                                 _("Cannot log in as %s at the %s domain"),
+                                                 login->user, login->domain);
+                break;
+        case KRB5KDC_ERR_PREAUTH_FAILED:
+                g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD,
+                                                 _("Invalid password, please try again"));
+                break;
+        default:
+                g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
+                                                 _("Couldn't connect to the %s domain: %s"),
+                                                 login->domain, krb5_get_error_message (k5, code));
+                break;
+        }
+
+        if (filename) {
+                g_unlink (filename);
+                g_free (filename);
+        }
+
+        if (k5)
+                krb5_free_context (k5);
+}
+
+void
+um_realm_login (UmRealmObject *realm,
+                const gchar *user,
+                const gchar *password,
+                GCancellable *cancellable,
+                GAsyncReadyCallback callback,
+                gpointer user_data)
+{
+        GSimpleAsyncResult *async;
+        LoginClosure *login;
+        UmRealmKerberos *kerberos;
+
+        g_return_if_fail (UM_REALM_IS_OBJECT (realm));
+        g_return_if_fail (user != NULL);
+        g_return_if_fail (password != NULL);
+        g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+        kerberos = um_realm_object_get_kerberos (realm);
+        g_return_if_fail (kerberos != NULL);
+
+        async = g_simple_async_result_new (NULL, callback, user_data,
+                                           um_realm_login);
+        login = g_slice_new0 (LoginClosure);
+        login->domain = g_strdup (um_realm_kerberos_get_domain_name (kerberos));
+        login->realm = g_strdup (um_realm_kerberos_get_realm_name (kerberos));
+        login->user = g_strdup (user);
+        login->password = g_strdup (password);
+        g_simple_async_result_set_op_res_gpointer (async, login, login_closure_free);
+
+        g_simple_async_result_set_handle_cancellation (async, TRUE);
+        g_simple_async_result_run_in_thread (async, kinit_thread_func,
+                                             G_PRIORITY_DEFAULT, cancellable);
+
+        g_object_unref (async);
+        g_object_unref (kerberos);
+}
+
+gboolean
+um_realm_login_finish (GAsyncResult *result,
+                       GBytes **credentials,
+                       GError **error)
+{
+        GSimpleAsyncResult *async;
+        LoginClosure *login;
+
+        g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL,
+                              um_realm_login), FALSE);
+        g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+        async = G_SIMPLE_ASYNC_RESULT (result);
+        if (g_simple_async_result_propagate_error (async, error))
+                return FALSE;
+
+        login = g_simple_async_result_get_op_res_gpointer (async);
+        if (credentials) {
+                if (login->credentials)
+                        *credentials = g_bytes_ref (login->credentials);
+                else
+                        *credentials = NULL;
+        }
+
+        return TRUE;
+}
diff --git a/gnome-initial-setup/pages/account/um-realm-manager.h b/gnome-initial-setup/pages/account/um-realm-manager.h
new file mode 100644
index 0000000..a604fae
--- /dev/null
+++ b/gnome-initial-setup/pages/account/um-realm-manager.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2012  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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Stef Walter <stefw gnome org>
+ */
+
+#ifndef __UM_REALM_MANAGER_H__
+#define __UM_REALM_MANAGER_H__
+
+#include "um-realm-generated.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+	UM_REALM_ERROR_BAD_LOGIN,
+	UM_REALM_ERROR_BAD_PASSWORD,
+	UM_REALM_ERROR_GENERIC,
+} UmRealmErrors;
+
+#define UM_REALM_ERROR             (um_realm_error_get_quark ())
+
+GQuark           um_realm_error_get_quark         (void) G_GNUC_CONST;
+
+#define UM_TYPE_REALM_MANAGER      (um_realm_manager_get_type ())
+#define UM_REALM_MANAGER(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_REALM_MANAGER, UmRealmManager))
+#define UM_IS_REALM_MANAGER(obj)   (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_REALM_MANAGER))
+
+typedef struct _UmRealmManager UmRealmManager;
+
+GType            um_realm_manager_get_type        (void) G_GNUC_CONST;
+
+void             um_realm_manager_new             (GCancellable *cancellable,
+                                                   GAsyncReadyCallback callback,
+                                                   gpointer user_data);
+
+UmRealmManager * um_realm_manager_new_finish      (GAsyncResult *result,
+                                                   GError **error);
+
+void             um_realm_manager_discover        (UmRealmManager *self,
+                                                   const gchar *input,
+                                                   GCancellable *cancellable,
+                                                   GAsyncReadyCallback callback,
+                                                   gpointer user_data);
+
+GList *          um_realm_manager_discover_finish (UmRealmManager *self,
+                                                   GAsyncResult *result,
+                                                   GError **error);
+
+GList *          um_realm_manager_get_realms      (UmRealmManager *self);
+
+void             um_realm_login                   (UmRealmObject *realm,
+                                                   const gchar *login,
+                                                   const gchar *password,
+                                                   GCancellable *cancellable,
+                                                   GAsyncReadyCallback callback,
+                                                   gpointer user_data);
+
+gboolean         um_realm_login_finish            (GAsyncResult *result,
+                                                   GBytes **credentials,
+                                                   GError **error);
+
+gboolean         um_realm_join_as_user            (UmRealmObject *realm,
+                                                   const gchar *login,
+                                                   const gchar *password,
+                                                   GBytes *credentials,
+                                                   GCancellable *cancellable,
+                                                   GAsyncReadyCallback callback,
+                                                   gpointer user_data)
+                                                   G_GNUC_WARN_UNUSED_RESULT;
+
+gboolean         um_realm_join_as_admin           (UmRealmObject *realm,
+                                                   const gchar *login,
+                                                   const gchar *password,
+                                                   GBytes *credentials,
+                                                   GCancellable *cancellable,
+                                                   GAsyncReadyCallback callback,
+                                                   gpointer user_data)
+                                                   G_GNUC_WARN_UNUSED_RESULT;
+
+gboolean         um_realm_join_finish             (UmRealmObject *realm,
+                                                   GAsyncResult *result,
+                                                   GError **error);
+
+gboolean         um_realm_is_configured           (UmRealmObject *realm);
+
+gchar *          um_realm_calculate_login         (UmRealmCommon *realm,
+                                                   const gchar *username);
+
+G_END_DECLS
+
+#endif /* __UM_REALM_H__ */



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