[evolution-data-server/account-mgmt: 7/40] Add a new "evolution-source-registry" D-Bus service.



commit 55470e8ef19c9729d90c40c3b14775e1548be6d4
Author: Matthew Barnes <mbarnes redhat com>
Date:   Thu Sep 29 16:08:20 2011 -0400

    Add a new "evolution-source-registry" D-Bus service.
    
    This new service manages data source key files and serves them to
    clients, through the ESource and ESourceRegistry client-side APIs.

 configure.ac                                       |   31 +-
 docs/reference/libebackend/libebackend-docs.xml    |    9 +-
 .../reference/libebackend/libebackend-sections.txt |  106 +
 docs/reference/libebackend/libebackend.types       |    6 +
 .../libedataserver/libedataserver-sections.txt     |    4 -
 libebackend/Makefile.am                            |   26 +-
 libebackend/e-backend-enums.h                      |   79 +
 libebackend/e-dbus-server.c                        |    6 +-
 libebackend/e-dbus-source-authenticator.c          | 1263 ++++++++
 libebackend/e-dbus-source-authenticator.h          |  170 +
 libebackend/e-dbus-source-server.c                 | 1583 ++++++++++
 libebackend/e-dbus-source-server.h                 |  129 +
 libebackend/e-server-side-source.c                 | 1058 +++++++
 libebackend/e-server-side-source.h                 |   87 +
 services/Makefile.am                               |    1 +
 services/evolution-addressbook-factory/Makefile.am |    5 +-
 ...evolution-addressbook-factory-migrate-basedir.c |  319 --
 .../evolution-addressbook-factory.c                |    6 -
 services/evolution-calendar-factory/Makefile.am    |    5 +-
 .../evolution-calendar-factory.c                   |    6 -
 services/evolution-source-registry/Makefile.am     |   49 +
 .../e-authentication-dialog.c                      |  622 ++++
 .../e-authentication-dialog.h                      |   79 +
 .../evolution-source-registry/e-authenticator.c    |  310 ++
 .../evolution-source-registry/e-authenticator.h    |   63 +
 .../evolution-source-registry-migrate-basedir.c}   |   79 +-
 .../evolution-source-registry-migrate-sources.c    | 3241 ++++++++++++++++++++
 .../evolution-source-registry.c                    |   86 +
 ...g.gnome.evolution.dataserver.Sources.service.in |    3 +
 29 files changed, 9076 insertions(+), 355 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 1c995cb..2912366 100644
--- a/configure.ac
+++ b/configure.ac
@@ -63,6 +63,7 @@ dnl D-Bus versioning
 dnl ******************************
 ADDRESS_BOOK_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.AddressBook3"
 CALENDAR_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.Calendar2"
+SOURCES_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.Sources0"
 
 AC_DEFINE_UNQUOTED(
        ADDRESS_BOOK_DBUS_SERVICE_NAME,
@@ -74,8 +75,14 @@ AC_DEFINE_UNQUOTED(
        ["$CALENDAR_DBUS_SERVICE_NAME"],
        [D-Bus service name for the calendar factory])
 
+AC_DEFINE_UNQUOTED(
+	SOURCES_DBUS_SERVICE_NAME,
+	["$SOURCES_DBUS_SERVICE_NAME"],
+	[D-Bus service name for the source registry])
+
 AC_SUBST(ADDRESS_BOOK_DBUS_SERVICE_NAME)
 AC_SUBST(CALENDAR_DBUS_SERVICE_NAME)
+AC_SUBST(SOURCES_DBUS_SERVICE_NAME)
 
 dnl ******************************
 dnl Libtool versioning
@@ -389,14 +396,15 @@ if test "x$enable_goa" = xyes; then
 fi
 AM_CONDITIONAL(HAVE_GOA, [test x$enable_goa = xyes])
 
+dnl ***********************************
+dnl Check for GNOME Keyring.
+dnl ***********************************
 if test x$os_win32 = xno; then
-  dnl ***********************************
-  dnl Check for GNOME Keyring.
-  dnl ***********************************
-  PKG_CHECK_MODULES(GNOME_KEYRING,
-	[gnome-keyring-1 >= gnome_keyring_minimum_version])
+	PKG_CHECK_MODULES(GNOME_KEYRING,
+		[gnome-keyring-1 >= gnome_keyring_minimum_version])
 fi
-
+AC_SUBST(GNOME_KEYRING_CFLAGS)
+AC_SUBST(GNOME_KEYRING_LIBS)
 
 dnl **********************************************************
 dnl gcr-base is needed for secure password exchange over D-Bus
@@ -1319,7 +1327,7 @@ if test "x$enable_maintainer_mode" = "xyes" ; then
 	AC_SUBST(FACTORY_GTK_LIBS)
 fi
 
-E_BACKEND_DEPS="gio-2.0 gmodule-2.0 libxml-2.0 gconf-2.0"
+E_BACKEND_DEPS="gio-2.0 gmodule-2.0 gnome-keyring-1 libxml-2.0 gconf-2.0"
 
 dnl ******************************
 dnl libebackend flags
@@ -1489,6 +1497,12 @@ AC_SUBST(ebook_backenddir)
 ecal_backenddir='${libdir}'/evolution-data-server/calendar-backends
 AC_SUBST(ecal_backenddir)
 
+ro_sourcesdir='${datadir}'/evolution-data-server-$BASE_VERSION/ro-sources
+AC_SUBST(ro_sourcesdir)
+
+rw_sourcesdir='${datadir}'/evolution-data-server-$BASE_VERSION/rw-sources
+AC_SUBST(rw_sourcesdir)
+
 if test "x$use_gweather" = "xyes"; then
 	weatherdatadir="$privdatadir/weather"
 	AC_SUBST(weatherdatadir)
@@ -1502,7 +1516,7 @@ dnl *******************
 dnl D-BUS service stuff
 dnl *******************
 m4_pattern_allow([AM_V_GEN])
-EVO_SUBST_SERVICE_RULE='%.service: %.service.in Makefile ; $(AM_V_GEN) sed -e "s|\ libexecdir\@|$(libexecdir)|" -e s"|\ ADDRESS_BOOK_DBUS_SERVICE_NAME\@|$(ADDRESS_BOOK_DBUS_SERVICE_NAME)|" -e "s|\ CALENDAR_DBUS_SERVICE_NAME\@|$(CALENDAR_DBUS_SERVICE_NAME)|" $< > $@'
+EVO_SUBST_SERVICE_RULE='%.service: %.service.in Makefile ; $(AM_V_GEN) sed -e "s|\ libexecdir\@|$(libexecdir)|" -e s"|\ ADDRESS_BOOK_DBUS_SERVICE_NAME\@|$(ADDRESS_BOOK_DBUS_SERVICE_NAME)|" -e "s|\ CALENDAR_DBUS_SERVICE_NAME\@|$(CALENDAR_DBUS_SERVICE_NAME)|" -e "s|\ SOURCES_DBUS_SERVICE_NAME\@|$(SOURCES_DBUS_SERVICE_NAME)|" $< > $@'
 AC_SUBST(EVO_SUBST_SERVICE_RULE)
 
 dnl ******************************
@@ -1617,6 +1631,7 @@ libedataserverui/libedataserverui.pc
 services/Makefile
 services/evolution-addressbook-factory/Makefile
 services/evolution-calendar-factory/Makefile
+services/evolution-source-registry/Makefile
 tests/Makefile
 tests/libebook/Makefile
 tests/libebook/client/Makefile
diff --git a/docs/reference/libebackend/libebackend-docs.xml b/docs/reference/libebackend/libebackend-docs.xml
index b5c7287..9ba7290 100644
--- a/docs/reference/libebackend/libebackend-docs.xml
+++ b/docs/reference/libebackend/libebackend-docs.xml
@@ -13,13 +13,16 @@
     <xi:include href="xml/e-backend.xml"/>
     <xi:include href="xml/e-backend-factory.xml"/>
     <xi:include href="xml/e-data-factory.xml"/>
-    <xi:include href="xml/e-dbus-service.xml"/>
+    <xi:include href="xml/e-dbus-server.xml"/>
+    <xi:include href="xml/e-dbus-source-server.xml"/>
+    <xi:include href="xml/e-dbus-source-authenticator.xml"/>
     <xi:include href="xml/e-extensible.xml"/>
     <xi:include href="xml/e-extension.xml"/>
     <xi:include href="xml/e-module.xml"/>
     <xi:include href="xml/e-file-cache.xml"/>
     <xi:include href="xml/e-dbhash.xml"/>
     <xi:include href="xml/e-db3-utils.xml"/>
+    <xi:include href="xml/e-server-side-source.xml"/>
     <xi:include href="xml/e-sqlite3-vfs.xml"/>
     <xi:include href="xml/e-offline-listener.xml"/>
   </chapter>
@@ -31,6 +34,10 @@
     <title>Index of deprecated symbols</title>
     <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
   </index>
+  <index id="api-index-3.6" role="3.6">
+    <title>Index of new symbols in 3.6</title>
+    <xi:include href="xml/api-index-3.6.xml"><xi:fallback /></xi:include>
+  </index>
   <index id="api-index-3.4" role="3.4">
     <title>Index of new symbols in 3.4</title>
     <xi:include href="xml/api-index-3.4.xml"><xi:fallback /></xi:include>
diff --git a/docs/reference/libebackend/libebackend-sections.txt b/docs/reference/libebackend/libebackend-sections.txt
index c54cdbc..cdbe74f 100644
--- a/docs/reference/libebackend/libebackend-sections.txt
+++ b/docs/reference/libebackend/libebackend-sections.txt
@@ -80,6 +80,85 @@ e_dbus_server_get_type
 </SECTION>
 
 <SECTION>
+<FILE>e-dbus-source-authenticator</FILE>
+<TITLE>EDBusSourceAuthenticator</TITLE>
+E_DBUS_SOURCE_AUTHENTICATOR_ERROR
+EDBusSourceAuthenticator
+EDBusSourceAuthenticatorResult
+e_dbus_source_authenticator_get_server
+e_dbus_source_authenticator_get_source_uid
+e_dbus_source_authenticator_get_icon
+e_dbus_source_authenticator_set_icon
+e_dbus_source_authenticator_get_primary_markup
+e_dbus_source_authenticator_set_primary_markup
+e_dbus_source_authenticator_get_secondary_markup
+e_dbus_source_authenticator_set_secondary_markup
+e_dbus_source_authenticator_initiate
+e_dbus_source_authenticator_dismiss
+e_dbus_source_authenticator_transmit
+e_dbus_source_authenticator_complete
+e_dbus_source_authenticator_store_password_sync
+e_dbus_source_authenticator_store_password
+e_dbus_source_authenticator_store_password_finish
+e_dbus_source_authenticator_lookup_password_sync
+e_dbus_source_authenticator_lookup_password
+e_dbus_source_authenticator_lookup_password_finish
+e_dbus_source_authenticator_delete_password_sync
+e_dbus_source_authenticator_delete_password
+e_dbus_source_authenticator_delete_password_finish
+<SUBSECTION Standard>
+E_DBUS_SOURCE_AUTHENTICATOR
+E_IS_DBUS_SOURCE_AUTHENTICATOR
+E_TYPE_DBUS_SOURCE_AUTHENTICATOR
+E_DBUS_SOURCE_AUTHENTICATOR_CLASS
+E_IS_DBUS_SOURCE_AUTHENTICATOR_CLASS
+E_DBUS_SOURCE_AUTHENTICATOR_GET_CLASS
+E_TYPE_DBUS_SOURCE_AUTHENTICATOR_RESULT
+EDBusSourceAuthenticatorClass
+<SUBSECTION Private>
+EDBusSourceAuthenticatorPrivate
+e_dbus_source_authenticator_error_quark
+e_dbus_source_authenticator_get_type
+e_dbus_source_authenticator_result_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-dbus-source-server</FILE>
+<TITLE>EDBusSourceServer</TITLE>
+E_DBUS_SOURCE_SERVER_OBJECT_PATH
+EDBusSourceServer
+EPasswordRememberType
+e_dbus_source_server_new
+e_dbus_source_server_get_authenticator_type
+e_dbus_source_server_set_authenticator_type
+e_dbus_source_server_add_source
+e_dbus_source_server_remove_source
+e_dbus_source_server_add_authenticator
+e_dbus_source_server_load_all
+EDBusLoadDirectoryFlags
+e_dbus_source_server_load_directory
+e_dbus_source_server_load_file
+e_dbus_source_server_load_error
+e_dbus_source_server_lookup_by_uid
+e_dbus_source_server_lookup_by_file
+<SUBSECTION Standard>
+E_DBUS_SOURCE_SERVER
+E_IS_DBUS_SOURCE_SERVER
+E_TYPE_DBUS_SOURCE_SERVER
+E_DBUS_SOURCE_SERVER_CLASS
+E_IS_DBUS_SOURCE_SERVER_CLASS
+E_DBUS_SOURCE_SERVER_GET_CLASS
+E_TYPE_DBUS_LOAD_DIRECTORY_FLAGS
+E_TYPE_PASSWORD_REMEMBER_TYPE
+EDBusSourceServerClass
+<SUBSECTION Private>
+EDBusSourceServerPrivate
+e_dbus_source_server_get_type
+e_dbus_load_directory_flags_get_type
+e_password_remember_type_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-file-cache</FILE>
 <TITLE>EFileCache</TITLE>
 EFileCache
@@ -208,6 +287,33 @@ e_offline_listener_get_type
 </SECTION>
 
 <SECTION>
+<FILE>e-server-side-source</FILE>
+<TITLE>EServerSideSource</TITLE>
+EServerSideSource
+e_server_side_source_get_user_dir
+e_server_side_source_new
+e_server_side_source_load
+e_server_side_source_get_file
+e_server_side_source_get_node
+e_server_side_source_get_server
+e_server_side_source_get_allow_auth_prompt
+e_server_side_source_set_allow_auth_prompt
+e_server_side_source_set_removable
+e_server_side_source_set_writable
+<SUBSECTION Standard>
+E_SERVER_SIDE_SOURCE
+E_IS_SERVER_SIDE_SOURCE
+E_TYPE_SERVER_SIDE_SOURCE
+E_SERVER_SIDE_SOURCE_CLASS
+E_IS_SERVER_SIDE_SOURCE_CLASS
+E_SERVER_SIDE_SOURCE_GET_CLASS
+EServerSideSourceClass
+<SUBSECTION Private>
+EServerSideSourcePrivate
+e_server_side_source_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-sqlite3-vfs</FILE>
 e_sqlite3_vfs_init
 </SECTION>
diff --git a/docs/reference/libebackend/libebackend.types b/docs/reference/libebackend/libebackend.types
index e23f54b..992d2c5 100644
--- a/docs/reference/libebackend/libebackend.types
+++ b/docs/reference/libebackend/libebackend.types
@@ -2,18 +2,24 @@
 #include <libebackend/e-backend-factory.h>
 #include <libebackend/e-data-factory.h>
 #include <libebackend/e-dbus-server.h>
+#include <libebackend/e-dbus-source-authenticator.h>
+#include <libebackend/e-dbus-source-server.h>
 #include <libebackend/e-extensible.h>
 #include <libebackend/e-extension.h>
 #include <libebackend/e-file-cache.h>
 #include <libebackend/e-module.h>
 #include <libebackend/e-offline-listener.h>
+#include <libebackend/e-server-side-source.h>
 
 e_backend_get_type
 e_backend_factory_get_type
 e_data_factory_get_type
 e_dbus_server_get_type
+e_dbus_source_authenticator_get_type
+e_dbus_source_server_get_type
 e_extensible_get_type
 e_extension_get_type
 e_file_cache_get_type
 e_module_get_type
 e_offline_listener_get_type
+e_server_side_source_get_type
diff --git a/docs/reference/libedataserver/libedataserver-sections.txt b/docs/reference/libedataserver/libedataserver-sections.txt
index bdda8bb..f6acfe8 100644
--- a/docs/reference/libedataserver/libedataserver-sections.txt
+++ b/docs/reference/libedataserver/libedataserver-sections.txt
@@ -284,10 +284,6 @@ e_dbus_source_set_uid
 e_dbus_source_get_data
 e_dbus_source_dup_data
 e_dbus_source_set_data
-e_dbus_source_call_authenticate_sync
-e_dbus_source_call_authenticate
-e_dbus_source_call_authenticate_finish
-e_dbus_source_complete_authenticate
 e_dbus_source_call_allow_auth_prompt_sync
 e_dbus_source_call_allow_auth_prompt
 e_dbus_source_call_allow_auth_prompt_finish
diff --git a/libebackend/Makefile.am b/libebackend/Makefile.am
index 3e2de98..54c3f02 100644
--- a/libebackend/Makefile.am
+++ b/libebackend/Makefile.am
@@ -1,27 +1,46 @@
+include $(top_srcdir)/glib-gen.mak
+glib_enum_headers=e-backend-enums.h
+glib_enum_output=e-backend-enumtypes
+glib_enum_define=E
+glib_enum_prefix=e
+
+ENUM_GENERATED = e-backend-enumtypes.h e-backend-enumtypes.c
+
+BUILT_SOURCES = $(ENUM_GENERATED)
+
 lib_LTLIBRARIES = libebackend-1.2.la
 
 libebackend_1_2_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-DG_LOG_DOMAIN=\"e-data-server\"			\
+	-DG_LOG_DOMAIN=\"libebackend\"				\
+	-DMODULE_DIRECTORY=\"$(moduledir)\"			\
+	-DE_DATA_SERVER_PRIVDATADIR=\"$(privdatadir)\"		\
+	-DSYSTEM_WIDE_RO_SOURCES_DIRECTORY=\"$(ro_sourcesdir)\"	\
+	-DSYSTEM_WIDE_RW_SOURCES_DIRECTORY=\"$(rw_sourcesdir)\"	\
 	$(DB_CFLAGS)						\
 	$(SQLITE3_CFLAGS)					\
 	$(E_BACKEND_CFLAGS)					\
+	$(GCR_BASE_CFLAGS)					\
 	$(GIO_UNIX_CFLAGS)					\
 	$(CODE_COVERAGE_CFLAGS)					\
 	$(NULL)
 
 libebackend_1_2_la_SOURCES =		\
+	$(BUILT_SOURCES)		\
 	e-backend.c			\
 	e-backend-factory.c		\
 	e-data-factory.c		\
 	e-dbus-server.c			\
+	e-dbus-source-authenticator.c	\
+	e-dbus-source-server.c		\
 	e-extensible.c			\
 	e-extension.c			\
 	e-offline-listener.c		\
 	e-dbhash.c			\
 	e-db3-utils.c			\
 	e-module.c			\
+	e-server-side-source.c		\
 	e-sqlite3-vfs.c			\
 	e-file-cache.c
 
@@ -29,6 +48,7 @@ libebackend_1_2_la_LIBADD = 				\
 	$(top_builddir)/libedataserver/libedataserver-1.2.la \
 	$(E_BACKEND_LIBS)				\
 	$(SQLITE3_LIBS)					\
+	$(GCR_BASE_LIBS)				\
 	$(GIO_UNIX_LIBS)				\
 	$(DB_LIBS)
 
@@ -41,15 +61,19 @@ libebackendincludedir = $(privincludedir)/libebackend
 
 libebackendinclude_HEADERS =		\
 	e-backend.h			\
+	e-backend-enums.h		\
 	e-backend-factory.h		\
 	e-data-factory.h		\
 	e-dbus-server.h			\
+	e-dbus-source-authenticator.h	\
+	e-dbus-source-server.h		\
 	e-extensible.h			\
 	e-extension.h			\
 	e-offline-listener.h		\
 	e-db3-utils.h			\
 	e-dbhash.h			\
 	e-module.h			\
+	e-server-side-source.h		\
 	e-sqlite3-vfs.h			\
 	e-file-cache.h
 
diff --git a/libebackend/e-backend-enums.h b/libebackend/e-backend-enums.h
new file mode 100644
index 0000000..7d0cc12
--- /dev/null
+++ b/libebackend/e-backend-enums.h
@@ -0,0 +1,79 @@
+/*
+ * e-backend-enums.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_BACKEND_ENUMS_H
+#define E_BACKEND_ENUMS_H
+
+/**
+ * EDBusLoadDirectoryFlags:
+ * @E_DBUS_LOAD_DIRECTORY_NONE:
+ *   Files loaded from the given directory get no extra permissions.
+ * @E_DBUS_LOAD_DIRECTORY_WRITABLE:
+ *   Files loaded from the given directory are writable.
+ * @E_DBUS_LOAD_DIRECTORY_REMOVABLE:
+ *   Files loaded from the given directory are removable.
+ *
+ * Permission flags used in e_dbus_source_server_load_directory().
+ *
+ * Since: 3.6
+ **/
+typedef enum { /*< flags >*/
+	E_DBUS_LOAD_DIRECTORY_NONE      = 0,
+	E_DBUS_LOAD_DIRECTORY_WRITABLE  = 1 << 0,
+	E_DBUS_LOAD_DIRECTORY_REMOVABLE = 1 << 1
+} EDBusLoadDirectoryFlags;
+
+/**
+ * EDBusSourceAuthenticatorResult:
+ * @E_DBUS_SOURCE_AUTHENTICATOR_SUCCESSFUL:
+ *   Client reported successful authentication.
+ * @E_DBUS_SOURCE_AUTHENTICATOR_DISMISSED:
+ *   User dismissed the authentication prompt.
+ * @E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED:
+ *   Client cancelled or failed to respond.
+ *
+ * Completion codes used by #EDBusSourceAuthenticator.
+ *
+ * Since: 3.6
+ **/
+typedef enum {
+	E_DBUS_SOURCE_AUTHENTICATOR_SUCCESSFUL,
+	E_DBUS_SOURCE_AUTHENTICATOR_DISMISSED,
+	E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED
+} EDBusSourceAuthenticatorResult;
+
+/**
+ * EPasswordRememberType:
+ * @E_PASSWORD_REMEMBER_NONE:
+ *    Do not remember the user's password.
+ * @E_PASSWORD_REMEMBER_SESSION:
+ *    Store the user's password in the session keyring.
+ * @E_PASSWORD_REMEMBER_FOREVER:
+ *    Store the user's password in the default keyring.
+ *
+ * Keyring storage options for user passwords.
+ *
+ * Since: 3.6
+ **/
+typedef enum {
+	E_PASSWORD_REMEMBER_NONE,
+	E_PASSWORD_REMEMBER_SESSION,
+	E_PASSWORD_REMEMBER_FOREVER
+} EPasswordRememberType;
+
+#endif /* E_BACKEND_ENUMS_H */
diff --git a/libebackend/e-dbus-server.c b/libebackend/e-dbus-server.c
index 36910de..68470e1 100644
--- a/libebackend/e-dbus-server.c
+++ b/libebackend/e-dbus-server.c
@@ -31,7 +31,6 @@
 #endif
 
 #include <libebackend/e-module.h>
-#include <libebackend/e-extensible.h>
 
 #define E_DBUS_SERVER_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
@@ -60,9 +59,8 @@ enum {
 
 static guint signals[LAST_SIGNAL];
 
-G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
-	EDBusServer, e_dbus_server, G_TYPE_OBJECT,
-	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
+G_DEFINE_ABSTRACT_TYPE (
+	EDBusServer, e_dbus_server, G_TYPE_OBJECT)
 
 static void
 dbus_server_bus_acquired_cb (GDBusConnection *connection,
diff --git a/libebackend/e-dbus-source-authenticator.c b/libebackend/e-dbus-source-authenticator.c
new file mode 100644
index 0000000..c7c0702
--- /dev/null
+++ b/libebackend/e-dbus-source-authenticator.c
@@ -0,0 +1,1263 @@
+/*
+ * e-dbus-source-authenticator.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-dbus-source-authenticator.h"
+
+/* XXX Yeah, yeah... */
+#define GCR_API_SUBJECT_TO_CHANGE
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gcr/gcr-base.h>
+
+#include <libebackend/e-backend-enumtypes.h>
+#include <libebackend/e-dbus-source-server.h>
+#include <libebackend/e-server-side-source.h>
+
+#define E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_DBUS_SOURCE_AUTHENTICATOR, EDBusSourceAuthenticatorPrivate))
+
+/* How long should clients have to respond before timing out the
+ * authentication session?  Need to balance allowing adequate time
+ * without blocking other requests too long if a client goes dark. */
+#define INACTIVITY_TIMEOUT (3 * 60)  /* in seconds */
+
+#define KEYRING_ITEM_ATTRIBUTE_NAME	"e-source-uid"
+#define KEYRING_ITEM_DISPLAY_FORMAT	"Evolution Data Source %s"
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EDBusSourceAuthenticatorPrivate {
+	GcrSecretExchange *secret_exchange;
+	EDBusSourceServer *server;
+	gboolean client_ready;
+	gboolean server_ready;
+	gchar *source_uid;
+	guint timeout_id;
+
+	/* These are for building authentication prompts.
+	 * The strings are in Pango's text markup language. */
+	GIcon *icon;
+	gchar *primary_markup;
+	gchar *secondary_markup;
+};
+
+struct _AsyncContext {
+	gchar *password;
+	gboolean permanently;
+};
+
+enum {
+	PROP_0,
+	PROP_ICON,
+	PROP_PRIMARY_MARKUP,
+	PROP_SECONDARY_MARKUP,
+	PROP_SERVER,
+	PROP_SOURCE_UID
+};
+
+enum {
+	COMPLETE,
+	LAST_SIGNAL
+};
+
+static GnomeKeyringPasswordSchema schema = {
+	GNOME_KEYRING_ITEM_GENERIC_SECRET,
+	{
+		{ KEYRING_ITEM_ATTRIBUTE_NAME,
+		  GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+		{ NULL, 0 }
+	}
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* Forward Declarations */
+static void	dbus_source_authenticator_msg
+				(EDBusSourceAuthenticator *authenticator,
+				 const gchar *format,
+				 ...) G_GNUC_PRINTF (2, 3);
+
+G_DEFINE_ABSTRACT_TYPE (
+	EDBusSourceAuthenticator,
+	e_dbus_source_authenticator,
+	E_DBUS_TYPE_AUTHENTICATOR_SKELETON)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	g_free (async_context->password);
+	g_slice_free (AsyncContext, async_context);
+}
+
+static void
+dbus_source_authenticator_msg (EDBusSourceAuthenticator *authenticator,
+                               const gchar *format,
+                               ...)
+{
+	GString *buffer;
+	const gchar *object_path;
+	va_list args;
+
+	buffer = g_string_sized_new (256);
+
+	/* Show a truncated object path. */
+	object_path = g_dbus_interface_skeleton_get_object_path (
+		G_DBUS_INTERFACE_SKELETON (authenticator));
+	object_path += strlen (E_DBUS_SOURCE_SERVER_OBJECT_PATH);
+
+	g_string_append_printf (buffer, "AUTH (%s): ", object_path);
+
+	va_start (args, format);
+	g_string_append_vprintf (buffer, format, args);
+	va_end (args);
+
+	g_print ("%s\n", buffer->str);
+
+	g_string_free (buffer, TRUE);
+}
+
+static void
+dbus_source_authenticator_maybe_initiate (EDBusSourceAuthenticator *authenticator)
+{
+	EDBusSourceAuthenticatorClass *class;
+
+	if (!authenticator->priv->client_ready)
+		return;
+
+	if (!authenticator->priv->server_ready)
+		return;
+
+	class = E_DBUS_SOURCE_AUTHENTICATOR_GET_CLASS (authenticator);
+	g_return_if_fail (class->initiate != NULL);
+
+	dbus_source_authenticator_msg (authenticator, "Initiated");
+
+	class->initiate (authenticator);
+}
+
+static gboolean
+dbus_source_authenticator_timeout_cb (EDBusSourceAuthenticator *authenticator)
+{
+	EDBusSourceAuthenticatorResult result;
+
+	authenticator->priv->timeout_id = 0;
+
+	dbus_source_authenticator_msg (authenticator, "Timed out");
+
+	result = E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED;
+	e_dbus_source_authenticator_complete (authenticator, result);
+
+	return FALSE;
+}
+
+static gboolean
+dbus_source_authenticator_handle_cancel (EDBusAuthenticator *authenticator,
+                                         GDBusMethodInvocation *invocation)
+{
+	dbus_source_authenticator_msg (
+		E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+		"Client cancelled");
+
+	e_dbus_source_authenticator_complete (
+		E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+		E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED);
+
+	e_dbus_authenticator_complete_cancel (authenticator, invocation);
+
+	return TRUE;
+}
+
+static gboolean
+dbus_source_authenticator_handle_ready (EDBusAuthenticator *authenticator,
+                                        GDBusMethodInvocation *invocation,
+                                        const gchar *encryption_key)
+{
+	EDBusSourceAuthenticatorPrivate *priv;
+
+	priv = E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	gcr_secret_exchange_receive (priv->secret_exchange, encryption_key);
+
+	/* This method is idempotent. */
+	if (!priv->client_ready) {
+		priv->client_ready = TRUE;
+		dbus_source_authenticator_maybe_initiate (
+			E_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+	}
+
+	e_dbus_authenticator_complete_ready (authenticator, invocation);
+
+	return TRUE;
+}
+
+static gboolean
+dbus_source_authenticator_cancel_timeout (EDBusAuthenticator *authenticator,
+                                          GDBusMethodInvocation *invocation)
+{
+	EDBusSourceAuthenticatorPrivate *priv;
+
+	priv = E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	/* Client responded, cancel the timeout. */
+	if (priv->timeout_id > 0) {
+		g_source_remove (priv->timeout_id);
+		priv->timeout_id = 0;
+	}
+
+	/* This signal handler does NOT handle the method invocation. */
+	return FALSE;
+}
+
+static void
+dbus_source_authenticator_start_timeout (EDBusAuthenticator *authenticator)
+{
+	EDBusSourceAuthenticatorPrivate *priv;
+
+	priv = E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	/* An authentication session starts when a D-Bus object path is
+	 * exported with an Authenticator interface (that's us) and the
+	 * client calls its 'Ready' method.  The session then ping pongs
+	 * between 'Authenticate' signals TO the client and 'Accepted'
+	 * or 'Rejected' method calls FROM the client.
+	 *
+	 * For each 'ping' we start a countdown timer on the order of
+	 * several minutes.  If the client does not 'pong' (respond to)
+	 * us before the countdown timer expires, we assume the client
+	 * has terminated and we close the authentication session. */
+
+	/* Timer should not already be running. */
+	g_warn_if_fail (priv->timeout_id == 0);
+
+	priv->timeout_id = g_timeout_add_seconds (
+		INACTIVITY_TIMEOUT, (GSourceFunc)
+		dbus_source_authenticator_timeout_cb,
+		authenticator);
+}
+
+static void
+dbus_source_authenticator_set_server (EDBusSourceAuthenticator *authenticator,
+                                      EDBusSourceServer *server)
+{
+	g_return_if_fail (E_IS_DBUS_SOURCE_SERVER (server));
+	g_return_if_fail (authenticator->priv->server == NULL);
+
+	authenticator->priv->server = g_object_ref (server);
+}
+
+static void
+dbus_source_authenticator_set_source_uid (EDBusSourceAuthenticator *authenticator,
+                                          const gchar *source_uid)
+{
+	g_return_if_fail (source_uid != NULL);
+	g_return_if_fail (authenticator->priv->source_uid == NULL);
+
+	authenticator->priv->source_uid = g_strdup (source_uid);
+}
+
+static void
+dbus_source_authenticator_set_property (GObject *object,
+                                        guint property_id,
+                                        const GValue *value,
+                                        GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ICON:
+			e_dbus_source_authenticator_set_icon (
+				E_DBUS_SOURCE_AUTHENTICATOR (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_PRIMARY_MARKUP:
+			e_dbus_source_authenticator_set_primary_markup (
+				E_DBUS_SOURCE_AUTHENTICATOR (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_SECONDARY_MARKUP:
+			e_dbus_source_authenticator_set_secondary_markup (
+				E_DBUS_SOURCE_AUTHENTICATOR (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_SERVER:
+			dbus_source_authenticator_set_server (
+				E_DBUS_SOURCE_AUTHENTICATOR (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SOURCE_UID:
+			dbus_source_authenticator_set_source_uid (
+				E_DBUS_SOURCE_AUTHENTICATOR (object),
+				g_value_get_string (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+dbus_source_authenticator_get_property (GObject *object,
+                                        guint property_id,
+                                        GValue *value,
+                                        GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ICON:
+			g_value_set_object (
+				value,
+				e_dbus_source_authenticator_get_icon (
+				E_DBUS_SOURCE_AUTHENTICATOR (object)));
+			return;
+
+		case PROP_PRIMARY_MARKUP:
+			g_value_set_string (
+				value,
+				e_dbus_source_authenticator_get_primary_markup (
+				E_DBUS_SOURCE_AUTHENTICATOR (object)));
+			return;
+
+		case PROP_SECONDARY_MARKUP:
+			g_value_set_string (
+				value,
+				e_dbus_source_authenticator_get_secondary_markup (
+				E_DBUS_SOURCE_AUTHENTICATOR (object)));
+			return;
+
+		case PROP_SERVER:
+			g_value_set_object (
+				value,
+				e_dbus_source_authenticator_get_server (
+				E_DBUS_SOURCE_AUTHENTICATOR (object)));
+			return;
+
+		case PROP_SOURCE_UID:
+			g_value_set_string (
+				value,
+				e_dbus_source_authenticator_get_source_uid (
+				E_DBUS_SOURCE_AUTHENTICATOR (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+dbus_source_authenticator_dispose (GObject *object)
+{
+	EDBusSourceAuthenticatorPrivate *priv;
+
+	priv = E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (object);
+
+	if (priv->secret_exchange != NULL) {
+		g_object_unref (priv->secret_exchange);
+		priv->secret_exchange = NULL;
+	}
+
+	if (priv->server != NULL) {
+		g_object_unref (priv->server);
+		priv->server = NULL;
+	}
+
+	if (priv->icon != NULL) {
+		g_object_unref (priv->icon);
+		priv->icon = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_dbus_source_authenticator_parent_class)->dispose (object);
+}
+
+static void
+dbus_source_authenticator_finalize (GObject *object)
+{
+	EDBusSourceAuthenticatorPrivate *priv;
+
+	priv = E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (object);
+
+	if (priv->timeout_id > 0)
+		g_source_remove (priv->timeout_id);
+
+	g_free (priv->source_uid);
+	g_free (priv->primary_markup);
+	g_free (priv->secondary_markup);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_dbus_source_authenticator_parent_class)->finalize (object);
+}
+
+static void
+dbus_source_authenticator_initiate (EDBusSourceAuthenticator *authenticator)
+{
+	/* This function is just a placeholder so subclasses can safely
+	 * chain up.  We might want to do something here in the future. */
+}
+
+static void
+dbus_source_authenticator_complete (EDBusSourceAuthenticator *authenticator,
+                                    EDBusSourceAuthenticatorResult result)
+{
+	/* This function is just a placeholder so subclasses can safely
+	 * chain up.  We might want to do something here in the future. */
+}
+
+static void
+e_dbus_source_authenticator_class_init (EDBusSourceAuthenticatorClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (
+		class, sizeof (EDBusSourceAuthenticatorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = dbus_source_authenticator_set_property;
+	object_class->get_property = dbus_source_authenticator_get_property;
+	object_class->dispose = dbus_source_authenticator_dispose;
+	object_class->finalize = dbus_source_authenticator_finalize;
+
+	class->initiate = dbus_source_authenticator_initiate;
+	class->complete = dbus_source_authenticator_complete;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ICON,
+		g_param_spec_object (
+			"icon",
+			"Icon",
+			"A GIcon to use in authentication prompts",
+			G_TYPE_ICON,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PRIMARY_MARKUP,
+		g_param_spec_string (
+			"primary-markup",
+			"Primary Markup",
+			"Primary label to use in authentication "
+			"prompts, in Pango's text markup language",
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SECONDARY_MARKUP,
+		g_param_spec_string (
+			"secondary-markup",
+			"Secondary Markup",
+			"Secondary label to use in authentication "
+			"prompts, in Pango's text markup language",
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SERVER,
+		g_param_spec_object (
+			"server",
+			"Server",
+			"The server to which the authenticator belongs",
+			E_TYPE_DBUS_SOURCE_SERVER,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE_UID,
+		g_param_spec_string (
+			"source-uid",
+			"Source UID",
+			"Unique ID of the data source being authenticated",
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	signals[COMPLETE] = g_signal_new (
+		"complete",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EDBusSourceAuthenticatorClass, complete),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__ENUM,
+		G_TYPE_NONE, 1,
+		E_TYPE_DBUS_SOURCE_AUTHENTICATOR_RESULT);
+}
+
+static void
+e_dbus_source_authenticator_init (EDBusSourceAuthenticator *authenticator)
+{
+	authenticator->priv =
+		E_DBUS_SOURCE_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	authenticator->priv->secret_exchange = gcr_secret_exchange_new (NULL);
+
+	/* Start the timeout immediately. */
+
+	dbus_source_authenticator_start_timeout (
+		E_DBUS_AUTHENTICATOR (authenticator));
+
+	/* The authenticate signal also starts the timeout. */
+
+	g_signal_connect (
+		authenticator, "authenticate",
+		G_CALLBACK (dbus_source_authenticator_start_timeout), NULL);
+
+	/* All method invocations cancel the timeout. */
+
+	g_signal_connect (
+		authenticator, "handle-cancel",
+		G_CALLBACK (dbus_source_authenticator_cancel_timeout), NULL);
+
+	g_signal_connect (
+		authenticator, "handle-ready",
+		G_CALLBACK (dbus_source_authenticator_cancel_timeout), NULL);
+
+	g_signal_connect (
+		authenticator, "handle-accepted",
+		G_CALLBACK (dbus_source_authenticator_cancel_timeout), NULL);
+
+	g_signal_connect (
+		authenticator, "handle-rejected",
+		G_CALLBACK (dbus_source_authenticator_cancel_timeout), NULL);
+
+	/* These method invocations do additional stuff. */
+
+	g_signal_connect (
+		authenticator, "handle-cancel",
+		G_CALLBACK (dbus_source_authenticator_handle_cancel), NULL);
+
+	g_signal_connect (
+		authenticator, "handle-ready",
+		G_CALLBACK (dbus_source_authenticator_handle_ready), NULL);
+}
+
+GQuark
+e_dbus_source_authenticator_error_quark (void)
+{
+	static GQuark quark = 0;
+
+	if (G_UNLIKELY (quark == 0)) {
+		const gchar *string;
+		string = "e-dbus-source-authenticator-error-quark";
+		quark = g_quark_from_static_string (string);
+	}
+
+	return quark;
+}
+
+EDBusSourceServer *
+e_dbus_source_authenticator_get_server (EDBusSourceAuthenticator *authenticator)
+{
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+	return authenticator->priv->server;
+}
+
+const gchar *
+e_dbus_source_authenticator_get_source_uid (EDBusSourceAuthenticator *authenticator)
+{
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+	return authenticator->priv->source_uid;
+}
+
+GIcon *
+e_dbus_source_authenticator_get_icon (EDBusSourceAuthenticator *authenticator)
+{
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+	return authenticator->priv->icon;
+}
+
+void
+e_dbus_source_authenticator_set_icon (EDBusSourceAuthenticator *authenticator,
+                                      GIcon *icon)
+{
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	if (icon != NULL) {
+		g_return_if_fail (G_IS_ICON (icon));
+		g_object_ref (icon);
+	}
+
+	if (authenticator->priv->icon != NULL)
+		g_object_unref (authenticator->priv->icon);
+
+	authenticator->priv->icon = icon;
+
+	g_object_notify (G_OBJECT (authenticator), "icon");
+}
+
+const gchar *
+e_dbus_source_authenticator_get_primary_markup (EDBusSourceAuthenticator *authenticator)
+{
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+	return authenticator->priv->primary_markup;
+}
+
+void
+e_dbus_source_authenticator_set_primary_markup (EDBusSourceAuthenticator *authenticator,
+                                                const gchar *primary_markup)
+{
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	g_free (authenticator->priv->primary_markup);
+	authenticator->priv->primary_markup = g_strdup (primary_markup);
+
+	g_object_notify (G_OBJECT (authenticator), "primary-markup");
+}
+
+const gchar *
+e_dbus_source_authenticator_get_secondary_markup (EDBusSourceAuthenticator *authenticator)
+{
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+	return authenticator->priv->secondary_markup;
+}
+
+void
+e_dbus_source_authenticator_set_secondary_markup (EDBusSourceAuthenticator *authenticator,
+                                                  const gchar *secondary_markup)
+{
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	g_free (authenticator->priv->secondary_markup);
+	authenticator->priv->secondary_markup = g_strdup (secondary_markup);
+
+	g_object_notify (G_OBJECT (authenticator), "secondary-markup");
+}
+
+void
+e_dbus_source_authenticator_initiate (EDBusSourceAuthenticator *authenticator)
+{
+	/* Note, we delay invoking the class method until the client
+	 * has also indicated it's ready.  This way the subclass can
+	 * start taking action directly in its initiate() method. */
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	/* This function is idempotent. */
+	if (!authenticator->priv->server_ready) {
+		authenticator->priv->server_ready = TRUE;
+		dbus_source_authenticator_maybe_initiate (authenticator);
+	}
+}
+
+void
+e_dbus_source_authenticator_dismiss (EDBusSourceAuthenticator *authenticator)
+{
+	EDBusSourceServer *server;
+	ESource *source;
+	const gchar *uid;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	dbus_source_authenticator_msg (authenticator, "Dismissed");
+
+	e_dbus_authenticator_emit_dismissed (
+		E_DBUS_AUTHENTICATOR (authenticator));
+
+	server = e_dbus_source_authenticator_get_server (authenticator);
+	uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+
+	/* This will return NULL if the authenticating data source
+	 * has not yet been submitted to the D-Bus registry service. */
+	source = e_dbus_source_server_lookup_by_uid (server, uid);
+	if (source != NULL) {
+		/* Prevent further user interruptions until this is reset. */
+		e_server_side_source_set_allow_auth_prompt (
+			E_SERVER_SIDE_SOURCE (source), FALSE);
+	}
+
+	/* XXX This may finalize the authenticator. Wonder if we
+	 *     should defer this call to an idle source to avoid
+	 *     potential lifecycle issues with signal handlers. */
+	e_dbus_source_authenticator_complete (
+		authenticator, E_DBUS_SOURCE_AUTHENTICATOR_DISMISSED);
+}
+
+void
+e_dbus_source_authenticator_transmit (EDBusSourceAuthenticator *authenticator,
+                                      const gchar *unencrypted_secret)
+{
+	gchar *encrypted_secret;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	encrypted_secret = gcr_secret_exchange_send (
+		authenticator->priv->secret_exchange, unencrypted_secret, -1);
+
+	dbus_source_authenticator_msg (authenticator, "Authenticate");
+
+	e_dbus_authenticator_emit_authenticate (
+		E_DBUS_AUTHENTICATOR (authenticator), encrypted_secret);
+
+	g_free (encrypted_secret);
+}
+
+void
+e_dbus_source_authenticator_complete (EDBusSourceAuthenticator *authenticator,
+                                      EDBusSourceAuthenticatorResult result)
+{
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	switch (result) {
+		case E_DBUS_SOURCE_AUTHENTICATOR_SUCCESSFUL:
+			dbus_source_authenticator_msg (
+				authenticator, "Complete (SUCCESSFUL)");
+			break;
+		case E_DBUS_SOURCE_AUTHENTICATOR_DISMISSED:
+			dbus_source_authenticator_msg (
+				authenticator, "Complete (DISMISSED)");
+			break;
+		case E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED:
+			dbus_source_authenticator_msg (
+				authenticator, "Complete (CANCELLED)");
+			break;
+		default:
+			g_warn_if_reached ();
+	}
+
+	g_signal_emit (authenticator, signals[COMPLETE], 0, result);
+}
+
+/* Helper for e_dbus_source_authenticator_store_password() */
+static void
+dbus_source_authenticator_store_password_thread (GSimpleAsyncResult *simple,
+                                                 GObject *object,
+                                                 GCancellable *cancellable)
+{
+	AsyncContext *async_context;
+	GError *error = NULL;
+
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	e_dbus_source_authenticator_store_password_sync (
+		E_DBUS_SOURCE_AUTHENTICATOR (object),
+		async_context->password,
+		async_context->permanently,
+		cancellable, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_dbus_source_authenticator_store_sync:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @password: the password to store
+ * @permanently: store permanently or just for the session
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Store a password for the data source that @authenticator is representing.
+ * If @permanently is %TRUE, the password is stored in the default keyring.
+ * Otherwise the password is stored in the memory-only session keyring.  If
+ * an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_authenticator_store_password_sync (EDBusSourceAuthenticator *authenticator,
+                                                 const gchar *password,
+                                                 gboolean permanently,
+                                                 GCancellable *cancellable,
+                                                 GError **error)
+{
+	GnomeKeyringResult result;
+	const gchar *keyring;
+	const gchar *uid;
+	gchar *display_name;
+
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), FALSE);
+	g_return_val_if_fail (password != NULL, FALSE);
+
+	/* XXX Synchronous gnome-keyring functions are not cancellable.
+	 *     Maybe they will be someday, but in the meantime check for
+	 *     cancellation ourselves before doing this. */
+	if (g_cancellable_set_error_if_cancelled (cancellable, error))
+		return FALSE;
+
+	if (permanently)
+		keyring = GNOME_KEYRING_DEFAULT;
+	else
+		keyring = GNOME_KEYRING_SESSION;
+
+	uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+	display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
+
+	result = gnome_keyring_store_password_sync (
+		&schema, keyring, display_name, password,
+		KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+
+	if (result == GNOME_KEYRING_RESULT_CANCELLED) {
+		g_cancellable_cancel (cancellable);
+		g_set_error_literal (
+			error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+			_("Keyring operation was cancelled"));
+
+	} else if (result != GNOME_KEYRING_RESULT_OK) {
+		g_set_error_literal (
+			error, E_DBUS_SOURCE_AUTHENTICATOR_ERROR,
+			result, gnome_keyring_result_to_message (result));
+	}
+
+	g_free (display_name);
+
+	return (result == GNOME_KEYRING_RESULT_OK);
+}
+
+/**
+ * e_dbus_source_authenticator_store_password:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @password: the password to store
+ * @permanently: store permanently or just for the session
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously stores a password for the data source that @authenticator
+ * is representing.  If @permanently is %TRUE, the password is stored in the
+ * default keyring.  Otherwise the password is stored in the memory-only
+ * session keyring.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_dbus_source_authenticator_store_password_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_authenticator_store_password (EDBusSourceAuthenticator *authenticator,
+                                            const gchar *password,
+                                            gboolean permanently,
+                                            gint io_priority,
+                                            GCancellable *cancellable,
+                                            GAsyncReadyCallback callback,
+                                            gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+	g_return_if_fail (password != NULL);
+
+	async_context = g_slice_new0 (AsyncContext);
+	async_context->password = g_strdup (password);
+	async_context->permanently = permanently;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (authenticator), callback, user_data,
+		e_dbus_source_authenticator_store_password);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, async_context, (GDestroyNotify) async_context_free);
+
+	g_simple_async_result_run_in_thread (
+		simple, dbus_source_authenticator_store_password_thread,
+		io_priority, cancellable);
+
+	g_object_unref (simple);
+}
+
+/**
+ * e_dbus_source_authenticator_store_password_finish:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finished the operation started with
+ * e_dbus_source_authenticator_store_password().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_authenticator_store_password_finish (EDBusSourceAuthenticator *authenticator,
+                                                   GAsyncResult *result,
+                                                   GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (authenticator),
+		e_dbus_source_authenticator_store_password), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
+/* Helper for e_dbus_source_authenticator_store_password() */
+static void
+dbus_source_authenticator_lookup_password_thread (GSimpleAsyncResult *simple,
+                                                  GObject *object,
+                                                  GCancellable *cancellable)
+{
+	AsyncContext *async_context;
+	GError *error = NULL;
+
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	e_dbus_source_authenticator_lookup_password_sync (
+		E_DBUS_SOURCE_AUTHENTICATOR (object), cancellable,
+		&async_context->password, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_dbus_source_authenticator_lookup_password_sync:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @password: return location for the password, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Looks up a password for the data source that @authenticator is
+ * representing.  Both the default and session keyrings are queried.
+ *
+ * Note the boolean return value indicates whether the lookup operation
+ * itself completed successfully, not whether a password was found.  If
+ * no password was found, the function will set @password to %NULL but
+ * still return %TRUE.  If an error occurs, the function sets @error
+ * and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_authenticator_lookup_password_sync (EDBusSourceAuthenticator *authenticator,
+                                                  GCancellable *cancellable,
+                                                  gchar **password,
+                                                  GError **error)
+{
+	GnomeKeyringResult result;
+	const gchar *uid;
+	gchar *temp = NULL;
+
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), FALSE);
+
+	/* XXX Synchronous gnome-keyring functions are not cancellable.
+	 *     Maybe they will be someday, but in the meantime check for
+	 *     cancellation ourselves before doing this. */
+	if (g_cancellable_set_error_if_cancelled (cancellable, error))
+		return FALSE;
+
+	uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+
+	result = gnome_keyring_find_password_sync (
+		&schema, &temp, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+
+	/* Not finding a data source password is not an error. */
+	if (result == GNOME_KEYRING_RESULT_NO_MATCH) {
+		result = GNOME_KEYRING_RESULT_OK;
+		gnome_keyring_free_password (temp);
+		temp = NULL;
+
+	} else if (result == GNOME_KEYRING_RESULT_CANCELLED) {
+		g_cancellable_cancel (cancellable);
+		g_set_error_literal (
+			error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+			_("Keyring operation was cancelled"));
+		g_warn_if_fail (temp == NULL);
+
+	} else if (result != GNOME_KEYRING_RESULT_OK) {
+		g_set_error_literal (
+			error, E_DBUS_SOURCE_AUTHENTICATOR_ERROR,
+			result, gnome_keyring_result_to_message (result));
+		g_warn_if_fail (temp == NULL);
+	}
+
+	/* Do not impose gnome-keyring's non-pageable memory API on the
+	 * caller, it's not worth the hassle.  Return a newly-allocated
+	 * string so the caller can free it with g_free(). */
+	if (password != NULL)
+		*password = g_strdup (temp);
+
+	if (temp != NULL)
+		gnome_keyring_free_password (temp);
+
+	return (result == GNOME_KEYRING_RESULT_OK);
+}
+
+/**
+ * e_dbus_source_authenticator_lookup_password:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously looks up a password for the data source that @authenticator
+ * is representing.  Both the default and session keyrings are queried.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_dbus_source_authenticator_lookup_password_finish() to get the
+ * result of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_authenticator_lookup_password (EDBusSourceAuthenticator *authenticator,
+                                             gint io_priority,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	async_context = g_slice_new0 (AsyncContext);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (authenticator), callback, user_data,
+		e_dbus_source_authenticator_lookup_password);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, async_context, (GDestroyNotify) async_context_free);
+
+	g_simple_async_result_run_in_thread (
+		simple, dbus_source_authenticator_lookup_password_thread,
+		io_priority, cancellable);
+
+	g_object_unref (simple);
+}
+
+/**
+ * e_dbus_source_authenticator_lookup_password_finish:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @result: a #GAsyncResult
+ * @password: return location for the password, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * e_dbus_source_authenticator_lookup_password().
+ *
+ * Note the boolean return value indicates whether the lookup operation
+ * itself completed successfully, not whether a password was found.  If
+ * no password was found, the function will set @password to %NULL but
+ * still return %TRUE.  If an error occurs, the function sets @error
+ * and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_authenticator_lookup_password_finish (EDBusSourceAuthenticator *authenticator,
+                                                    GAsyncResult *result,
+                                                    gchar **password,
+                                                    GError **error)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (authenticator),
+		e_dbus_source_authenticator_lookup_password), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return FALSE;
+
+	if (password != NULL) {
+		*password = async_context->password;
+		async_context->password = NULL;
+	}
+
+	return TRUE;
+}
+
+/* Helper for e_dbus_source_authenticator_delete_password() */
+static void
+dbus_source_authenticator_delete_password_thread (GSimpleAsyncResult *simple,
+                                                  GObject *object,
+                                                  GCancellable *cancellable)
+{
+	GError *error = NULL;
+
+	e_dbus_source_authenticator_delete_password_sync (
+		E_DBUS_SOURCE_AUTHENTICATOR (object), cancellable, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+}
+
+/**
+ * e_dbus_source_authenticator_delete_password_sync:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Deletes the password for the data source that @authenticator is
+ * representing from either the default keyring or session keyring.
+ *
+ * Note the boolean return value indicates whether the delete operation
+ * itself completed successfully, not whether a password was found and
+ * deleted.  If no password was found, the function will still return
+ * %TRUE.  If an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_authenticator_delete_password_sync (EDBusSourceAuthenticator *authenticator,
+                                                  GCancellable *cancellable,
+                                                  GError **error)
+{
+	GnomeKeyringResult result;
+	const gchar *uid;
+
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), FALSE);
+
+	/* XXX Synchronous gnome-keyring functions are not cancellable.
+	 *     Maybe they will be someday, but in the meantime check for
+	 *     cancellation ourselves before doing this. */
+	if (g_cancellable_set_error_if_cancelled (cancellable, error))
+		return FALSE;
+
+	uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+
+	result = gnome_keyring_delete_password_sync (
+		&schema, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+
+	/* Not finding a data source password is not an error. */
+	if (result == GNOME_KEYRING_RESULT_NO_MATCH) {
+		result = GNOME_KEYRING_RESULT_OK;
+
+	} else if (result == GNOME_KEYRING_RESULT_CANCELLED) {
+		g_cancellable_cancel (cancellable);
+		g_set_error_literal (
+			error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+			_("Keyring operation was cancelled"));
+
+	} else if (result != GNOME_KEYRING_RESULT_OK) {
+		g_set_error_literal (
+			error, E_DBUS_SOURCE_AUTHENTICATOR_ERROR,
+			result, gnome_keyring_result_to_message (result));
+	}
+
+	return (result == GNOME_KEYRING_RESULT_OK);
+}
+
+/**
+ * e_dbus_source_authenticator_delete_password:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asyncronously deletes the password for the data source that @authenticator
+ * is representing from either the default keyring or session keyring.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_dbus_source_authenticator_delete_password_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_authenticator_delete_password (EDBusSourceAuthenticator *authenticator,
+                                             gint io_priority,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (authenticator), callback, user_data,
+		e_dbus_source_authenticator_delete_password);
+
+	g_simple_async_result_run_in_thread (
+		simple, dbus_source_authenticator_delete_password_thread,
+		io_priority, cancellable);
+
+	g_object_unref (simple);
+}
+
+/**
+ * e_dbus_source_authenticator_delete_password_finish:
+ * @authenticator: an #EDBusSourceAuthenticator
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * e_dbus_source_authenticator_delete_password().
+ *
+ * Note the boolean return value indicates whether the delete operation
+ * itself completed successfully, not whether a password was found and
+ * deleted.  If no password was found, the function will still return
+ * %TRUE.  If an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_authenticator_delete_password_finish (EDBusSourceAuthenticator *authenticator,
+                                                    GAsyncResult *result,
+                                                    GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (authenticator),
+		e_dbus_source_authenticator_delete_password), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
diff --git a/libebackend/e-dbus-source-authenticator.h b/libebackend/e-dbus-source-authenticator.h
new file mode 100644
index 0000000..551703b
--- /dev/null
+++ b/libebackend/e-dbus-source-authenticator.h
@@ -0,0 +1,170 @@
+/*
+ * e-dbus-source-authenticator.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_DBUS_SOURCE_AUTHENTICATOR_H
+#define E_DBUS_SOURCE_AUTHENTICATOR_H
+
+/* This needs to be in the public header since we're
+ * reusing the GnomeKeyringResult enum for error codes. */
+#include <gnome-keyring.h>
+
+#include <libebackend/e-backend-enums.h>
+#include <libedataserver/e-dbus-authenticator.h>
+
+/* Standard GObject macros */
+#define E_TYPE_DBUS_SOURCE_AUTHENTICATOR \
+	(e_dbus_source_authenticator_get_type ())
+#define E_DBUS_SOURCE_AUTHENTICATOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_DBUS_SOURCE_AUTHENTICATOR, EDBusSourceAuthenticator))
+#define E_DBUS_SOURCE_AUTHENTICATOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_DBUS_SOURCE_AUTHENTICATOR, EDBusSourceAuthenticatorClass))
+#define E_IS_DBUS_SOURCE_AUTHENTICATOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_DBUS_SOURCE_AUTHENTICATOR))
+#define E_IS_DBUS_SOURCE_AUTHENTICATOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_DBUS_SOURCE_AUTHENTICATOR))
+#define E_DBUS_SOURCE_AUTHENTICATOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_DBUS_SOURCE_AUTHENTICATOR, EDBusSourceAuthenticatorClass))
+
+/**
+ * E_DBUS_SOURCE_AUTHENTICATOR_ERROR:
+ *
+ * Error domain for password storage and retrieval.  Error codes in this
+ * domain are defined by the #GnomeKeyringResult enumeration.  See #GError
+ * for information on error domains.
+ *
+ * Since: 3.6
+ **/
+#define E_DBUS_SOURCE_AUTHENTICATOR_ERROR \
+	(e_dbus_source_authenticator_error_quark ())
+
+G_BEGIN_DECLS
+
+struct _EDBusSourceServer;
+
+typedef struct _EDBusSourceAuthenticator EDBusSourceAuthenticator;
+typedef struct _EDBusSourceAuthenticatorClass EDBusSourceAuthenticatorClass;
+typedef struct _EDBusSourceAuthenticatorPrivate EDBusSourceAuthenticatorPrivate;
+
+struct _EDBusSourceAuthenticator {
+	EDBusAuthenticatorSkeleton parent;
+	EDBusSourceAuthenticatorPrivate *priv;
+};
+
+struct _EDBusSourceAuthenticatorClass {
+	EDBusAuthenticatorSkeletonClass parent_class;
+
+	/* Methods */
+	void	(*initiate)	(EDBusSourceAuthenticator *authenticator);
+
+	/* Signals */
+	void	(*complete)	(EDBusSourceAuthenticator *authenticator,
+				 EDBusSourceAuthenticatorResult result);
+};
+
+GQuark		e_dbus_source_authenticator_error_quark
+				(void) G_GNUC_CONST;
+GType		e_dbus_source_authenticator_get_type
+				(void) G_GNUC_CONST;
+struct _EDBusSourceServer *
+		e_dbus_source_authenticator_get_server
+				(EDBusSourceAuthenticator *authenticator);
+const gchar *	e_dbus_source_authenticator_get_source_uid
+				(EDBusSourceAuthenticator *authenticator);
+GIcon *		e_dbus_source_authenticator_get_icon
+				(EDBusSourceAuthenticator *authenticator);
+void		e_dbus_source_authenticator_set_icon
+				(EDBusSourceAuthenticator *authenticator,
+				 GIcon *icon);
+const gchar *	e_dbus_source_authenticator_get_primary_markup
+				(EDBusSourceAuthenticator *authenticator);
+void		e_dbus_source_authenticator_set_primary_markup
+				(EDBusSourceAuthenticator *authenticator,
+				 const gchar *primary_markup);
+const gchar *	e_dbus_source_authenticator_get_secondary_markup
+				(EDBusSourceAuthenticator *authenticator);
+void		e_dbus_source_authenticator_set_secondary_markup
+				(EDBusSourceAuthenticator *authenticator,
+				 const gchar *secondary_markup);
+void		e_dbus_source_authenticator_initiate
+				(EDBusSourceAuthenticator *authenticator);
+void		e_dbus_source_authenticator_dismiss
+				(EDBusSourceAuthenticator *authenticator);
+void		e_dbus_source_authenticator_transmit
+				(EDBusSourceAuthenticator *authenticator,
+				 const gchar *unencrypted_secret);
+void		e_dbus_source_authenticator_complete
+				(EDBusSourceAuthenticator *authenticator,
+				 EDBusSourceAuthenticatorResult result);
+gboolean	e_dbus_source_authenticator_store_password_sync
+				(EDBusSourceAuthenticator *authenticator,
+				 const gchar *password,
+				 gboolean permanently,
+				 GCancellable *cancellable,
+				 GError **error);
+void		e_dbus_source_authenticator_store_password
+				(EDBusSourceAuthenticator *authenticator,
+				 const gchar *password,
+				 gboolean permanently,
+				 gint io_priority,
+				 GCancellable *cancellable,
+				 GAsyncReadyCallback callback,
+				 gpointer user_data);
+gboolean	e_dbus_source_authenticator_store_password_finish
+				(EDBusSourceAuthenticator *authenticator,
+				 GAsyncResult *result,
+				 GError **error);
+gboolean	e_dbus_source_authenticator_lookup_password_sync
+				(EDBusSourceAuthenticator *authenticator,
+				 GCancellable *cancellable,
+				 gchar **password,
+				 GError **error);
+void		e_dbus_source_authenticator_lookup_password
+				(EDBusSourceAuthenticator *authenticator,
+				 gint io_priority,
+				 GCancellable *cancellable,
+				 GAsyncReadyCallback callback,
+				 gpointer user_data);
+gboolean	e_dbus_source_authenticator_lookup_password_finish
+				(EDBusSourceAuthenticator *authenticator,
+				 GAsyncResult *result,
+				 gchar **password,
+				 GError **error);
+gboolean	e_dbus_source_authenticator_delete_password_sync
+				(EDBusSourceAuthenticator *authenticator,
+				 GCancellable *cancellable,
+				 GError **error);
+void		e_dbus_source_authenticator_delete_password
+				(EDBusSourceAuthenticator *authenticator,
+				 gint io_priority,
+				 GCancellable *cancellable,
+				 GAsyncReadyCallback callback,
+				 gpointer user_data);
+gboolean	e_dbus_source_authenticator_delete_password_finish
+				(EDBusSourceAuthenticator *authenticator,
+				 GAsyncResult *result,
+				 GError **error);
+
+G_END_DECLS
+
+#endif /* E_DBUS_SOURCE_AUTHENTICATOR_H */
+
diff --git a/libebackend/e-dbus-source-server.c b/libebackend/e-dbus-source-server.c
new file mode 100644
index 0000000..578a435
--- /dev/null
+++ b/libebackend/e-dbus-source-server.c
@@ -0,0 +1,1583 @@
+/*
+ * e-dbus-source-server.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-dbus-source-server.h"
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/e-uid.h>
+#include <libedataserver/e-marshal.h>
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-dbus-source-manager.h>
+
+#include <libebackend/e-extensible.h>
+#include <libebackend/e-server-side-source.h>
+#include <libebackend/e-dbus-source-authenticator.h>
+
+#define E_DBUS_SOURCE_SERVER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_DBUS_SOURCE_SERVER, EDBusSourceServerPrivate))
+
+#define FALLBACK_AUTH_ICON_NAME "dialog-password"
+
+struct _EDBusSourceServerPrivate {
+	GDBusObjectManagerServer *object_manager;
+	EDBusSourceManager *source_manager;
+
+	GHashTable *sources;  /* sources added to hierarchy */
+	GHashTable *orphans;  /* sources waiting for parent */
+	GHashTable *monitors;
+
+	GMutex *sources_lock;
+	GMutex *orphans_lock;
+
+	/* In pseudo-Python notation:
+	 *
+	 * auth_queue = [ UID, ... ]
+	 * auth_table = { UID : [ authenticator, ... ] }
+	 * active_auth_table_queue = auth_table[auth_queue[0]]
+	 * active_auth = active_auth_table_queue[0]
+	 *
+	 * We process all authenticators for a given source UID at once.
+	 * The thought being after the first authenticator for a given UID
+	 * completes (the first being most likely to trigger a user prompt),
+	 * then any other authenticators for that same UID should complete
+	 * quickly, hopefully without having to reprompt.  That is unless
+	 * the user decides not to cache the secret at all, in which case
+	 * he gets what he asked for: lots of annoying prompts.
+	 */
+	GQueue *auth_queue;
+	GHashTable *auth_table;
+	GQueue *active_auth_table_queue;
+	EDBusSourceAuthenticator *active_auth;
+
+	GType authenticator_type;
+	guint authentication_count;
+};
+
+enum {
+	PROP_0,
+	PROP_AUTHENTICATOR_TYPE
+};
+
+enum {
+	LOAD_ERROR,
+	FILES_LOADED,
+	SOURCE_ADDED,
+	SOURCE_REMOVED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (
+	EDBusSourceServer,
+	e_dbus_source_server,
+	E_TYPE_DBUS_SERVER,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL))
+
+/* GDestroyNotify callback for 'auth_table' values */
+static void
+free_auth_queue (GQueue *queue)
+{
+	/* XXX g_queue_clear_full() would be nice here. */
+	while (!g_queue_is_empty (queue))
+		g_object_unref (g_queue_pop_head (queue));
+
+	g_queue_free (queue);
+}
+
+static void
+dbus_source_server_sources_insert (EDBusSourceServer *server,
+                                   ESource *source)
+{
+	const gchar *uid;
+
+	uid = e_source_get_uid (source);
+	g_return_if_fail (uid != NULL);
+
+	g_mutex_lock (server->priv->sources_lock);
+
+	g_hash_table_insert (
+		server->priv->sources,
+		g_strdup (uid), g_object_ref (source));
+
+	g_mutex_unlock (server->priv->sources_lock);
+}
+
+static gboolean
+dbus_source_server_sources_remove (EDBusSourceServer *server,
+                                   ESource *source)
+{
+	const gchar *uid;
+	gboolean removed;
+
+	uid = e_source_get_uid (source);
+	g_return_val_if_fail (uid != NULL, FALSE);
+
+	g_mutex_lock (server->priv->sources_lock);
+
+	removed = g_hash_table_remove (server->priv->sources, uid);
+
+	g_mutex_unlock (server->priv->sources_lock);
+
+	return removed;
+}
+
+static GList *
+dbus_source_server_sources_get_values (EDBusSourceServer *server)
+{
+	GList *values;
+
+	g_mutex_lock (server->priv->sources_lock);
+
+	values = g_hash_table_get_values (server->priv->sources);
+
+	g_list_foreach (values, (GFunc) g_object_ref, NULL);
+
+	g_mutex_unlock (server->priv->sources_lock);
+
+	return values;
+}
+
+static void
+dbus_source_server_orphans_insert (EDBusSourceServer *server,
+                                   ESource *orphan_source)
+{
+	GHashTable *orphans;
+	GPtrArray *array;
+	gchar *parent_uid;
+
+	g_mutex_lock (server->priv->orphans_lock);
+
+	orphans = server->priv->orphans;
+
+	parent_uid = e_source_dup_parent (orphan_source);
+
+	/* A top-level object has no parent UID, so we
+	 * use a special "empty" key in the hash table. */
+	if (parent_uid == NULL)
+		parent_uid = g_strdup ("");
+
+	array = g_hash_table_lookup (orphans, parent_uid);
+
+	if (array == NULL) {
+		array = g_ptr_array_new_with_free_func (g_object_unref);
+
+		/* Takes ownership of the 'parent_uid' string. */
+		g_hash_table_insert (orphans, parent_uid, array);
+		parent_uid = NULL;
+	}
+
+	g_ptr_array_add (array, g_object_ref (orphan_source));
+
+	g_free (parent_uid);
+
+	g_mutex_unlock (server->priv->orphans_lock);
+}
+
+static gboolean
+dbus_source_server_orphans_remove (EDBusSourceServer *server,
+                                   ESource *orphan_source)
+{
+	GHashTable *orphans;
+	GPtrArray *array;
+	gchar *parent_uid;
+	gboolean removed = FALSE;
+
+	g_mutex_lock (server->priv->orphans_lock);
+
+	orphans = server->priv->orphans;
+
+	parent_uid = e_source_dup_parent (orphan_source);
+
+	/* A top-level object has no parent UID, so we
+	 * use a special "empty" key in the hash table. */
+	if (parent_uid == NULL)
+		parent_uid = g_strdup ("");
+
+	array = g_hash_table_lookup (orphans, parent_uid);
+
+	if (array != NULL) {
+		/* Array is not ordered, so use "remove_fast". */
+		removed = g_ptr_array_remove_fast (array, orphan_source);
+	}
+
+	g_free (parent_uid);
+
+	g_mutex_unlock (server->priv->orphans_lock);
+
+	return removed;
+}
+
+static GPtrArray *
+dbus_source_server_orphans_steal (EDBusSourceServer *server,
+                                  ESource *parent_source)
+{
+	GHashTable *orphans;
+	GPtrArray *array;
+	const gchar *parent_uid;
+
+	parent_uid = e_source_get_uid (parent_source);
+	g_return_val_if_fail (parent_uid != NULL, NULL);
+
+	g_mutex_lock (server->priv->orphans_lock);
+
+	orphans = server->priv->orphans;
+
+	array = g_hash_table_lookup (orphans, parent_uid);
+
+	/* g_hash_table_remove() will unreference the array,
+	 * so we need to reference it first to keep it alive. */
+	if (array != NULL) {
+		g_ptr_array_ref (array);
+		g_hash_table_remove (orphans, parent_uid);
+	}
+
+	g_mutex_unlock (server->priv->orphans_lock);
+
+	return array;
+}
+
+static GQueue *
+dbus_source_server_auth_table_lookup (EDBusSourceServer *server,
+                                      const gchar *uid)
+{
+	GHashTable *hash_table;
+	GQueue *queue;
+
+	hash_table = server->priv->auth_table;
+	queue = g_hash_table_lookup (hash_table, uid);
+
+	if (queue == NULL) {
+		queue = g_queue_new ();
+		g_hash_table_insert (hash_table, g_strdup (uid), queue);
+	}
+
+	return queue;
+}
+
+static void
+dbus_source_server_maybe_initiate_next_authenticator (EDBusSourceServer *server)
+{
+	GQueue *queue;
+
+	if (server->priv->active_auth != NULL)
+		return;
+
+	/* Check if there's any authenticators left in the active
+	 * auth table queue.  If the user provided a valid secret
+	 * and elected to save it in the keyring, or if the user
+	 * dismissed the authentication prompt, then we should be
+	 * able to process the remaining authenticators quickly. */
+	if (server->priv->active_auth_table_queue != NULL &&
+            !g_queue_is_empty (server->priv->active_auth_table_queue)) {
+		queue = server->priv->active_auth_table_queue;
+		server->priv->active_auth = g_queue_peek_head (queue);
+
+	/* Otherwise find the next non-empty auth table queue to
+	 * be processed, according to the UIDs in the auth queue. */
+	} else while (!g_queue_is_empty (server->priv->auth_queue)) {
+		gchar *uid;
+
+		uid = g_queue_pop_head (server->priv->auth_queue);
+		queue = dbus_source_server_auth_table_lookup (server, uid);
+		g_free (uid);
+
+		if (g_queue_is_empty (queue))
+			continue;
+
+		server->priv->active_auth_table_queue = queue;
+		server->priv->active_auth = g_queue_peek_head (queue);
+	}
+
+	/* Initiate the new active authenicator.  This signals it to
+	 * respond with a cached secret in the keyring if it can, or
+	 * else show an authentication prompt and wait for input. */
+	if (server->priv->active_auth != NULL)
+		e_dbus_source_authenticator_initiate (
+			server->priv->active_auth);
+	else
+		server->priv->active_auth_table_queue = NULL;
+}
+
+static void
+dbus_source_server_auth_complete_cb (EDBusSourceAuthenticator *authenticator,
+                                     EDBusSourceAuthenticatorResult result,
+                                     EDBusSourceServer *server)
+{
+	GQueue *queue;
+	const gchar *uid;
+
+	uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+	g_return_if_fail (uid != NULL);
+
+	queue = dbus_source_server_auth_table_lookup (server, uid);
+
+	/* Remove the completed authenticator from its queue. */
+	if (g_queue_remove (queue, authenticator))
+		g_object_unref (authenticator);
+
+	/* If the completed authenticator was the active one,
+	 * clear the active pointer for the next authenticator. */
+	if (authenticator == server->priv->active_auth)
+		server->priv->active_auth = NULL;
+
+	dbus_source_server_maybe_initiate_next_authenticator (server);
+}
+
+static gboolean
+dbus_source_server_authenticate_cb (EDBusSourceManager *interface,
+                                    GDBusMethodInvocation *invocation,
+                                    const gchar *source_uid,
+                                    const gchar *icon_string,
+                                    const gchar *primary_markup,
+                                    const gchar *secondary_markup,
+                                    EDBusSourceServer *server)
+{
+	GDBusConnection *connection;
+	EDBusSourceAuthenticator *authenticator;
+	const gchar *base_object_path;
+	gchar *auth_object_path;
+	GIcon *auth_icon;
+	GType auth_type;
+	GError *error = NULL;
+
+	auth_type = e_dbus_source_server_get_authenticator_type (server);
+	connection = g_dbus_method_invocation_get_connection (invocation);
+
+	/* If no valid authenticator type has been installed,
+	 * we have to pass on handling the authenticate() call. */
+	if (!g_type_is_a (auth_type, E_TYPE_DBUS_SOURCE_AUTHENTICATOR))
+		return FALSE;
+
+	/* Create the authenticator object. */
+	authenticator = g_object_new (
+		auth_type, "server", server, "source-uid", source_uid, NULL);
+
+	/* Configure the authenticator object. */
+	auth_icon = g_icon_new_for_string (icon_string, &error);
+	if (error != NULL) {
+		auth_icon = g_themed_icon_new (FALLBACK_AUTH_ICON_NAME);
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_clear_error (&error);
+	}
+	g_object_set (
+		authenticator,
+		"icon", auth_icon,
+		"primary-markup", primary_markup,
+		"secondary-markup", secondary_markup,
+		NULL);
+	g_object_unref (auth_icon);
+
+	/* Export the authenticator to a unique object path.  This
+	 * effectively initiates a new authentication session with
+	 * the method caller. */
+
+	base_object_path = E_DBUS_SOURCE_SERVER_OBJECT_PATH;
+
+	auth_object_path = g_strdup_printf (
+		"%s/auth_%u", base_object_path,
+		server->priv->authentication_count++);
+
+	g_dbus_interface_skeleton_export (
+		G_DBUS_INTERFACE_SKELETON (authenticator),
+		connection, auth_object_path, &error);
+
+	if (error != NULL) {
+		g_dbus_method_invocation_take_error (invocation, error);
+		g_object_unref (authenticator);
+		g_free (auth_object_path);
+		return TRUE;
+	}
+
+	/* This references the authenticator and adds it to a queue. */
+	e_dbus_source_server_add_authenticator (server, authenticator);
+
+	e_dbus_source_manager_complete_authenticate (
+		interface, invocation, auth_object_path);
+
+	g_object_unref (authenticator);
+	g_free (auth_object_path);
+
+	return TRUE;
+}
+
+static gboolean
+dbus_source_server_create_source_cb (EDBusSourceManager *interface,
+                                     GDBusMethodInvocation *invocation,
+                                     const gchar *uid,
+                                     const gchar *data,
+                                     EDBusSourceServer *server)
+{
+	ESource *source = NULL;
+	EDBusObject *dbus_object;
+	GDBusObject *g_dbus_object;
+	GFile *file;
+	GKeyFile *key_file;
+	const gchar *user_dir;
+	const gchar *object_path;
+	gchar *safe_uid;
+	gchar *path;
+	gsize length;
+	GError *error = NULL;
+
+	length = strlen (data);
+
+	/* Make sure the data is syntactically valid. */
+	key_file = g_key_file_new ();
+	g_key_file_load_from_data (
+		key_file, data, length, G_KEY_FILE_NONE, &error);
+	g_key_file_free (key_file);
+
+	if (error != NULL) {
+		g_dbus_method_invocation_take_error (invocation, error);
+		return TRUE;
+	}
+
+	/* Check that the given unique identifier really is unique.
+	 *
+	 * XXX There's a valid case to be made that the server should be
+	 *     assigning unique identifiers to new sources to avoid this
+	 *     error.  That's fine for standalone sources but makes life
+	 *     more difficult for clients creating a set or hierarchy of
+	 *     sources that cross reference one another, such for a mail
+	 *     account.  Having CLIENTS generate new UIDs means they can
+	 *     prepare any cross references in advance, then submit each
+	 *     source as is without having to make further modifications
+	 *     as would be necessary if using server-assigned UIDs.
+	 *
+	 *     Anyway, if used properly the odds of a UID collision here
+	 *     are slim enough that I think it's a reasonable trade-off.
+	 */
+	if (e_dbus_source_server_lookup_by_uid (server, uid) != NULL) {
+		g_dbus_method_invocation_return_error (
+			invocation, G_IO_ERROR, G_IO_ERROR_EXISTS,
+			_("UID '%s' is already in use"), uid);
+		return TRUE;
+	}
+
+	/* Generate a new UID and build a file path from it. */
+	safe_uid = g_strdup (uid);
+	e_filename_make_safe (safe_uid);
+	user_dir = e_server_side_source_get_user_dir ();
+	path = g_build_filename (user_dir, safe_uid, NULL);
+	file = g_file_new_for_path (path);
+	g_free (path);
+	g_free (safe_uid);
+
+	/* Write the data to disk.  The file monitor should eventually
+	 * notice the new file and call e_dbus_source_server_load_file()
+	 * per design, but we're going to beat it to the punch since we
+	 * need to return the new D-Bus object path back to the caller.
+	 * By the time the file monitor gets around to loading the file,
+	 * it will simply get back the EDBusSourceObject we've already
+	 * created and exported. */
+
+	g_file_replace_contents (
+		file, data, length, NULL, FALSE,
+		G_FILE_CREATE_PRIVATE, NULL, NULL, &error);
+
+	if (error == NULL)
+		source = e_dbus_source_server_load_file (server, file, &error);
+
+	g_object_unref (file);
+
+	if (error != NULL) {
+		g_dbus_method_invocation_take_error (invocation, error);
+		return TRUE;
+	}
+
+	/* New sources are always writable + removable. */
+	e_server_side_source_set_writable (
+		E_SERVER_SIDE_SOURCE (source), TRUE);
+	e_server_side_source_set_removable (
+		E_SERVER_SIDE_SOURCE (source), TRUE);
+
+	dbus_object = e_source_get_dbus_object (source);
+	g_dbus_object = G_DBUS_OBJECT (dbus_object);
+	object_path = g_dbus_object_get_object_path (g_dbus_object);
+
+	e_dbus_source_manager_complete_create_source (
+		interface, invocation, object_path);
+
+	return TRUE;
+}
+
+static void
+dbus_source_server_monitor_changed_cb (GFileMonitor *monitor,
+                                       GFile *file,
+                                       GFile *other_file,
+                                       GFileMonitorEvent event_type,
+                                       EDBusSourceServer *server)
+{
+	gchar *basename;
+	gboolean hidden;
+
+	basename = g_file_get_basename (file);
+	hidden = g_str_has_prefix (basename, ".");
+	g_free (basename);
+
+	/* XXX Ignore hidden files so we don't try
+	 *     to load .goutputstream-XXXXXX files. */
+	if (hidden)
+		return;
+
+	if (event_type == G_FILE_MONITOR_EVENT_CREATED) {
+		ESource *source;
+		GError *error = NULL;
+
+		source = e_server_side_source_new (server, file, &error);
+
+		/* Sanity check. */
+		g_return_if_fail (
+			((source != NULL) && (error == NULL)) ||
+			((source == NULL) && (error != NULL)));
+
+		if (error == NULL) {
+			e_dbus_source_server_add_source (server, source);
+			g_object_unref (source);
+		} else {
+			e_dbus_source_server_load_error (
+				server, file, error);
+			g_error_free (error);
+		}
+	}
+
+	if (event_type == G_FILE_MONITOR_EVENT_DELETED) {
+		ESource *source;
+
+		source = e_dbus_source_server_lookup_by_file (server, file);
+
+		if (source == NULL)
+			return;
+
+		/* If the key file for a non-removable source was
+		 * somehow deleted, disregard the event and leave
+		 * the source object in memory. */
+		if (!e_source_get_removable (source))
+			return;
+
+		e_dbus_source_server_remove_source (server, source);
+	}
+}
+
+static gboolean
+dbus_source_server_traverse_cb (GNode *node,
+                                GQueue *queue)
+{
+	g_queue_push_tail (queue, g_object_ref (node->data));
+
+	return FALSE;
+}
+
+static void
+dbus_source_server_queue_subtree (ESource *source,
+                                  GQueue *queue)
+{
+	GNode *node;
+
+	node = e_server_side_source_get_node (E_SERVER_SIDE_SOURCE (source));
+
+	g_node_traverse (
+		node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+		(GNodeTraverseFunc) dbus_source_server_traverse_cb, queue);
+}
+
+static gboolean
+dbus_source_server_find_parent (EDBusSourceServer *server,
+                                ESource *source)
+{
+	ESource *parent;
+	const gchar *parent_uid;
+
+	/* If the given source references a parent source and the
+	 * parent source is not present in the hierarchy, the given
+	 * source is added to an orphan table until the referenced
+	 * parent is added to the hierarchy. */
+
+	parent_uid = e_source_get_parent (source);
+
+	if (parent_uid == NULL || *parent_uid == '\0')
+		return TRUE;
+
+	parent = g_hash_table_lookup (server->priv->sources, parent_uid);
+
+	if (parent != NULL) {
+		GNode *parent_node;
+		GNode *object_node;
+
+		parent_node = e_server_side_source_get_node (
+			E_SERVER_SIDE_SOURCE (parent));
+		object_node = e_server_side_source_get_node (
+			E_SERVER_SIDE_SOURCE (source));
+		g_node_append (parent_node, object_node);
+
+		return TRUE;
+	}
+
+	dbus_source_server_orphans_insert (server, source);
+
+	return FALSE;
+}
+
+static void
+dbus_source_server_adopt_orphans (EDBusSourceServer *server,
+                                  ESource *source)
+{
+	GPtrArray *array;
+
+	/* Check if a newly-added source has any orphan sources
+	 * that are waiting for it.  The orphans can now be added
+	 * to the hierarchy as children of the newly-added source. */
+
+	array = dbus_source_server_orphans_steal (server, source);
+
+	if (array != NULL) {
+		guint ii;
+
+		for (ii = 0; ii < array->len; ii++) {
+			ESource *orphan = array->pdata[ii];
+			e_dbus_source_server_add_source (server, orphan);
+		}
+
+		g_ptr_array_unref (array);
+	}
+}
+
+static void
+dbus_source_server_set_property (GObject *object,
+                                 guint property_id,
+                                 const GValue *value,
+                                 GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_AUTHENTICATOR_TYPE:
+			e_dbus_source_server_set_authenticator_type (
+				E_DBUS_SOURCE_SERVER (object),
+				g_value_get_gtype (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+dbus_source_server_get_property (GObject *object,
+                                 guint property_id,
+                                 GValue *value,
+                                 GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_AUTHENTICATOR_TYPE:
+			g_value_set_gtype (
+				value,
+				e_dbus_source_server_get_authenticator_type (
+				E_DBUS_SOURCE_SERVER (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+dbus_source_server_dispose (GObject *object)
+{
+	EDBusSourceServerPrivate *priv;
+
+	priv = E_DBUS_SOURCE_SERVER_GET_PRIVATE (object);
+
+	if (priv->object_manager != NULL) {
+		g_object_unref (priv->object_manager);
+		priv->object_manager = NULL;
+	}
+
+	if (priv->source_manager != NULL) {
+		g_object_unref (priv->source_manager);
+		priv->source_manager = NULL;
+	}
+
+	g_hash_table_remove_all (priv->sources);
+	g_hash_table_remove_all (priv->orphans);
+	g_hash_table_remove_all (priv->monitors);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_dbus_source_server_parent_class)->dispose (object);
+}
+
+static void
+dbus_source_server_finalize (GObject *object)
+{
+	EDBusSourceServerPrivate *priv;
+
+	priv = E_DBUS_SOURCE_SERVER_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->sources);
+	g_hash_table_destroy (priv->orphans);
+	g_hash_table_destroy (priv->monitors);
+
+	g_mutex_free (priv->sources_lock);
+	g_mutex_free (priv->orphans_lock);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_dbus_source_server_parent_class)->finalize (object);
+}
+
+static void
+dbus_source_server_constructed (GObject *object)
+{
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_dbus_source_server_parent_class)->
+		constructed (object);
+
+	/* Load all module libraries containing extensions. */
+
+	e_dbus_server_load_modules (E_DBUS_SERVER (object));
+
+	/* Instantiate all EDBusSourceServer extensions. */
+
+	e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static void
+dbus_source_server_bus_acquired (EDBusServer *server,
+                                 GDBusConnection *connection)
+{
+	EDBusSourceServerPrivate *priv;
+	GError *error = NULL;
+
+	priv = E_DBUS_SOURCE_SERVER_GET_PRIVATE (server);
+
+	g_dbus_object_manager_server_set_connection (
+		priv->object_manager, connection);
+
+	g_dbus_interface_skeleton_export (
+		G_DBUS_INTERFACE_SKELETON (priv->source_manager),
+		connection, E_DBUS_SOURCE_SERVER_OBJECT_PATH, &error);
+
+	/* Terminate the server if we can't export the interface. */
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		e_dbus_server_quit (server);
+		g_error_free (error);
+	}
+
+	/* Chain up to parent's bus_acquired() method. */
+	E_DBUS_SERVER_CLASS (e_dbus_source_server_parent_class)->
+		bus_acquired (server, connection);
+}
+
+static void
+dbus_source_server_source_added (EDBusSourceServer *server,
+                                 ESource *source)
+{
+	EDBusObject *dbus_object;
+	GDBusObject *g_dbus_object;
+	const gchar *uid;
+	const gchar *object_name;
+	const gchar *object_path;
+
+	dbus_object = e_source_get_dbus_object (source);
+	g_dbus_object = G_DBUS_OBJECT (dbus_object);
+
+	g_dbus_object_manager_server_export_uniquely (
+		server->priv->object_manager,
+		G_DBUS_OBJECT_SKELETON (dbus_object));
+
+	uid = e_source_get_uid (source);
+
+	object_path = g_dbus_object_get_object_path (g_dbus_object);
+	object_name = strrchr (object_path, '/') + 1;
+
+	g_print ("Adding UID:%s ('%s')\n", uid, object_name);
+}
+
+static void
+dbus_source_server_source_removed (EDBusSourceServer *server,
+                                   ESource *source)
+{
+	EDBusObject *dbus_object;
+	GDBusObject *g_dbus_object;
+	const gchar *uid;
+	const gchar *object_name;
+	const gchar *object_path;
+
+	uid = e_source_get_uid (source);
+
+	dbus_object = e_source_get_dbus_object (source);
+	g_dbus_object = G_DBUS_OBJECT (dbus_object);
+
+	object_path = g_dbus_object_get_object_path (g_dbus_object);
+	object_name = strrchr (object_path, '/') + 1;
+
+	g_print ("Removing UID:%s ('%s')\n", uid, object_name);
+
+	g_dbus_object_manager_server_unexport (
+		server->priv->object_manager, object_path);
+}
+
+static void
+e_dbus_source_server_class_init (EDBusSourceServerClass *class)
+{
+	GObjectClass *object_class;
+	EDBusServerClass *dbus_server_class;
+
+	g_type_class_add_private (class, sizeof (EDBusSourceServerPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = dbus_source_server_set_property;
+	object_class->get_property = dbus_source_server_get_property;
+	object_class->dispose = dbus_source_server_dispose;
+	object_class->finalize = dbus_source_server_finalize;
+	object_class->constructed = dbus_source_server_constructed;
+
+	dbus_server_class = E_DBUS_SERVER_CLASS (class);
+	dbus_server_class->bus_name = SOURCES_DBUS_SERVICE_NAME;
+	dbus_server_class->module_directory = MODULE_DIRECTORY;
+	dbus_server_class->bus_acquired = dbus_source_server_bus_acquired;
+
+	class->source_added = dbus_source_server_source_added;
+	class->source_removed = dbus_source_server_source_removed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_AUTHENTICATOR_TYPE,
+		g_param_spec_gtype (
+			"authenticator-type",
+			"Authenticator Type",
+			"GType for handling authentications",
+			E_TYPE_DBUS_SOURCE_AUTHENTICATOR,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * EDBusSourceServer::load-error:
+	 * @server: the #EDBusSourceServer which emitted the signal
+	 * @file: the #GFile being loaded
+	 * @error: a #GError describing the error
+	 *
+	 * Emitted when an error occurs while loading or parsing a
+	 * data source key file.
+	 **/
+	signals[LOAD_ERROR] = g_signal_new (
+		"load-error",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EDBusSourceServerClass, load_error),
+		NULL, NULL,
+		e_marshal_VOID__OBJECT_BOXED,
+		G_TYPE_NONE, 2,
+		G_TYPE_FILE,
+		G_TYPE_ERROR | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	/**
+	 * EDBusSourceServer::files-loaded:
+	 * @server: the #EDBusSourceServer which emitted the signal
+	 *
+	 * Emitted after all data source key files are loaded on startup.
+	 * Extensions can connect to this signal to perform any additional
+	 * work prior to running the main loop.
+	 **/
+	signals[FILES_LOADED] = g_signal_new (
+		"files-loaded",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EDBusSourceServerClass, files_loaded),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	/**
+	 * EDBusSourceServer::source-added
+	 * @server: the #EDBusSourceServer which emitted the signal
+	 * @source: the newly-added #EServerSideSource
+	 *
+	 * Emitted when an #EDBusSourceObject is added to @server.
+	 **/
+	signals[SOURCE_ADDED] = g_signal_new (
+		"source-added",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EDBusSourceServerClass, source_added),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SERVER_SIDE_SOURCE);
+
+	/**
+	 * EDBusSourceServer::source-removed
+	 * @server: the #EDBusSourceServer when emitted the signal
+	 * @source: the #EServerSideSource that got removed
+	 *
+	 * Emitted when an #EDBusSourceObject is removed from @server.
+	 **/
+	signals[SOURCE_REMOVED] = g_signal_new (
+		"source-removed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EDBusSourceServerClass, source_removed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SERVER_SIDE_SOURCE);
+}
+
+static void
+e_dbus_source_server_init (EDBusSourceServer *server)
+{
+	GDBusObjectManagerServer *object_manager;
+	EDBusSourceManager *source_manager;
+	GHashTable *sources;
+	GHashTable *orphans;
+	GHashTable *monitors;
+	GHashTable *auth_table;
+	const gchar *object_path;
+
+	object_path = E_DBUS_SOURCE_SERVER_OBJECT_PATH;
+	object_manager = g_dbus_object_manager_server_new (object_path);
+	source_manager = e_dbus_source_manager_skeleton_new ();
+
+	/* UID string -> ESource */
+	sources = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_object_unref);
+
+	/* Parent UID string -> GPtrArray of ESources */
+	orphans = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_ptr_array_unref);
+
+	monitors = g_hash_table_new_full (
+		(GHashFunc) g_file_hash,
+		(GEqualFunc) g_file_equal,
+		(GDestroyNotify) g_object_unref,
+		(GDestroyNotify) g_object_unref);
+
+	auth_table = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) free_auth_queue);
+
+	server->priv = E_DBUS_SOURCE_SERVER_GET_PRIVATE (server);
+	server->priv->object_manager = object_manager;
+	server->priv->source_manager = source_manager;
+	server->priv->sources = sources;
+	server->priv->orphans = orphans;
+	server->priv->monitors = monitors;
+	server->priv->sources_lock = g_mutex_new ();
+	server->priv->orphans_lock = g_mutex_new ();
+	server->priv->auth_queue = g_queue_new ();
+	server->priv->auth_table = auth_table;
+
+	g_signal_connect (
+		source_manager, "handle-authenticate",
+		G_CALLBACK (dbus_source_server_authenticate_cb),
+		server);
+
+	g_signal_connect (
+		source_manager, "handle-create-source",
+		G_CALLBACK (dbus_source_server_create_source_cb),
+		server);
+}
+
+/**
+ * e_dbus_source_server_new:
+ *
+ * Creates a new instance of #EDBusSourceServer.
+ *
+ * Returns: a new instance of #EDBusSourceServer
+ *
+ * Since: 3.6
+ **/
+EDBusServer *
+e_dbus_source_server_new (void)
+{
+	return g_object_new (E_TYPE_DBUS_SOURCE_SERVER, NULL);
+}
+
+/**
+ * e_dbus_source_server_get_authenticator_type:
+ * @server: an #EDBusSourceServer
+ *
+ * Returns the type of object to create to service authentication requests.
+ *
+ * Returns: the authenticator #GType
+ *
+ * Since: 3.6
+ **/
+GType
+e_dbus_source_server_get_authenticator_type (EDBusSourceServer *server)
+{
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_SERVER (server), G_TYPE_INVALID);
+
+	return server->priv->authenticator_type;
+}
+
+/**
+ * e_dbus_source_server_set_authenticator_type:
+ * @server: an #EDBusSourceServer
+ * @authenticator_type: the authenticator #GType
+ *
+ * Sets the type of object to create to service authentication requests.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_server_set_authenticator_type (EDBusSourceServer *server,
+                                             GType authenticator_type)
+{
+	gboolean valid_authenticator_type;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_SERVER (server));
+
+	valid_authenticator_type = g_type_is_a (
+		authenticator_type, E_TYPE_DBUS_SOURCE_AUTHENTICATOR);
+	g_return_if_fail (valid_authenticator_type);
+
+	server->priv->authenticator_type = authenticator_type;
+
+	g_object_notify (G_OBJECT (server), "authenticator-type");
+}
+
+/**
+ * e_dbus_source_server_add_source:
+ * @server: an #EDBusSourceServer
+ * @source: an #ESource
+ *
+ * Adds @source to @server.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_server_add_source (EDBusSourceServer *server,
+                                 ESource *source)
+{
+	const gchar *uid;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_SERVER (server));
+	g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+	uid = e_source_get_uid (source);
+	g_return_if_fail (uid != NULL);
+
+	g_mutex_lock (server->priv->sources_lock);
+
+	/* Check if we already have this object in the hierarchy. */
+	if (g_hash_table_lookup (server->priv->sources, uid) != NULL) {
+		g_mutex_unlock (server->priv->sources_lock);
+		return;
+	}
+
+	/* Make sure the parent object (if any) is in the hierarchy. */
+	if (!dbus_source_server_find_parent (server, source)) {
+		g_mutex_unlock (server->priv->sources_lock);
+		return;
+	}
+
+	g_mutex_unlock (server->priv->sources_lock);
+
+	dbus_source_server_sources_insert (server, source);
+
+	g_signal_emit (server, signals[SOURCE_ADDED], 0, source);
+
+	/* Adopt any orphans that have been waiting for this object. */
+	dbus_source_server_adopt_orphans (server, source);
+}
+
+/* Helper for e_dbus_source_server_remove_object() */
+static void
+dbus_source_server_remove_object (EDBusSourceServer *server,
+                                  ESource *source)
+{
+	g_object_ref (source);
+
+	if (dbus_source_server_sources_remove (server, source)) {
+		EServerSideSource *ss_source;
+
+		ss_source = E_SERVER_SIDE_SOURCE (source);
+		dbus_source_server_orphans_insert (server, source);
+		g_node_unlink (e_server_side_source_get_node (ss_source));
+		g_signal_emit (server, signals[SOURCE_REMOVED], 0, source);
+	}
+
+	g_object_unref (source);
+}
+
+/**
+ * e_dbus_source_server_remove_source:
+ * @server: an #EDBusSourceServer
+ * @source: an #ESource
+ *
+ * Removes @source and all of its descendants from @server.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_server_remove_source (EDBusSourceServer *server,
+                                    ESource *source)
+{
+	ESource *child;
+	GQueue queue = G_QUEUE_INIT;
+	const gchar *uid;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_SERVER (server));
+	g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+	uid = e_source_get_uid (source);
+
+	/* If the removed source is in the server hierarchy, gather
+	 * it and all of its descendants into a queue in "post-order"
+	 * so we're always processing leaf nodes as we pop sources off
+	 * the head of the queue. */
+	if (e_dbus_source_server_lookup_by_uid (server, uid) != NULL)
+		dbus_source_server_queue_subtree (source, &queue);
+
+	/* Move the queued descendants to the orphan table, and emit a
+	 * "source-removed" signal for each source.  This will include
+	 * the removed source unless the source was already an orphan,
+	 * in which case the queue will be empty. */
+	while ((child = g_queue_pop_head (&queue)) != NULL) {
+		dbus_source_server_remove_object (server, child);
+		g_object_unref (child);
+	}
+
+	/* The removed source should be in the orphan table now. */
+	dbus_source_server_orphans_remove (server, source);
+}
+
+/**
+ * e_dbus_source_server_add_authenticator:
+ * @server: an #EDBusSourceServer
+ * @authenticator: an #EDBusSourceAuthenticator
+ *
+ * Queues an authentication session.  When its turn comes, and if necessary,
+ * the user will be prompted for a secret.  Sessions are queued this way to
+ * prevent user prompts from piling up on the screen.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_server_add_authenticator (EDBusSourceServer *server,
+                                        EDBusSourceAuthenticator *authenticator)
+{
+	const gchar *uid;
+	GQueue *queue;
+
+	g_return_if_fail (E_IS_DBUS_SOURCE_SERVER (server));
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+
+	uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+	g_return_if_fail (uid != NULL);
+
+	/* Add the authenticator to the appropriate queue. */
+	queue = dbus_source_server_auth_table_lookup (server, uid);
+	g_queue_push_tail (queue, g_object_ref (authenticator));
+
+	/* Blindly push the UID onto the processing queue. */
+	g_queue_push_tail (server->priv->auth_queue, g_strdup (uid));
+
+	g_signal_connect (
+		authenticator, "complete",
+		G_CALLBACK (dbus_source_server_auth_complete_cb),
+		server);
+
+	dbus_source_server_maybe_initiate_next_authenticator (server);
+}
+
+/**
+ * e_dbus_source_server_load_all:
+ * @server: an #EDBusSourceServer
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads data source key files from standard system-wide and user-specific
+ * locations.  Because multiple errors can occur when loading multiple files,
+ * @error is only set if a directory can not be opened.  If a key file fails
+ * to load, the error is broadcast through the #EDBusSourceServer::load-error
+ * signal.
+ *
+ * Returns: %TRUE if the standard directories were successfully opened,
+ *          but this does not imply the key files were successfully loaded
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_server_load_all (EDBusSourceServer *server,
+                               GError **error)
+{
+	EDBusLoadDirectoryFlags flags;
+	const gchar *directory;
+	gboolean success;
+
+	g_return_val_if_fail (E_IS_DBUS_SOURCE_SERVER (server), FALSE);
+
+	/* Load the user's sources directory first so that user-specific
+	 * data sources overshadow predefined data sources with identical
+	 * UIDs.  The 'local' data source is one such example. */
+
+	directory = e_server_side_source_get_user_dir ();
+	flags = E_DBUS_LOAD_DIRECTORY_REMOVABLE |
+		E_DBUS_LOAD_DIRECTORY_WRITABLE;
+	success = e_dbus_source_server_load_directory (
+		server, directory, flags, error);
+	g_prefix_error (error, "%s: ", directory);
+
+	if (!success)
+		return FALSE;
+
+	directory = SYSTEM_WIDE_RO_SOURCES_DIRECTORY;
+	flags = E_DBUS_LOAD_DIRECTORY_NONE;
+	success = e_dbus_source_server_load_directory (
+		server, directory, flags, error);
+	g_prefix_error (error, "%s: ", directory);
+
+	if (!success)
+		return FALSE;
+
+	directory = SYSTEM_WIDE_RW_SOURCES_DIRECTORY;
+	flags = E_DBUS_LOAD_DIRECTORY_WRITABLE;
+	success = e_dbus_source_server_load_directory (
+		server, directory, flags, error);
+	g_prefix_error (error, "%s: ", directory);
+
+	if (!success)
+		return FALSE;
+
+	/* Signal that all files are now loaded. */
+	g_signal_emit (server, signals[FILES_LOADED], 0);
+
+	return TRUE;
+}
+
+/**
+ * e_dbus_source_server_load_directory:
+ * @server: an #EDBusSourceServer
+ * @path: the path to the directory to load
+ * @flags: permission flags for files loaded from @path
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads data source key files in @path.  Because multiple errors can
+ * occur when loading multiple files, @error is only set if @path can
+ * not be opened.  If a key file fails to load, the error is broadcast
+ * through the #EDBusSourceServer::load-error signal.
+ *
+ * If the #E_DBUS_LOAD_DIRECTORY_REMOVABLE flag is given, then the @server
+ * will emit signals on the D-Bus interface when key files are created or
+ * deleted in @path.
+ *
+ * Returns: %TRUE if @path was successfully opened, but this
+ *          does not imply the key files were successfully loaded
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_dbus_source_server_load_directory (EDBusSourceServer *server,
+                                     const gchar *path,
+                                     EDBusLoadDirectoryFlags flags,
+                                     GError **error)
+{
+	GDir *dir;
+	GFile *file;
+	const gchar *name;
+	gboolean writable;
+	gboolean removable;
+
+	g_return_val_if_fail (E_IS_DBUS_SOURCE_SERVER (server), FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+
+	writable = ((flags & E_DBUS_LOAD_DIRECTORY_WRITABLE) != 0);
+	removable = ((flags & E_DBUS_LOAD_DIRECTORY_REMOVABLE) != 0);
+
+	/* If the directory doesn't exist then there's nothing to load.
+	 * Note we do not use G_FILE_TEST_DIR here.  If the given path
+	 * exists but is not a directory then we let g_dir_open() fail. */
+	if (!g_file_test (path, G_FILE_TEST_EXISTS))
+		return TRUE;
+
+	dir = g_dir_open (path, 0, error);
+	if (dir == NULL)
+		return FALSE;
+
+	file = g_file_new_for_path (path);
+
+	while ((name = g_dir_read_name (dir)) != NULL) {
+		ESource *source;
+		GFile *child;
+		GError *local_error = NULL;
+
+		/* XXX Ignore hidden files so we don't try
+		 *     to load .goutputstream-XXXXXX files. */
+		if (g_str_has_prefix (name, "."))
+			continue;
+
+		child = g_file_get_child (file, name);
+
+		/* Note that we may get back an already-loaded data
+		 * source object with an identical UID, in which case
+		 * we override its permission flags below.  That's on
+		 * purpose.  That's why the "load_all" function loads
+		 * the user directory before loading the system-wide
+		 * directories.  If there's a UID collision between
+		 * a data source in the user's directory and a data
+		 * source in a system-wide directory, the permission
+		 * flags for the system-wide directory win.
+		 *
+		 * Consider an example:
+		 *
+		 * The built-in 'local' data source should always be
+		 * writable but not removable.
+		 *
+		 * Suppose the user temporarily disables the 'local'
+		 * data source.  The altered 'local' data source file
+		 * (with Enabled=false) is saved in the user's sources
+		 * directory.
+		 *
+		 * On the next startup, the altered 'local' file is
+		 * first loaded from the user's source directory and
+		 * given removable + writable permissions.
+		 *
+		 * We then load data sources from the 'rw-sources'
+		 * system directory containing the unaltered 'local'
+		 * file (with Enabled=true), which is not removable.
+		 *
+		 * We keep the contents of the altered 'local' file
+		 * (Enabled=false), but override its permissions to
+		 * just be writable, not removable.
+		 */
+
+		source = e_dbus_source_server_load_file (
+			server, child, &local_error);
+
+		/* Set the data source's permission flags, which
+		 * determines which D-Bus interfaces it exports:
+		 * write() if writable, remove() if removable. */
+		if (source != NULL) {
+			e_server_side_source_set_writable (
+				E_SERVER_SIDE_SOURCE (source), writable);
+			e_server_side_source_set_removable (
+				E_SERVER_SIDE_SOURCE (source), removable);
+		}
+
+		if (local_error != NULL) {
+			e_dbus_source_server_load_error (
+				server, child, local_error);
+			g_error_free (local_error);
+		}
+
+		g_object_unref (child);
+	}
+
+	g_dir_close (dir);
+
+	/* Only data source files in the user's
+	 * sources directory should be removable. */
+	if (removable) {
+		GFileMonitor *monitor;
+
+		monitor = g_file_monitor_directory (
+			file, G_FILE_MONITOR_NONE, NULL, error);
+		if (monitor == NULL)
+			return FALSE;
+
+		g_signal_connect (
+			monitor, "changed",
+			G_CALLBACK (dbus_source_server_monitor_changed_cb),
+			server);
+
+		g_hash_table_insert (
+			server->priv->monitors,
+			g_object_ref (file), monitor);
+	}
+
+	g_object_unref (file);
+
+	return TRUE;
+}
+
+/**
+ * e_dbus_source_server_load_file:
+ * @server: an #EDBusSourceServer
+ * @file: the data source key file to load
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates an #ESource for a native key file and adds it to @server.
+ * If an error occurs, the function returns %NULL and sets @error.
+ *
+ * Returns: the newly-added #ESource, or %NULL on error
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_dbus_source_server_load_file (EDBusSourceServer *server,
+                                GFile *file,
+                                GError **error)
+{
+	ESource *source;
+
+	g_return_val_if_fail (E_IS_DBUS_SOURCE_SERVER (server), NULL);
+	g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+	/* Check if we already have this file loaded. */
+	source = e_dbus_source_server_lookup_by_file (server, file);
+	if (source != NULL)
+		return source;
+
+	/* Create a new ESource and add it to the server. */
+	source = e_server_side_source_new (server, file, error);
+	if (source != NULL) {
+		e_dbus_source_server_add_source (server, source);
+		g_object_unref (source);
+	}
+
+	return source;
+}
+
+/**
+ * e_dbus_source_server_load_error:
+ * @server: an #EBusSourceServer
+ * @file: the #GFile that failed to load
+ * @error: a #GError describing the load error
+ *
+ * Emits the #EDBusSourceServer::load-error signal.
+ *
+ * Since: 3.6
+ **/
+void
+e_dbus_source_server_load_error (EDBusSourceServer *server,
+                                 GFile *file,
+                                 const GError *error)
+{
+	g_return_if_fail (E_IS_DBUS_SOURCE_SERVER (server));
+	g_return_if_fail (G_IS_FILE (file));
+	g_return_if_fail (error != NULL);
+
+	g_signal_emit (server, signals[LOAD_ERROR], 0, file, error);
+}
+
+/**
+ * e_dbus_source_server_lookup_by_uid:
+ * @server: an #EDBusSourceServer
+ * @uid: a unique identifier string
+ *
+ * Looks up an #ESource in @server by its unique identifier string.
+ *
+ * Returns: an #ESource, or %NULL if no match was found
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_dbus_source_server_lookup_by_uid (EDBusSourceServer *server,
+                                    const gchar *uid)
+{
+	g_return_val_if_fail (E_IS_DBUS_SOURCE_SERVER (server), NULL);
+	g_return_val_if_fail (uid != NULL, NULL);
+
+	return g_hash_table_lookup (server->priv->sources, uid);
+}
+
+/**
+ * e_dbus_source_server_lookup_by_file:
+ * @server: an #EDBusSourceServer
+ * @file: a #GFile
+ *
+ * Looks up an #ESource in @server by its #GFile.
+ *
+ * Returns: an #ESource, or %NULL if no match was found
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_dbus_source_server_lookup_by_file (EDBusSourceServer *server,
+                                     GFile *file)
+{
+	ESource *source;
+	gchar *uid;
+
+	g_return_val_if_fail (E_IS_DBUS_SOURCE_SERVER (server), NULL);
+	g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+	uid = g_file_get_basename (file);
+	source = e_dbus_source_server_lookup_by_uid (server, uid);
+	g_free (uid);
+
+	return source;
+}
+
+/**
+ * e_dbus_source_server_list_sources:
+ * @server: an #EDBusSourceServer
+ * @extension_name: an extension name, or %NULL
+ *
+ * Returns a list of registered sources, sorted by display name.  If
+ * @extension_name is given, restrict the list to sources having that
+ * extension name.
+ *
+ * The sources returned in the list are referenced for thread-safety.
+ * They must each be unreferenced with g_object_unref() when finished
+ * with them.  Free the returned #GList itself with g_list_free().
+ *
+ * An easy way to free the list properly in one step is as follows:
+ *
+ * |[
+ *   g_list_free_full (list, g_object_unref);
+ * ]|
+ *
+ * Returns: a sorted list of sources
+ *
+ * Since: 3.6
+ **/
+GList *
+e_dbus_source_server_list_sources (EDBusSourceServer *server,
+                                   const gchar *extension_name)
+{
+	GList *list, *link;
+	GQueue trash = G_QUEUE_INIT;
+
+	g_return_val_if_fail (E_IS_DBUS_SOURCE_SERVER (server), NULL);
+
+	list = g_list_sort (
+		dbus_source_server_sources_get_values (server),
+		(GCompareFunc) e_source_compare_by_display_name);
+
+	if (extension_name == NULL)
+		return list;
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESource *source = E_SOURCE (link->data);
+
+		if (!e_source_has_extension (source, extension_name)) {
+			g_queue_push_tail (&trash, link);
+			g_object_unref (source);
+		}
+	}
+
+	/* We do want pop_head() here, not pop_head_link(). */
+	while ((link = g_queue_pop_head (&trash)) != NULL)
+		list = g_list_delete_link (list, link);
+
+	return list;
+}
+
diff --git a/libebackend/e-dbus-source-server.h b/libebackend/e-dbus-source-server.h
new file mode 100644
index 0000000..3dce483
--- /dev/null
+++ b/libebackend/e-dbus-source-server.h
@@ -0,0 +1,129 @@
+/*
+ * e-dbus-source-server.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_DBUS_SOURCE_SERVER_H
+#define E_DBUS_SOURCE_SERVER_H
+
+#include <libebackend/e-backend-enums.h>
+#include <libebackend/e-dbus-server.h>
+#include <libebackend/e-dbus-source-authenticator.h>
+#include <libedataserver/e-source.h>
+
+/* Standard GObject macros */
+#define E_TYPE_DBUS_SOURCE_SERVER \
+	(e_dbus_source_server_get_type ())
+#define E_DBUS_SOURCE_SERVER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_DBUS_SOURCE_SERVER, EDBusSourceServer))
+#define E_DBUS_SOURCE_SERVER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_DBUS_SOURCE_SERVER, EDBusSourceServerClass))
+#define E_IS_DBUS_SOURCE_SERVER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_DBUS_SOURCE_SERVER))
+#define E_IS_DBUS_SOURCE_SERVER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_DBUS_SOURCE_SERVER))
+#define E_DBUS_SOURCE_SERVER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_DBUS_SOURCE_SERVER, EDBusSourceServerClass))
+
+/**
+ * E_DBUS_SOURCE_SERVER_OBJECT_PATH:
+ *
+ * D-Bus object path of the data source server.
+ *
+ * Since: 3.6
+ **/
+#define E_DBUS_SOURCE_SERVER_OBJECT_PATH \
+	"/org/gnome/evolution/dataserver/SourceManager"
+
+G_BEGIN_DECLS
+
+typedef struct _EDBusSourceServer EDBusSourceServer;
+typedef struct _EDBusSourceServerClass EDBusSourceServerClass;
+typedef struct _EDBusSourceServerPrivate EDBusSourceServerPrivate;
+
+/**
+ * EDBusSourceServer:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.6
+ **/
+struct _EDBusSourceServer {
+	EDBusServer parent;
+	EDBusSourceServerPrivate *priv;
+};
+
+struct _EDBusSourceServerClass {
+	EDBusServerClass parent_class;
+
+	/* Signals */
+	void		(*load_error)		(EDBusSourceServer *server,
+						 GFile *file,
+						 const GError *error);
+	void		(*files_loaded)		(EDBusSourceServer *server);
+	void		(*source_added)		(EDBusSourceServer *server,
+						 ESource *source);
+	void		(*source_removed)	(EDBusSourceServer *server,
+						 ESource *source);
+};
+
+GType		e_dbus_source_server_get_type	(void) G_GNUC_CONST;
+EDBusServer *	e_dbus_source_server_new	(void);
+GType		e_dbus_source_server_get_authenticator_type
+						(EDBusSourceServer *server);
+void		e_dbus_source_server_set_authenticator_type
+						(EDBusSourceServer *server,
+						 GType authenticator_type);
+void		e_dbus_source_server_add_source	(EDBusSourceServer *server,
+						 ESource *source);
+void		e_dbus_source_server_remove_source
+						(EDBusSourceServer *server,
+						 ESource *source);
+void		e_dbus_source_server_add_authenticator
+						(EDBusSourceServer *server,
+						 EDBusSourceAuthenticator *authenticator);
+gboolean	e_dbus_source_server_load_all	(EDBusSourceServer *server,
+						 GError **error);
+gboolean	e_dbus_source_server_load_directory
+						(EDBusSourceServer *server,
+						 const gchar *path,
+						 EDBusLoadDirectoryFlags flags,
+						 GError **error);
+ESource *	e_dbus_source_server_load_file	(EDBusSourceServer *server,
+						 GFile *file,
+						 GError **error);
+void		e_dbus_source_server_load_error	(EDBusSourceServer *server,
+						 GFile *file,
+						 const GError *error);
+ESource *	e_dbus_source_server_lookup_by_uid
+						(EDBusSourceServer *server,
+						 const gchar *uid);
+ESource *	e_dbus_source_server_lookup_by_file
+						(EDBusSourceServer *server,
+						 GFile *file);
+GList *		e_dbus_source_server_list_sources
+						(EDBusSourceServer *server,
+						 const gchar *extension_name);
+
+G_END_DECLS
+
+#endif /* E_DBUS_SOURCE_SERVER_H */
diff --git a/libebackend/e-server-side-source.c b/libebackend/e-server-side-source.c
new file mode 100644
index 0000000..d642614
--- /dev/null
+++ b/libebackend/e-server-side-source.c
@@ -0,0 +1,1058 @@
+/*
+ * e-server-side-source.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-server-side-source.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/e-uid.h>
+#include <libedataserver/e-data-server-util.h>
+
+#define E_SERVER_SIDE_SOURCE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSourcePrivate))
+
+#define DBUS_OBJECT_PATH	E_DBUS_SOURCE_SERVER_OBJECT_PATH "/Source"
+
+#define PRIMARY_GROUP_NAME	"Data Source"
+
+typedef struct _AsyncClosure AsyncClosure;
+
+struct _EServerSideSourcePrivate {
+	gpointer server;  /* weak pointer */
+
+	GNode node;
+	GFile *file;
+
+	gboolean allow_auth_prompt;
+	guint changed_idle_id;
+};
+
+struct _AsyncClosure {
+	GMainLoop *loop;
+	GMainContext *context;
+	GAsyncResult *result;
+};
+
+enum {
+	PROP_0,
+	PROP_ALLOW_AUTH_PROMPT,
+	PROP_FILE,
+	PROP_REMOVABLE,
+	PROP_SERVER,
+	PROP_WRITABLE
+};
+
+static GInitableIface *initable_parent_interface;
+
+/* Forward Declarations */
+static void	e_server_side_source_initable_init
+						(GInitableIface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	EServerSideSource,
+	e_server_side_source,
+	E_TYPE_SOURCE,
+	G_IMPLEMENT_INTERFACE (
+		G_TYPE_INITABLE,
+		e_server_side_source_initable_init))
+
+static AsyncClosure *
+async_closure_new (void)
+{
+	AsyncClosure *closure;
+
+	closure = g_slice_new0 (AsyncClosure);
+	closure->context = g_main_context_new ();
+	closure->loop = g_main_loop_new (closure->context, FALSE);
+
+	g_main_context_push_thread_default (closure->context);
+
+	return closure;
+}
+
+static GAsyncResult *
+async_closure_wait (AsyncClosure *closure)
+{
+	g_main_loop_run (closure->loop);
+
+	return closure->result;
+}
+
+static void
+async_closure_free (AsyncClosure *closure)
+{
+	g_main_context_pop_thread_default (closure->context);
+
+	g_main_loop_unref (closure->loop);
+	g_main_context_unref (closure->context);
+
+	if (closure->result != NULL)
+		g_object_unref (closure->result);
+
+	g_slice_free (AsyncClosure, closure);
+}
+
+static void
+async_closure_callback (GObject *object,
+                        GAsyncResult *result,
+                        gpointer user_data)
+{
+	AsyncClosure *closure = user_data;
+
+	/* Replace any previous result. */
+	if (closure->result != NULL)
+		g_object_unref (closure->result);
+	closure->result = g_object_ref (result);
+
+	g_main_loop_quit (closure->loop);
+}
+
+static gboolean
+server_side_source_parse_data (GKeyFile *key_file,
+                               const gchar *data,
+                               gsize length,
+                               GError **error)
+{
+	gboolean success;
+
+	success = g_key_file_load_from_data (
+		key_file, data, length, G_KEY_FILE_NONE, error);
+
+	if (!success)
+		return FALSE;
+
+	/* Make sure the key file has a [Data Source] group. */
+	if (!g_key_file_has_group (key_file, PRIMARY_GROUP_NAME)) {
+		g_set_error (
+			error, G_KEY_FILE_ERROR,
+			G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
+			_("Data source is missing a [%s] group"),
+			PRIMARY_GROUP_NAME);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+server_side_source_traverse_cb (GNode *node,
+                                GQueue *queue)
+{
+	g_queue_push_tail (queue, g_object_ref (node->data));
+
+	return FALSE;
+}
+
+static gboolean
+server_side_source_allow_auth_prompt_cb (EDBusSource *interface,
+                                         GDBusMethodInvocation *invocation,
+                                         EServerSideSource *source)
+{
+	e_server_side_source_set_allow_auth_prompt (source, TRUE);
+
+	e_dbus_source_complete_allow_auth_prompt (interface, invocation);
+
+	return TRUE;
+}
+
+static gboolean
+server_side_source_remove_cb (EDBusSourceRemovable *interface,
+                              GDBusMethodInvocation *invocation,
+                              EServerSideSource *source)
+{
+	GQueue queue = G_QUEUE_INIT;
+	GList *head, *link;
+	GNode *node;
+	GError *error = NULL;
+
+	/* Collect the source and its descendants into a queue and
+	 * check that all queued sources are removable.  We do this
+	 * here rather than in our remove() method, because this is
+	 * only enforced for client requests.  Server-side code can
+	 * pretty much do what it wants. */
+
+	node = e_server_side_source_get_node (source);
+
+	g_node_traverse (
+		node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+		(GNodeTraverseFunc) server_side_source_traverse_cb, &queue);
+
+	head = g_queue_peek_head_link (&queue);
+
+	for (link = head; link != NULL; link = g_list_next (link)) {
+		ESource *child = E_SOURCE (link->data);
+
+		if (!e_source_get_removable (child)) {
+			g_set_error (
+				&error, G_IO_ERROR,
+				G_IO_ERROR_PERMISSION_DENIED,
+				_("Data source '%s' not removable"),
+				e_source_get_display_name (child));
+			break;
+		}
+	}
+
+	if (error == NULL)
+		e_source_remove_sync (E_SOURCE (source), NULL, &error);
+
+	while (!g_queue_is_empty (&queue))
+		g_object_unref (g_queue_pop_head (&queue));
+
+	if (error != NULL)
+		g_dbus_method_invocation_take_error (invocation, error);
+	else
+		e_dbus_source_removable_complete_remove (
+			interface, invocation);
+
+	return TRUE;
+}
+
+static gboolean
+server_side_source_write_cb (EDBusSourceWritable *interface,
+                             GDBusMethodInvocation *invocation,
+                             const gchar *data,
+                             EServerSideSource *source)
+{
+	GKeyFile *key_file;
+	GError *error = NULL;
+
+	/* Note we don't need to verify the source is writable here
+	 * since if it isn't, the write() method won't be available. */
+
+	key_file = g_key_file_new ();
+
+	server_side_source_parse_data (key_file, data, strlen (data), &error);
+
+	g_key_file_free (key_file);
+
+	if (error == NULL)
+		e_source_write_sync (E_SOURCE (source), NULL, &error);
+
+	if (error != NULL)
+		g_dbus_method_invocation_take_error (invocation, error);
+	else
+		e_dbus_source_writable_complete_write (
+			interface, invocation);
+
+	return TRUE;
+}
+
+static gboolean
+server_side_source_changed_idle_cb (gpointer user_data)
+{
+	EServerSideSourcePrivate *priv;
+	ESource *source = E_SOURCE (user_data);
+	GError *error = NULL;
+
+	priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
+	priv->changed_idle_id = 0;
+
+	/* This updates both D-Bus and the key file. */
+	e_source_write_sync (source, NULL, &error);
+
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+
+	return FALSE;
+}
+
+static void
+server_side_source_set_file (EServerSideSource *source,
+                             GFile *file)
+{
+	g_return_if_fail (file == NULL || G_IS_FILE (file));
+	g_return_if_fail (source->priv->file == NULL);
+
+	if (file != NULL)
+		source->priv->file = g_object_ref (file);
+}
+
+static void
+server_side_source_set_server (EServerSideSource *source,
+                               EDBusSourceServer *server)
+{
+	g_return_if_fail (E_IS_DBUS_SOURCE_SERVER (server));
+	g_return_if_fail (source->priv->server == NULL);
+
+	source->priv->server = server;
+
+	g_object_add_weak_pointer (
+		G_OBJECT (server), &source->priv->server);
+}
+
+static void
+server_side_source_set_property (GObject *object,
+                                 guint property_id,
+                                 const GValue *value,
+                                 GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ALLOW_AUTH_PROMPT:
+			e_server_side_source_set_allow_auth_prompt (
+				E_SERVER_SIDE_SOURCE (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_FILE:
+			server_side_source_set_file (
+				E_SERVER_SIDE_SOURCE (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_REMOVABLE:
+			e_server_side_source_set_removable (
+				E_SERVER_SIDE_SOURCE (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_SERVER:
+			server_side_source_set_server (
+				E_SERVER_SIDE_SOURCE (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_WRITABLE:
+			e_server_side_source_set_writable (
+				E_SERVER_SIDE_SOURCE (object),
+				g_value_get_boolean (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+server_side_source_get_property (GObject *object,
+                                 guint property_id,
+                                 GValue *value,
+                                 GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ALLOW_AUTH_PROMPT:
+			g_value_set_boolean (
+				value,
+				e_server_side_source_get_allow_auth_prompt (
+				E_SERVER_SIDE_SOURCE (object)));
+			return;
+
+		case PROP_FILE:
+			g_value_set_object (
+				value,
+				e_server_side_source_get_file (
+				E_SERVER_SIDE_SOURCE (object)));
+			return;
+
+		case PROP_REMOVABLE:
+			g_value_set_boolean (
+				value,
+				e_source_get_removable (
+				E_SOURCE (object)));
+			return;
+
+		case PROP_SERVER:
+			g_value_set_object (
+				value,
+				e_server_side_source_get_server (
+				E_SERVER_SIDE_SOURCE (object)));
+			return;
+
+		case PROP_WRITABLE:
+			g_value_set_boolean (
+				value,
+				e_source_get_writable (
+				E_SOURCE (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+server_side_source_dispose (GObject *object)
+{
+	EServerSideSourcePrivate *priv;
+
+	priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (object);
+
+	if (priv->server != NULL) {
+		g_object_remove_weak_pointer (
+			G_OBJECT (priv->server), &priv->server);
+		priv->server = NULL;
+	}
+
+	if (priv->file != NULL) {
+		g_object_unref (priv->file);
+		priv->file = NULL;
+	}
+
+	if (priv->changed_idle_id > 0) {
+		g_source_remove (priv->changed_idle_id);
+		priv->changed_idle_id = 0;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_server_side_source_parent_class)->dispose (object);
+}
+
+static void
+server_side_source_finalize (GObject *object)
+{
+	EServerSideSourcePrivate *priv;
+
+	priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (object);
+
+	g_node_unlink (&priv->node);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_server_side_source_parent_class)->finalize (object);
+}
+
+static void
+server_side_source_constructed (GObject *object)
+{
+	EServerSideSource *source;
+	EDBusObject *dbus_object;
+	EDBusSource *dbus_source;
+	GFile *file;
+	gchar *uid;
+
+	source = E_SERVER_SIDE_SOURCE (object);
+	file = e_server_side_source_get_file (source);
+
+	dbus_object = e_source_get_dbus_object (E_SOURCE (source));
+
+	dbus_source = e_dbus_source_skeleton_new ();
+
+	e_dbus_object_skeleton_set_source (
+		E_DBUS_OBJECT_SKELETON (dbus_object), dbus_source);
+
+	if (file != NULL)
+		uid = g_file_get_basename (file);
+	else
+		uid = e_uid_new ();
+	e_dbus_source_set_uid (dbus_source, uid);
+	g_free (uid);
+
+	g_signal_connect (
+		dbus_source, "handle-allow-auth-prompt",
+		G_CALLBACK (server_side_source_allow_auth_prompt_cb), source);
+
+	g_object_unref (dbus_source);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_server_side_source_parent_class)->
+		constructed (object);
+}
+
+static void
+server_side_source_changed (ESource *source)
+{
+	EServerSideSourcePrivate *priv;
+
+	priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
+
+	/* Schedule an idle callback. */
+	if (priv->changed_idle_id == 0)
+		priv->changed_idle_id = g_idle_add (
+			server_side_source_changed_idle_cb, source);
+}
+
+static gboolean
+server_side_source_remove_sync (ESource *source,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+	AsyncClosure *closure;
+	GAsyncResult *result;
+	gboolean success;
+
+	closure = async_closure_new ();
+
+	e_source_remove (
+		source, cancellable, async_closure_callback, closure);
+
+	result = async_closure_wait (closure);
+
+	success = e_source_remove_finish (source, result, error);
+
+	async_closure_free (closure);
+
+	return success;
+}
+
+static void
+server_side_source_remove (ESource *source,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data)
+{
+	EServerSideSourcePrivate *priv;
+	GSimpleAsyncResult *simple;
+	EDBusSourceServer *server;
+	GQueue queue = G_QUEUE_INIT;
+	GList *list, *link;
+	gboolean success = TRUE;
+	GError *error = NULL;
+
+	/* XXX Yes we block here.  We do this operation
+	 *     synchronously to keep the server code simple. */
+
+	priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (source), callback, user_data,
+		server_side_source_remove);
+
+	/* Collect the source and its descendants into a queue.
+	 * Do this before unexporting so we hold references to
+	 * all the removed sources. */
+	g_node_traverse (
+		&priv->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+		(GNodeTraverseFunc) server_side_source_traverse_cb, &queue);
+
+	/* Unexport the object and its descendants. */
+	server = E_DBUS_SOURCE_SERVER (priv->server);
+	e_dbus_source_server_remove_source (server, source);
+
+	list = g_queue_peek_head_link (&queue);
+
+	/* Delete the key file for each source in the queue. */
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		EServerSideSource *child;
+		GFile *file;
+
+		child = E_SERVER_SIDE_SOURCE (link->data);
+		file = e_server_side_source_get_file (child);
+
+		if (file != NULL)
+			success = g_file_delete (file, cancellable, &error);
+
+		if (!success)
+			goto exit;
+	}
+
+exit:
+	while (!g_queue_is_empty (&queue))
+		g_object_unref (g_queue_pop_head (&queue));
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete_in_idle (simple);
+	g_object_unref (simple);
+}
+
+static gboolean
+server_side_source_remove_finish (ESource *source,
+                                  GAsyncResult *result,
+                                  GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (source),
+		server_side_source_remove), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static gboolean
+server_side_source_write_sync (ESource *source,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+	AsyncClosure *closure;
+	GAsyncResult *result;
+	gboolean success;
+
+	closure = async_closure_new ();
+
+	e_source_write (
+		source, cancellable, async_closure_callback, closure);
+
+	result = async_closure_wait (closure);
+
+	success = e_source_write_finish (source, result, error);
+
+	async_closure_free (closure);
+
+	return success;
+}
+
+static void
+server_side_source_write (ESource *source,
+                          GCancellable *cancellable,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
+{
+	EServerSideSourcePrivate *priv;
+	GSimpleAsyncResult *simple;
+	EDBusObject *dbus_object;
+	EDBusSource *dbus_source;
+	gchar *old_data;
+	gchar *new_data;
+	gsize length;
+	GError *error = NULL;
+
+	/* XXX Yes we block here.  We do this operation
+	 *     synchronously to keep the server code simple. */
+
+	priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (source), callback, user_data,
+		server_side_source_write);
+
+	dbus_object = e_source_get_dbus_object (source);
+	dbus_source = e_dbus_object_peek_source (dbus_object);
+
+	old_data = e_dbus_source_dup_data (dbus_source);
+	new_data = e_source_to_string (source, &length);
+
+	/* Setting the "data" property triggers ESource::changed,
+	 * which calls e_source_write_sync(), which sets the "data"
+	 * property, etc.  This breaks an otherwise infinite loop. */
+	if (g_strcmp0 (old_data, new_data) != 0)
+		e_dbus_source_set_data (dbus_source, new_data);
+
+	/* We always write to the key file, though. */
+
+	if (priv->file != NULL) {
+		const gchar *dirname;
+		gchar *basename;
+		gchar *filename;
+		GFile *file;
+
+		/* When writing source data to disk, always write to the
+		 * user directory, even if the key file was originally in
+		 * the system-wide directory. */
+
+		dirname = e_server_side_source_get_user_dir ();
+		basename = g_file_get_basename (priv->file);
+		filename = g_build_filename (dirname, basename, NULL);
+		file = g_file_new_for_path (filename);
+		g_free (filename);
+		g_free (basename);
+
+		if (!g_file_equal (file, priv->file)) {
+			g_object_unref (priv->file);
+			priv->file = g_object_ref (file);
+		}
+
+		g_file_replace_contents (
+			file, new_data, length, NULL, FALSE,
+			G_FILE_CREATE_NONE, NULL, cancellable, &error);
+
+		g_object_unref (file);
+	}
+
+	g_free (old_data);
+	g_free (new_data);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete_in_idle (simple);
+	g_object_unref (simple);
+}
+
+static gboolean
+server_side_source_write_finish (ESource *source,
+                                 GAsyncResult *result,
+                                 GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (source),
+		server_side_source_write), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static gboolean
+server_side_source_initable_init (GInitable *initable,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+	EServerSideSource *source;
+
+	source = E_SERVER_SIDE_SOURCE (initable);
+
+	if (!e_server_side_source_load (source, cancellable, error))
+		return FALSE;
+
+	/* Chain up to parent interface's init() method. */
+	return initable_parent_interface->init (initable, cancellable, error);
+}
+
+static void
+e_server_side_source_class_init (EServerSideSourceClass *class)
+{
+	GObjectClass *object_class;
+	ESourceClass *source_class;
+
+	g_type_class_add_private (class, sizeof (EServerSideSourcePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = server_side_source_set_property;
+	object_class->get_property = server_side_source_get_property;
+	object_class->dispose = server_side_source_dispose;
+	object_class->finalize = server_side_source_finalize;
+	object_class->constructed = server_side_source_constructed;
+
+	source_class = E_SOURCE_CLASS (class);
+	source_class->changed = server_side_source_changed;
+	source_class->remove_sync = server_side_source_remove_sync;
+	source_class->remove = server_side_source_remove;
+	source_class->remove_finish = server_side_source_remove_finish;
+	source_class->write_sync = server_side_source_write_sync;
+	source_class->write = server_side_source_write;
+	source_class->write_finish = server_side_source_write_finish;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ALLOW_AUTH_PROMPT,
+		g_param_spec_boolean (
+			"allow-auth-prompt",
+			"Allow Auth Prompt",
+			"Whether authentication sessions may "
+			"interrupt the user for a password",
+			TRUE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FILE,
+		g_param_spec_object (
+			"file",
+			"File",
+			"The key file for the data source",
+			G_TYPE_FILE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	/* This overrides the "removable" property
+	 * in ESourceClass with a writable version. */
+	g_object_class_install_property (
+		object_class,
+		PROP_REMOVABLE,
+		g_param_spec_boolean (
+			"removable",
+			"Removable",
+			"Whether the data source is removable",
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SERVER,
+		g_param_spec_object (
+			"server",
+			"Server",
+			"The server to which the data source belongs",
+			E_TYPE_DBUS_SOURCE_SERVER,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	/* This overrides the "writable" property
+	 * in ESourceClass with a writable version. */
+	g_object_class_install_property (
+		object_class,
+		PROP_WRITABLE,
+		g_param_spec_boolean (
+			"writable",
+			"Writable",
+			"Whether the data source is writable",
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_server_side_source_initable_init (GInitableIface *interface)
+{
+	initable_parent_interface = g_type_interface_peek_parent (interface);
+
+	interface->init = server_side_source_initable_init;
+}
+
+static void
+e_server_side_source_init (EServerSideSource *source)
+{
+	source->priv = E_SERVER_SIDE_SOURCE_GET_PRIVATE (source);
+
+	source->priv->node.data = source;
+}
+
+const gchar *
+e_server_side_source_get_user_dir (void)
+{
+	static gchar *dirname = NULL;
+
+	if (G_UNLIKELY (dirname == NULL)) {
+		const gchar *config_dir = e_get_user_config_dir ();
+		dirname = g_build_filename (config_dir, "sources", NULL);
+		g_mkdir_with_parents (dirname, 0700);
+	}
+
+	return dirname;
+}
+
+ESource *
+e_server_side_source_new (EDBusSourceServer *server,
+                          GFile *file,
+                          GError **error)
+{
+	EDBusObjectSkeleton *dbus_object;
+	ESource *source;
+
+	g_return_val_if_fail (E_IS_DBUS_SOURCE_SERVER (server), NULL);
+	g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
+
+	/* XXX This is an awkward way of initializing the "dbus-object"
+	 *     property, but e_source_get_dbus_object() needs to work. */
+	dbus_object = e_dbus_object_skeleton_new (DBUS_OBJECT_PATH);
+
+	source = g_initable_new (
+		E_TYPE_SERVER_SIDE_SOURCE, NULL, error,
+		"dbus-object", dbus_object,
+		"file", file, "server", server, NULL);
+
+	g_object_unref (dbus_object);
+
+	return source;
+}
+
+gboolean
+e_server_side_source_load (EServerSideSource *source,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+	EDBusObject *dbus_object;
+	EDBusSource *dbus_source;
+	GFileInfo *file_info;
+	GKeyFile *key_file;
+	GFile *file;
+	gboolean file_is_writable = TRUE;
+	gboolean success;
+	gchar *data = NULL;
+	gsize length;
+	GError *local_error = NULL;
+
+	g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), FALSE);
+
+	file = e_server_side_source_get_file (source);
+
+	/* Data sources with no GFile always load successfully. */
+	if (file == NULL)
+		return TRUE;
+
+	g_file_load_contents (
+		file, cancellable, &data, &length, NULL, &local_error);
+
+	/* Sanity check. */
+	g_warn_if_fail (
+		((data != NULL) && (local_error == NULL)) ||
+		((data == NULL) && (local_error != NULL)));
+
+	/* Disregard G_IO_ERROR_NOT_FOUND and treat it as a successful load. */
+	if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+		g_error_free (local_error);
+
+		/* Create the bare minimum to pass parse_data(). */
+		data = g_strdup_printf ("[%s]", PRIMARY_GROUP_NAME);
+		length = strlen (data);
+
+	} else if (local_error != NULL) {
+		g_propagate_error (error, local_error);
+		return FALSE;
+	}
+
+	key_file = g_key_file_new ();
+
+	success = server_side_source_parse_data (
+		key_file, data, length, error);
+
+	g_key_file_free (key_file);
+
+	if (!success) {
+		g_free (data);
+		return FALSE;
+	}
+
+	file_info = g_file_query_info (
+		file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+		G_FILE_QUERY_INFO_NONE, cancellable, NULL);
+
+	/* This part is optional, so ignore errors. */
+	if (file_info != NULL) {
+		file_is_writable = g_file_info_get_attribute_boolean (
+			file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
+		g_object_unref (file_info);
+	}
+
+	/* Update the D-Bus interface properties. */
+
+	dbus_object = e_source_get_dbus_object (E_SOURCE (source));
+	dbus_source = e_dbus_object_get_source (dbus_object);
+
+	e_dbus_source_set_data (dbus_source, data);
+
+	/* If we cannot write to the key file then clearly the
+	 * data source is not writable, but the inverse is not
+	 * necessarily true: the data source may still not be
+	 * writable even if its key file is. */
+	if (!file_is_writable)
+		e_server_side_source_set_writable (source, FALSE);
+
+	g_object_unref (dbus_source);
+
+	g_free (data);
+
+	return TRUE;
+}
+
+GFile *
+e_server_side_source_get_file (EServerSideSource *source)
+{
+	g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
+
+	return source->priv->file;
+}
+
+GNode *
+e_server_side_source_get_node (EServerSideSource *source)
+{
+	g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
+
+	return &source->priv->node;
+}
+
+EDBusSourceServer *
+e_server_side_source_get_server (EServerSideSource *source)
+{
+	g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), NULL);
+
+	return source->priv->server;
+}
+
+gboolean
+e_server_side_source_get_allow_auth_prompt (EServerSideSource *source)
+{
+	g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), FALSE);
+
+	return source->priv->allow_auth_prompt;
+}
+
+void
+e_server_side_source_set_allow_auth_prompt (EServerSideSource *source,
+                                            gboolean allow_auth_prompt)
+{
+	g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+	source->priv->allow_auth_prompt = allow_auth_prompt;
+
+	g_object_notify (G_OBJECT (source), "allow-auth-prompt");
+}
+
+void
+e_server_side_source_set_removable (EServerSideSource *source,
+                                    gboolean removable)
+{
+	EDBusSourceRemovable *dbus_source_removable = NULL;
+	EDBusObject *dbus_object;
+	gboolean currently_removable;
+
+	g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+	dbus_object = e_source_get_dbus_object (E_SOURCE (source));
+	currently_removable = e_source_get_removable (E_SOURCE (source));
+
+	if (removable == currently_removable)
+		return;
+
+	if (removable) {
+		dbus_source_removable =
+			e_dbus_source_removable_skeleton_new ();
+
+		g_signal_connect (
+			dbus_source_removable, "handle-remove",
+			G_CALLBACK (server_side_source_remove_cb), source);
+	}
+
+	e_dbus_object_skeleton_set_source_removable (
+		E_DBUS_OBJECT_SKELETON (dbus_object), dbus_source_removable);
+
+	if (dbus_source_removable != NULL)
+		g_object_unref (dbus_source_removable);
+
+	g_object_notify (G_OBJECT (source), "removable");
+}
+
+void
+e_server_side_source_set_writable (EServerSideSource *source,
+                                   gboolean writable)
+{
+	EDBusSourceWritable *dbus_source_writable = NULL;
+	EDBusObject *dbus_object;
+	gboolean currently_writable;
+
+	g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
+
+	dbus_object = e_source_get_dbus_object (E_SOURCE (source));
+	currently_writable = e_source_get_writable (E_SOURCE (source));
+
+	if (writable == currently_writable)
+		return;
+
+	if (writable) {
+		dbus_source_writable =
+			e_dbus_source_writable_skeleton_new ();
+
+		g_signal_connect (
+			dbus_source_writable, "handle-write",
+			G_CALLBACK (server_side_source_write_cb), source);
+	}
+
+	e_dbus_object_skeleton_set_source_writable (
+		E_DBUS_OBJECT_SKELETON (dbus_object), dbus_source_writable);
+
+	if (dbus_source_writable != NULL)
+		g_object_unref (dbus_source_writable);
+
+	g_object_notify (G_OBJECT (source), "writable");
+}
+
diff --git a/libebackend/e-server-side-source.h b/libebackend/e-server-side-source.h
new file mode 100644
index 0000000..88bcac9
--- /dev/null
+++ b/libebackend/e-server-side-source.h
@@ -0,0 +1,87 @@
+/*
+ * e-server-side-source.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_SERVER_SIDE_SOURCE_H
+#define E_SERVER_SIDE_SOURCE_H
+
+#include <libedataserver/e-source.h>
+#include <libebackend/e-dbus-source-server.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SERVER_SIDE_SOURCE \
+	(e_server_side_source_get_type ())
+#define E_SERVER_SIDE_SOURCE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSource))
+#define E_SERVER_SIDE_SOURCE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSourceClass))
+#define E_IS_SERVER_SIDE_SOURCE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SERVER_SIDE_SOURCE))
+#define E_IS_SERVER_SIDE_SOURCE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SERVER_SIDE_SOURCE))
+#define E_SERVER_SIDE_SOURCE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SERVER_SIDE_SOURCE, EServerSideSourceClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EServerSideSource EServerSideSource;
+typedef struct _EServerSideSourceClass EServerSideSourceClass;
+typedef struct _EServerSideSourcePrivate EServerSideSourcePrivate;
+
+struct _EServerSideSource {
+	ESource parent;
+	EServerSideSourcePrivate *priv;
+};
+
+struct _EServerSideSourceClass {
+	ESourceClass parent_class;
+};
+
+GType		e_server_side_source_get_type	(void) G_GNUC_CONST;
+const gchar *	e_server_side_source_get_user_dir
+						(void) G_GNUC_CONST;
+ESource *	e_server_side_source_new	(EDBusSourceServer *server,
+						 GFile *file,
+						 GError **error);
+gboolean	e_server_side_source_load	(EServerSideSource *source,
+						 GCancellable *cancellable,
+						 GError **error);
+GFile *		e_server_side_source_get_file	(EServerSideSource *source);
+GNode *		e_server_side_source_get_node	(EServerSideSource *source);
+EDBusSourceServer *
+		e_server_side_source_get_server	(EServerSideSource *source);
+gboolean	e_server_side_source_get_allow_auth_prompt
+						(EServerSideSource *source);
+void		e_server_side_source_set_allow_auth_prompt
+						(EServerSideSource *source,
+						 gboolean allow_auth_prompt);
+void		e_server_side_source_set_removable
+						(EServerSideSource *source,
+						 gboolean removable);
+void		e_server_side_source_set_writable
+						(EServerSideSource *source,
+						 gboolean writable);
+
+G_END_DECLS
+
+#endif /* E_SERVER_SIDE_SOURCE_H */
+
diff --git a/services/Makefile.am b/services/Makefile.am
index 1750bfa..551a440 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -3,6 +3,7 @@ NULL =
 SUBDIRS = \
 	evolution-addressbook-factory \
 	evolution-calendar-factory \
+	evolution-source-registry \
 	$(NULL)
 
 -include $(top_srcdir)/git.mk
diff --git a/services/evolution-addressbook-factory/Makefile.am b/services/evolution-addressbook-factory/Makefile.am
index 0bc4a6f..c2beab2 100644
--- a/services/evolution-addressbook-factory/Makefile.am
+++ b/services/evolution-addressbook-factory/Makefile.am
@@ -19,15 +19,16 @@ evolution_addressbook_factory_CPPFLAGS = \
 	-I$(top_builddir) \
 	-I$(top_builddir)/addressbook \
 	$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
+	$(GNOME_KEYRING_CFLAGS) \
 	$(FACTORY_GTK_CFLAGS) \
 	$(DBUS_GLIB_CFLAGS) \
+	$(SOUP_CFLAGS) \
 	$(GOA_CFLAGS) \
 	$(CODE_COVERAGE_CFLAGS) \
 	$(NULL)
 
 evolution_addressbook_factory_SOURCES = \
 	evolution-addressbook-factory.c \
-	evolution-addressbook-factory-migrate-basedir.c \
 	$(NULL)
 
 evolution_addressbook_factory_LDADD = \
@@ -35,8 +36,10 @@ evolution_addressbook_factory_LDADD = \
 	$(top_builddir)/libebackend/libebackend-1.2.la \
 	$(top_builddir)/libedataserver/libedataserver-1.2.la \
 	$(EVOLUTION_ADDRESSBOOK_LIBS) \
+	$(GNOME_KEYRING_LIBS) \
 	$(FACTORY_GTK_LIBS) \
 	$(DBUS_GLIB_LIBS) \
+	$(SOUP_LIBS) \
 	$(GOA_LIBS) \
 	$(NULL)
 
diff --git a/services/evolution-addressbook-factory/evolution-addressbook-factory.c b/services/evolution-addressbook-factory/evolution-addressbook-factory.c
index aa1080d..9450c81 100644
--- a/services/evolution-addressbook-factory/evolution-addressbook-factory.c
+++ b/services/evolution-addressbook-factory/evolution-addressbook-factory.c
@@ -53,9 +53,6 @@ static GOptionEntry entries[] = {
 	{ NULL }
 };
 
-/* Forward Declarations */
-void evolution_addressbook_factory_migrate_basedir (void);
-
 gint
 main (gint argc,
       gchar **argv)
@@ -119,9 +116,6 @@ main (gint argc,
 		exit (EXIT_FAILURE);
 	}
 
-	/* Migrate user data from ~/.evolution to XDG base directories. */
-	evolution_addressbook_factory_migrate_basedir ();
-
 	e_gdbus_templates_init_main_thread ();
 
 	server = e_data_book_factory_new (NULL, &error);
diff --git a/services/evolution-calendar-factory/Makefile.am b/services/evolution-calendar-factory/Makefile.am
index 991191c..3c8fdcd 100644
--- a/services/evolution-calendar-factory/Makefile.am
+++ b/services/evolution-calendar-factory/Makefile.am
@@ -19,14 +19,15 @@ evolution_calendar_factory_CPPFLAGS = \
 	-I$(top_builddir) \
 	-I$(top_builddir)/calendar \
 	$(EVOLUTION_CALENDAR_CFLAGS) \
+	$(GNOME_KEYRING_CFLAGS) \
 	$(FACTORY_GTK_CFLAGS) \
 	$(DBUS_GLIB_CFLAGS) \
+	$(SOUP_CFLAGS) \
 	$(CODE_COVERAGE_CFLAGS) \
 	$(NULL)
 
 evolution_calendar_factory_SOURCES = \
 	evolution-calendar-factory.c \
-	evolution-calendar-factory-migrate-basedir.c \
 	$(NULL)
 
 evolution_calendar_factory_LDADD = \
@@ -34,8 +35,10 @@ evolution_calendar_factory_LDADD = \
 	$(top_builddir)/libebackend/libebackend-1.2.la \
 	$(top_builddir)/libedataserver/libedataserver-1.2.la \
 	$(EVOLUTION_CALENDAR_LIBS) \
+	$(GNOME_KEYRING_LIBS) \
 	$(FACTORY_GTK_LIBS) \
 	$(DBUS_GLIB_LIBS) \
+	$(SOUP_LIBS) \
 	$(NULL)
 
 evolution_calendar_factory_LDFLAGS = \
diff --git a/services/evolution-calendar-factory/evolution-calendar-factory.c b/services/evolution-calendar-factory/evolution-calendar-factory.c
index a044e62..aa76072 100644
--- a/services/evolution-calendar-factory/evolution-calendar-factory.c
+++ b/services/evolution-calendar-factory/evolution-calendar-factory.c
@@ -57,9 +57,6 @@ static GOptionEntry entries[] = {
 	{ NULL }
 };
 
-/* Forward Declarations */
-void evolution_calendar_factory_migrate_basedir (void);
-
 gint
 main (gint argc,
       gchar **argv)
@@ -127,9 +124,6 @@ main (gint argc,
 	ical_set_unknown_token_handling_setting (ICAL_DISCARD_TOKEN);
 #endif
 
-	/* Migrate user data from ~/.evolution to XDG base directories. */
-	evolution_calendar_factory_migrate_basedir ();
-
 	e_gdbus_templates_init_main_thread ();
 
 	server = e_data_cal_factory_new (NULL, &error);
diff --git a/services/evolution-source-registry/Makefile.am b/services/evolution-source-registry/Makefile.am
new file mode 100644
index 0000000..6cf7b04
--- /dev/null
+++ b/services/evolution-source-registry/Makefile.am
@@ -0,0 +1,49 @@
+NULL =
+
+service_in_files = org.gnome.evolution.dataserver.Sources.service.in
+servicedir = $(datadir)/dbus-1/services
+service_DATA = $(service_in_files:.service.in=.service)
+ EVO_SUBST_SERVICE_RULE@
+
+CLEANFILES = $(service_DATA)
+EXTRA_DIST = $(service_in_files)
+
+libexec_PROGRAMS = evolution-source-registry
+
+evolution_source_registry_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/calendar \
+	-I$(top_srcdir)/addressbook \
+	-I$(top_builddir) \
+	-DG_LOG_DOMAIN=\"evolution-source-registry\" \
+	-DLOCALEDIR=\"$(localedir)\" \
+	$(E_DATA_SERVER_UI_CFLAGS) \
+	$(GNOME_KEYRING_CFLAGS) \
+	$(CAMEL_CFLAGS) \
+	$(SOUP_CFLAGS) \
+	$(NULL)
+
+evolution_source_registry_SOURCES = \
+	evolution-source-registry.c \
+	evolution-source-registry-migrate-basedir.c \
+	evolution-source-registry-migrate-sources.c \
+	e-authentication-dialog.c \
+	e-authentication-dialog.h \
+	e-authenticator.c \
+	e-authenticator.h \
+	$(NULL)
+
+evolution_source_registry_LDADD = \
+	$(top_builddir)/calendar/libecal/libecal-1.2.la \
+	$(top_builddir)/addressbook/libebook/libebook-1.2.la \
+	$(top_builddir)/libebackend/libebackend-1.2.la \
+	$(top_builddir)/libedataserver/libedataserver-1.2.la \
+	$(top_builddir)/camel/libcamel-1.2.la \
+	$(E_DATA_SERVER_UI_LIBS) \
+	$(GNOME_KEYRING_LIBS) \
+	$(CAMEL_LIBS) \
+	$(SOUP_LIBS) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/services/evolution-source-registry/e-authentication-dialog.c b/services/evolution-source-registry/e-authentication-dialog.c
new file mode 100644
index 0000000..4383b56
--- /dev/null
+++ b/services/evolution-source-registry/e-authentication-dialog.c
@@ -0,0 +1,622 @@
+/*
+ * e-authentication-dialog.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-authentication-dialog.h"
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include <libebackend/e-backend-enumtypes.h>
+#include <libedataserver/e-source-address-book.h>
+#include <libedataserver/e-source-authentication.h>
+#include <libedataserver/e-source-calendar.h>
+#include <libedataserver/e-source-mail-account.h>
+
+#define E_AUTHENTICATION_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_AUTHENTICATION_DIALOG, EAuthenticationDialogPrivate))
+
+#define TRY_AGAIN_DELAY  3  /* seconds */
+
+struct _EAuthenticationDialogPrivate {
+	EDBusSourceAuthenticator *authenticator;
+
+	GtkWidget *auth_button;			/* not_referenced */
+	GtkWidget *cancel_button;		/* not referenced */
+	GtkWidget *entry;			/* not referenced */
+	GtkWidget *warning;			/* not referenced */
+
+	/* These form a radio group. */
+	GtkToggleButton *remember_none;		/* not referenced */
+	GtkToggleButton *remember_session;	/* not referenced */
+	GtkToggleButton *remember_forever;	/* not referenced */
+
+	gchar *password;
+	gboolean authenticating;
+	guint try_again_timeout;
+};
+
+enum {
+	PROP_0,
+	PROP_AUTHENTICATING,
+	PROP_AUTHENTICATOR,
+	PROP_PASSWORD,
+	PROP_REMEMBER_TYPE
+};
+
+G_DEFINE_TYPE (
+	EAuthenticationDialog,
+	e_authentication_dialog,
+	GTK_TYPE_DIALOG)
+
+static void
+authentication_dialog_remember_toggled_cb (GtkToggleButton *toggle_button,
+                                           EAuthenticationDialog *dialog)
+{
+	if (gtk_toggle_button_get_active (toggle_button))
+		g_object_notify (G_OBJECT (dialog), "remember-type");
+}
+
+static void
+authentication_dialog_set_authenticator (EAuthenticationDialog *dialog,
+                                         EDBusSourceAuthenticator *authenticator)
+{
+	g_return_if_fail (E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+	g_return_if_fail (dialog->priv->authenticator == NULL);
+
+	dialog->priv->authenticator = g_object_ref (authenticator);
+}
+
+static void
+authentication_dialog_set_property (GObject *object,
+                                    guint property_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_AUTHENTICATOR:
+			authentication_dialog_set_authenticator (
+				E_AUTHENTICATION_DIALOG (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+authentication_dialog_get_property (GObject *object,
+                                    guint property_id,
+                                    GValue *value,
+                                    GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_AUTHENTICATING:
+			g_value_set_boolean (
+				value,
+				e_authentication_dialog_get_authenticating (
+				E_AUTHENTICATION_DIALOG (object)));
+			return;
+
+		case PROP_AUTHENTICATOR:
+			g_value_set_object (
+				value,
+				e_authentication_dialog_get_authenticator (
+				E_AUTHENTICATION_DIALOG (object)));
+
+		case PROP_PASSWORD:
+			g_value_set_string (
+				value,
+				e_authentication_dialog_get_password (
+				E_AUTHENTICATION_DIALOG (object)));
+			return;
+
+		case PROP_REMEMBER_TYPE:
+			g_value_set_enum (
+				value,
+				e_authentication_dialog_get_remember_type (
+				E_AUTHENTICATION_DIALOG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+authentication_dialog_dispose (GObject *object)
+{
+	EAuthenticationDialogPrivate *priv;
+
+	priv = E_AUTHENTICATION_DIALOG_GET_PRIVATE (object);
+
+	if (priv->authenticator != NULL) {
+		g_object_unref (priv->authenticator);
+		priv->authenticator = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_authentication_dialog_parent_class)->
+		dispose (object);
+}
+
+static void
+authentication_dialog_finalize (GObject *object)
+{
+	EAuthenticationDialogPrivate *priv;
+
+	priv = E_AUTHENTICATION_DIALOG_GET_PRIVATE (object);
+
+	if (priv->try_again_timeout > 0)
+		g_source_remove (priv->try_again_timeout);
+
+	g_free (priv->password);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_authentication_dialog_parent_class)->
+		finalize (object);
+}
+
+static void
+authentication_dialog_constructed (GObject *object)
+{
+	EAuthenticationDialogPrivate *priv;
+	EDBusSourceAuthenticator *authenticator;
+	GtkDialog *dialog;
+	GtkWindow *window;
+	GtkWidget *action_area;
+	GtkWidget *content_area;
+	GtkWidget *radio_box;
+	GtkWidget *container;
+	GtkWidget *widget;
+	GIcon *icon;
+	GSList *group = NULL;
+	const gchar *text;
+	const gchar *stock_id;
+	gint row;
+
+	stock_id = GTK_STOCK_DIALOG_AUTHENTICATION;
+	priv = E_AUTHENTICATION_DIALOG_GET_PRIVATE (object);
+
+	authenticator = e_authentication_dialog_get_authenticator (
+		E_AUTHENTICATION_DIALOG (object));
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_authentication_dialog_parent_class)->
+		constructed (object);
+
+	window = GTK_WINDOW (object);
+	gtk_window_set_title (window, "");
+	gtk_window_set_position (window, GTK_WIN_POS_CENTER);
+	gtk_window_set_modal (window, TRUE);
+	gtk_window_set_resizable (window, FALSE);
+	gtk_window_set_keep_above (window, TRUE);
+	gtk_window_set_icon_name (window, stock_id);
+
+	dialog = GTK_DIALOG (object);
+	action_area = gtk_dialog_get_action_area (dialog);
+	content_area = gtk_dialog_get_content_area (dialog);
+
+	priv->cancel_button = gtk_dialog_add_button (
+		dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+
+	priv->auth_button = gtk_dialog_add_button (
+		dialog, _("_Authenticate"), GTK_RESPONSE_OK);
+
+	gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
+
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+	gtk_box_set_spacing (GTK_BOX (content_area), 2);  /* 2 * 5 + 2 = 12 */
+	gtk_container_set_border_width (GTK_CONTAINER (action_area), 5);
+	gtk_box_set_spacing (GTK_BOX (action_area), 6);
+
+	widget = gtk_grid_new ();
+	gtk_grid_set_column_spacing (GTK_GRID (widget), 12);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 5);
+	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	/* XXX gtk_image_new_from_gicon() disregards the icon size we pass
+	 *     it when the initial GIcon is NULL.  Pass something non-NULL
+	 *     so the icon size sticks, then override it with a binding. */
+	icon = g_themed_icon_new (stock_id);
+	widget = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
+	gtk_widget_set_valign (widget, GTK_ALIGN_START);
+	gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 1);
+	gtk_widget_show (widget);
+	g_object_unref (icon);
+
+	/* XXX Don't use G_BINDING_SYNC_CREATE because the authenticator's
+	 *     GIcon is probably NULL right now, and setting GtkImage:gicon
+	 *     to NULL seems to make the image widget forget the icon size
+	 *     we just assigned it, even WITH the above workaround. */
+	g_object_bind_property (
+		authenticator, "icon",
+		widget, "gicon",
+		G_BINDING_DEFAULT);
+
+	widget = gtk_grid_new ();
+	gtk_grid_set_row_spacing (GTK_GRID (widget), 12);
+	gtk_grid_set_column_spacing (GTK_GRID (widget), 12);
+	gtk_grid_attach (GTK_GRID (container), widget, 1, 0, 1, 1);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	row = 0;  /* Row 0 */
+
+	widget = gtk_label_new (NULL);
+	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+	gtk_widget_set_halign (widget, GTK_ALIGN_START);
+	gtk_widget_set_hexpand (widget, TRUE);
+	gtk_grid_attach (GTK_GRID (container), widget, 0, row, 2, 1);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		authenticator, "primary-markup",
+		widget, "label",
+		G_BINDING_SYNC_CREATE);
+
+	row++;  /* Row 1 */
+
+	/* XXX Packing a line-wrapped GtkLabel sanely is tricky.
+	 *     Setting GtkWidget:halign to GTK_ALIGN_START seems to
+	 *     work in GTK+ 3.2 for the non-wrapped label above, but
+	 *     for this guy, setting GtkMisc:xalign was the only way
+	 *     to convince GTK+ to NOT center it over the entry. */
+	widget = gtk_label_new (NULL);
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_widget_set_hexpand (widget, TRUE);
+	gtk_grid_attach (GTK_GRID (container), widget, 0, row, 2, 1);
+	gtk_widget_show (widget);
+
+	/* Leave some extra space between the label and entry. */
+	gtk_widget_set_margin_bottom (widget, 6);
+
+	g_object_bind_property (
+		authenticator, "secondary-markup",
+		widget, "label",
+		G_BINDING_SYNC_CREATE);
+
+	row++;  /* Row 2 */
+
+	widget = gtk_entry_new ();
+	gtk_widget_set_hexpand (widget, TRUE);
+	gtk_widget_set_can_focus (widget, TRUE);
+	gtk_entry_set_visibility (GTK_ENTRY (widget), FALSE);
+	gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+	gtk_grid_attach (GTK_GRID (container), widget, 1, row, 1, 1);
+	priv->entry = widget;  /* not referenced */
+	gtk_widget_grab_focus (widget);
+	gtk_widget_show (widget);
+
+	widget = gtk_label_new_with_mnemonic ("_Password:");
+	gtk_label_set_mnemonic_widget (GTK_LABEL (widget), priv->entry);
+	gtk_grid_attach (GTK_GRID (container), widget, 0, row, 1, 1);
+	gtk_widget_show (widget);
+
+	row++;  /* Row 3 */
+
+	radio_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+	gtk_grid_attach (GTK_GRID (container), radio_box, 0, row, 2, 1);
+	gtk_widget_show (radio_box);
+
+	text = _("Forget password immediately");
+	widget = gtk_radio_button_new_with_label (group, text);
+	gtk_widget_set_halign (widget, GTK_ALIGN_START);
+	gtk_box_pack_start (GTK_BOX (radio_box), widget, FALSE, TRUE, 0);
+	priv->remember_none = GTK_TOGGLE_BUTTON (widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		widget, "toggled",
+		G_CALLBACK (authentication_dialog_remember_toggled_cb),
+		object);
+
+	group = gtk_radio_button_get_group (
+		GTK_RADIO_BUTTON (priv->remember_none));
+
+	text = _("Remember password until you logout");
+	widget = gtk_radio_button_new_with_label (group, text);
+	gtk_widget_set_halign (widget, GTK_ALIGN_START);
+	gtk_box_pack_start (GTK_BOX (radio_box), widget, FALSE, TRUE, 0);
+	priv->remember_session = GTK_TOGGLE_BUTTON (widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		widget, "toggled",
+		G_CALLBACK (authentication_dialog_remember_toggled_cb),
+		object);
+
+	group = gtk_radio_button_get_group (
+		GTK_RADIO_BUTTON (priv->remember_none));
+
+	text = _("Remember forever");
+	widget = gtk_radio_button_new_with_label (group, text);
+	gtk_widget_set_halign (widget, GTK_ALIGN_START);
+	gtk_box_pack_start (GTK_BOX (radio_box), widget, FALSE, TRUE, 0);
+	priv->remember_forever = GTK_TOGGLE_BUTTON (widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		widget, "toggled",
+		G_CALLBACK (authentication_dialog_remember_toggled_cb),
+		object);
+
+	row++;  /* Row 4 */
+
+	widget = gtk_label_new (" ");
+	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+	gtk_widget_set_halign (widget, GTK_ALIGN_START);
+	gtk_widget_set_hexpand (widget, TRUE);
+	gtk_grid_attach (GTK_GRID (container), widget, 0, row, 2, 1);
+	priv->warning = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	g_object_bind_property (
+		object, "authenticating",
+		priv->auth_button, "sensitive",
+		G_BINDING_SYNC_CREATE |
+		G_BINDING_INVERT_BOOLEAN);
+
+	g_object_bind_property (
+		object, "authenticating",
+		priv->entry, "sensitive",
+		G_BINDING_SYNC_CREATE |
+		G_BINDING_INVERT_BOOLEAN);
+
+	/* Pick "remember until you logout" as the default. */
+	gtk_toggle_button_set_active (priv->remember_session, TRUE);
+}
+
+static void
+authentication_dialog_response (GtkDialog *dialog,
+                                gint response)
+{
+	EAuthenticationDialogPrivate *priv;
+
+	priv = E_AUTHENTICATION_DIALOG_GET_PRIVATE (dialog);
+
+	/* GtkDialog does not implement this method.  Do not chain up. */
+
+	if (response == GTK_RESPONSE_OK) {
+		GdkCursor *cursor;
+		GdkWindow *window;
+		const gchar *text;
+
+		/* Make the cursor appear busy. */
+		cursor = gdk_cursor_new (GDK_WATCH);
+		window = gtk_widget_get_window (GTK_WIDGET (dialog));
+		gdk_window_set_cursor (window, cursor);
+		g_object_unref (cursor);
+
+		text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
+
+		g_free (priv->password);
+		priv->password = g_strdup (text);
+		g_object_notify (G_OBJECT (dialog), "password");
+
+		priv->authenticating = TRUE;
+		g_object_notify (G_OBJECT (dialog), "authenticating");
+	}
+}
+
+static void
+e_authentication_dialog_class_init (EAuthenticationDialogClass *class)
+{
+	GObjectClass *object_class;
+	GtkDialogClass *dialog_class;
+
+	g_type_class_add_private (class, sizeof (EAuthenticationDialogPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = authentication_dialog_set_property;
+	object_class->get_property = authentication_dialog_get_property;
+	object_class->dispose = authentication_dialog_dispose;
+	object_class->finalize = authentication_dialog_finalize;
+	object_class->constructed = authentication_dialog_constructed;
+
+	dialog_class = GTK_DIALOG_CLASS (class);
+	dialog_class->response = authentication_dialog_response;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_AUTHENTICATING,
+		g_param_spec_boolean (
+			"authenticating",
+			"Authenticating",
+			"The user has clicked Authenticate and "
+			"the dialog is waiting for confirmation",
+			FALSE,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_AUTHENTICATOR,
+		g_param_spec_object (
+			"authenticator",
+			"Authenticator",
+			"The D-Bus authenticator object",
+			E_TYPE_DBUS_SOURCE_AUTHENTICATOR,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PASSWORD,
+		g_param_spec_string (
+			"password",
+			"Password",
+			"The user-provided password",
+			NULL,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	/* Default value not honored.  See constructed(). */
+	g_object_class_install_property (
+		object_class,
+		PROP_REMEMBER_TYPE,
+		g_param_spec_enum (
+			"remember-type",
+			"Remember Type",
+			"Whether to remember the password for future "
+			"sessions once authentication is confirmed",
+			E_TYPE_PASSWORD_REMEMBER_TYPE,
+			E_PASSWORD_REMEMBER_SESSION,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_authentication_dialog_init (EAuthenticationDialog *dialog)
+{
+	dialog->priv = E_AUTHENTICATION_DIALOG_GET_PRIVATE (dialog);
+}
+
+GtkWidget *
+e_authentication_dialog_new (EDBusSourceAuthenticator *authenticator)
+{
+	g_return_val_if_fail (
+		E_IS_DBUS_SOURCE_AUTHENTICATOR (authenticator), NULL);
+
+	return g_object_new (
+		E_TYPE_AUTHENTICATION_DIALOG,
+		"authenticator", authenticator, NULL);
+}
+
+gboolean
+e_authentication_dialog_get_authenticating (EAuthenticationDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_AUTHENTICATION_DIALOG (dialog), FALSE);
+
+	return dialog->priv->authenticating;
+}
+
+EDBusSourceAuthenticator *
+e_authentication_dialog_get_authenticator (EAuthenticationDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_AUTHENTICATION_DIALOG (dialog), NULL);
+
+	return dialog->priv->authenticator;
+}
+
+const gchar *
+e_authentication_dialog_get_password (EAuthenticationDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_AUTHENTICATION_DIALOG (dialog), NULL);
+
+	return dialog->priv->password;
+}
+
+EPasswordRememberType
+e_authentication_dialog_get_remember_type (EAuthenticationDialog *dialog)
+{
+	g_return_val_if_fail (
+		E_IS_AUTHENTICATION_DIALOG (dialog),
+		E_PASSWORD_REMEMBER_NONE);
+
+	if (gtk_toggle_button_get_active (dialog->priv->remember_none))
+		return E_PASSWORD_REMEMBER_NONE;
+
+	if (gtk_toggle_button_get_active (dialog->priv->remember_session))
+		return E_PASSWORD_REMEMBER_SESSION;
+
+	if (gtk_toggle_button_get_active (dialog->priv->remember_forever))
+		return E_PASSWORD_REMEMBER_FOREVER;
+
+	g_return_val_if_reached (E_PASSWORD_REMEMBER_NONE);
+}
+
+/* Helper for e_authentication_dialog_try_again() */
+static gboolean
+authentication_dialog_timeout_cb (EAuthenticationDialog *dialog)
+{
+	dialog->priv->try_again_timeout = 0;
+
+	/* Clear the warning message. */
+
+	gtk_label_set_markup (GTK_LABEL (dialog->priv->warning), " ");
+
+	/* Reset the authenticating state and previous password. */
+
+	dialog->priv->authenticating = FALSE;
+	g_object_notify (G_OBJECT (dialog), "authenticating");
+
+	g_free (dialog->priv->password);
+	dialog->priv->password = NULL;
+	g_object_notify (G_OBJECT (dialog), "password");
+
+	/* Put keyboard focus back on the entry. */
+
+	gtk_widget_grab_focus (dialog->priv->entry);
+
+	return FALSE;
+}
+
+void
+e_authentication_dialog_try_again (EAuthenticationDialog *dialog)
+{
+	GdkWindow *window;
+	gchar *markup;
+	gint x, y;
+	gint ii;
+	gint diff;
+
+	g_return_if_fail (E_IS_AUTHENTICATION_DIALOG (dialog));
+
+	/* Set the cursor back to normal. */
+
+	window = gtk_widget_get_window (GTK_WIDGET (dialog));
+	gdk_window_set_cursor (window, NULL);
+
+	/* Shake the window. */
+
+	gtk_window_get_position (GTK_WINDOW (dialog), &x, &y);
+
+	for (ii = 0; ii < 10; ii++) {
+		diff = (ii % 2 == 0) ? -15 : 15;
+		gtk_window_move (GTK_WINDOW (dialog), x + diff, y);
+		while (gtk_events_pending ())
+			gtk_main_iteration ();
+		g_usleep (10000);
+	}
+
+	gtk_window_move (GTK_WINDOW (dialog), x, y);
+
+	/* Show a warning message and pause for a moment. */
+
+	markup = g_markup_printf_escaped (
+		"<b>%s</b>", _("Incorrect password, please try again."));
+	gtk_label_set_markup (GTK_LABEL (dialog->priv->warning), markup);
+	g_free (markup);
+
+	if (dialog->priv->try_again_timeout > 0)
+		g_source_remove (dialog->priv->try_again_timeout);
+
+	dialog->priv->try_again_timeout = gdk_threads_add_timeout_seconds (
+		TRY_AGAIN_DELAY, (GSourceFunc)
+		authentication_dialog_timeout_cb, dialog);
+}
+
diff --git a/services/evolution-source-registry/e-authentication-dialog.h b/services/evolution-source-registry/e-authentication-dialog.h
new file mode 100644
index 0000000..c10d480
--- /dev/null
+++ b/services/evolution-source-registry/e-authentication-dialog.h
@@ -0,0 +1,79 @@
+/*
+ * e-authentication-dialog.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_AUTHENTICATION_DIALOG_H
+#define E_AUTHENTICATION_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <libebackend/e-backend-enums.h>
+#include <libebackend/e-dbus-source-authenticator.h>
+
+/* Standard GObject macros */
+#define E_TYPE_AUTHENTICATION_DIALOG \
+	(e_authentication_dialog_get_type ())
+#define E_AUTHENTICATION_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_AUTHENTICATION_DIALOG, EAuthenticationDialog))
+#define E_AUTHENTICATION_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_AUTHENTICATION_DIALOG, EAuthenticationDialogClass))
+#define E_IS_AUTHENTICATION_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_AUTHENTICATION_DIALOG))
+#define E_IS_AUTHENTICATION_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_AUTHENTICATION_DIALOG))
+#define E_AUTHENTICATION_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_AUTHENTICATION_DIALOG, EAuthenticationDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAuthenticationDialog EAuthenticationDialog;
+typedef struct _EAuthenticationDialogClass EAuthenticationDialogClass;
+typedef struct _EAuthenticationDialogPrivate EAuthenticationDialogPrivate;
+
+struct _EAuthenticationDialog {
+	GtkDialog parent;
+	EAuthenticationDialogPrivate *priv;
+};
+
+struct _EAuthenticationDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_authentication_dialog_get_type
+						(void) G_GNUC_CONST;
+GtkWidget *	e_authentication_dialog_new
+				(EDBusSourceAuthenticator *authenticator);
+gboolean	e_authentication_dialog_get_authenticating
+				(EAuthenticationDialog *dialog);
+EDBusSourceAuthenticator *
+		e_authentication_dialog_get_authenticator
+				(EAuthenticationDialog *dialog);
+const gchar *	e_authentication_dialog_get_password
+				(EAuthenticationDialog *dialog);
+EPasswordRememberType
+		e_authentication_dialog_get_remember_type
+				(EAuthenticationDialog *dialog);
+void		e_authentication_dialog_try_again
+				(EAuthenticationDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_AUTHENTICATION_DIALOG_H */
diff --git a/services/evolution-source-registry/e-authenticator.c b/services/evolution-source-registry/e-authenticator.c
new file mode 100644
index 0000000..cd498df
--- /dev/null
+++ b/services/evolution-source-registry/e-authenticator.c
@@ -0,0 +1,310 @@
+/*
+ * e-authenticator.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-authenticator.h"
+
+#include <libebackend/e-dbus-source-server.h>
+#include <libebackend/e-server-side-source.h>
+
+#include "e-authentication-dialog.h"
+
+#define E_AUTHENTICATOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_AUTHENTICATOR, EAuthenticatorPrivate))
+
+struct _EAuthenticatorPrivate {
+	GCancellable *cancellable;
+	GtkWidget *dialog;
+};
+
+G_DEFINE_TYPE (
+	EAuthenticator,
+	e_authenticator,
+	E_TYPE_DBUS_SOURCE_AUTHENTICATOR)
+
+static void
+authenticator_dialog_response_cb (EAuthenticationDialog *dialog,
+                                  gint response_id,
+                                  EAuthenticator *authenticator)
+{
+	if (response_id == GTK_RESPONSE_OK) {
+		const gchar *password;
+
+		password = e_authentication_dialog_get_password (dialog);
+		g_return_if_fail (password != NULL);
+
+		e_dbus_source_authenticator_transmit (
+			E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+			password);
+
+	} else {
+		e_dbus_source_authenticator_dismiss (
+			E_DBUS_SOURCE_AUTHENTICATOR (authenticator));
+	}
+}
+
+static gboolean
+authenticator_handle_accepted (EDBusAuthenticator *authenticator,
+                               GDBusMethodInvocation *invocation)
+{
+	EAuthenticatorPrivate *priv;
+	EAuthenticationDialog *dialog;
+	EPasswordRememberType remember_type;
+	const gchar *password;
+
+	priv = E_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	dialog = E_AUTHENTICATION_DIALOG (priv->dialog);
+	password = e_authentication_dialog_get_password (dialog);
+
+	remember_type =
+		gtk_widget_get_visible (GTK_WIDGET (dialog)) ?
+		e_authentication_dialog_get_remember_type (dialog) :
+		E_PASSWORD_REMEMBER_NONE;
+
+	/* If the user wants the password remembered, store it now.
+	 * We don't really care if this works or not, so a callback
+	 * function is unnecessary.  But still do it asynchronously
+	 * in case the password service is unresponsive. */
+
+	if (remember_type == E_PASSWORD_REMEMBER_SESSION)
+		e_dbus_source_authenticator_store_password (
+			E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+			password, FALSE, G_PRIORITY_LOW, NULL, NULL, NULL);
+
+	if (remember_type == E_PASSWORD_REMEMBER_FOREVER)
+		e_dbus_source_authenticator_store_password (
+			E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+			password, TRUE, G_PRIORITY_LOW, NULL, NULL, NULL);
+
+	e_dbus_source_authenticator_complete (
+		E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+		E_DBUS_SOURCE_AUTHENTICATOR_SUCCESSFUL);
+
+	e_dbus_authenticator_complete_accepted (authenticator, invocation);
+
+	return TRUE;
+}
+
+static gboolean
+authenticator_handle_rejected (EDBusAuthenticator *authenticator,
+                               GDBusMethodInvocation *invocation)
+{
+	EAuthenticatorPrivate *priv;
+
+	priv = E_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	/* The cached password is bad so delete it from the keyring.
+	 * We don't really care if this works or not, so a callback
+	 * function is unnecessary.  But still do it asynchronously
+	 * in case the password service is unresponsive. */
+	e_dbus_source_authenticator_delete_password (
+		E_DBUS_SOURCE_AUTHENTICATOR (authenticator),
+		G_PRIORITY_LOW, NULL, NULL, NULL);
+
+	e_dbus_authenticator_complete_rejected (authenticator, invocation);
+
+	if (gtk_widget_get_visible (priv->dialog))
+		e_authentication_dialog_try_again (
+			E_AUTHENTICATION_DIALOG (priv->dialog));
+
+	gtk_widget_show (priv->dialog);
+
+	return TRUE;
+}
+
+static void
+authenticator_password_lookup_cb (GObject *source_object,
+                                  GAsyncResult *result,
+                                  gpointer user_data)
+{
+	EAuthenticatorPrivate *priv;
+	EDBusSourceAuthenticator *authenticator;
+	EDBusSourceServer *server;
+	ESource *source;
+	gboolean allow_auth_prompt;
+	gchar *password = NULL;
+	const gchar *uid;
+	GError *error = NULL;
+
+	priv = E_AUTHENTICATOR_GET_PRIVATE (source_object);
+	authenticator = E_DBUS_SOURCE_AUTHENTICATOR (source_object);
+
+	e_dbus_source_authenticator_lookup_password_finish (
+		authenticator, result, &password, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		return;
+
+	/* If we failed to talk to the secret service, I guess we have
+	 * to prompt the user now.  Leave a breadcrumb as evidence that
+	 * something went wrong. */
+	} else if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_clear_error (&error);
+	}
+
+	/* If we found a stored password, signal the client without
+	 * interrupting the user.  Note, if the client responds with
+	 * Rejected(), we'll have to interrupt the user after all. */
+	if (password != NULL) {
+		e_dbus_source_authenticator_transmit (authenticator, password);
+		g_free (password);
+		return;
+	}
+
+	server = e_dbus_source_authenticator_get_server (authenticator);
+	uid = e_dbus_source_authenticator_get_source_uid (authenticator);
+
+	/* This will return NULL if the authenticating data source
+	 * has not yet been submitted to the D-Bus registry service. */
+	source = e_dbus_source_server_lookup_by_uid (server, uid);
+	if (source != NULL)
+		allow_auth_prompt =
+			e_server_side_source_get_allow_auth_prompt (
+			E_SERVER_SIDE_SOURCE (source));
+	else
+		allow_auth_prompt = TRUE;
+
+	/* Check if we're allowed to interrupt the user for a password.
+	 * If not, we have no choice but to dismiss the authentication
+	 * request. */
+	if (allow_auth_prompt)
+		gtk_widget_show (priv->dialog);
+	else
+		e_dbus_source_authenticator_dismiss (authenticator);
+}
+
+static void
+authenticator_dispose (GObject *object)
+{
+	EAuthenticatorPrivate *priv;
+
+	priv = E_AUTHENTICATOR_GET_PRIVATE (object);
+
+	if (priv->cancellable != NULL) {
+		g_object_unref (priv->cancellable);
+		priv->cancellable = NULL;
+	}
+
+	if (priv->dialog != NULL) {
+		gtk_widget_destroy (priv->dialog);
+		priv->dialog = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_authenticator_parent_class)->dispose (object);
+}
+
+static void
+authenticator_constructed (GObject *object)
+{
+	EAuthenticatorPrivate *priv;
+	EDBusSourceAuthenticator *authenticator;
+
+	priv = E_AUTHENTICATOR_GET_PRIVATE (object);
+	authenticator = E_DBUS_SOURCE_AUTHENTICATOR (object);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_authenticator_parent_class)->constructed (object);
+
+	priv->dialog = e_authentication_dialog_new (authenticator);
+
+	/* Let the dialog's own response() class method
+	 * run first.  It updates the "password" property
+	 * that we need in our signal handler. */
+	g_signal_connect_after (
+		priv->dialog, "response",
+		G_CALLBACK (authenticator_dialog_response_cb),
+		object);
+}
+
+static void
+authenticator_initiate (EDBusSourceAuthenticator *authenticator)
+{
+	EAuthenticatorPrivate *priv;
+
+	/* Note, our base class won't call us until both server and
+	 * client are ready, so we don't have to worry about any of
+	 * that synchronization stuff here. */
+
+	priv = E_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	/* Chain up to parent's initiate() method. */
+	E_DBUS_SOURCE_AUTHENTICATOR_CLASS (e_authenticator_parent_class)->
+		initiate (authenticator);
+
+	g_warn_if_fail (priv->cancellable == NULL);
+	priv->cancellable = g_cancellable_new ();
+
+	e_dbus_source_authenticator_lookup_password (
+		authenticator, G_PRIORITY_DEFAULT, priv->cancellable,
+		(GAsyncReadyCallback) authenticator_password_lookup_cb,
+		NULL);
+}
+
+static void
+authenticator_complete (EDBusSourceAuthenticator *authenticator,
+                        EDBusSourceAuthenticatorResult result)
+{
+	EAuthenticatorPrivate *priv;
+
+	priv = E_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	/* Chain up to parent's complete() method. */
+	E_DBUS_SOURCE_AUTHENTICATOR_CLASS (e_authenticator_parent_class)->
+		complete (authenticator, result);
+
+	if (result == E_DBUS_SOURCE_AUTHENTICATOR_CANCELLED)
+		g_cancellable_cancel (priv->cancellable);
+
+	gtk_widget_hide (priv->dialog);
+}
+
+static void
+e_authenticator_class_init (EAuthenticatorClass *class)
+{
+	GObjectClass *object_class;
+	EDBusSourceAuthenticatorClass *source_auth_class;
+
+	g_type_class_add_private (class, sizeof (EAuthenticatorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = authenticator_dispose;
+	object_class->constructed = authenticator_constructed;
+
+	source_auth_class = E_DBUS_SOURCE_AUTHENTICATOR_CLASS (class);
+	source_auth_class->initiate = authenticator_initiate;
+	source_auth_class->complete = authenticator_complete;
+}
+
+static void
+e_authenticator_init (EAuthenticator *authenticator)
+{
+	authenticator->priv = E_AUTHENTICATOR_GET_PRIVATE (authenticator);
+
+	g_signal_connect (
+		authenticator, "handle-accepted",
+		G_CALLBACK (authenticator_handle_accepted), NULL);
+
+	g_signal_connect (
+		authenticator, "handle-rejected",
+		G_CALLBACK (authenticator_handle_rejected), NULL);
+}
+
diff --git a/services/evolution-source-registry/e-authenticator.h b/services/evolution-source-registry/e-authenticator.h
new file mode 100644
index 0000000..0939a88
--- /dev/null
+++ b/services/evolution-source-registry/e-authenticator.h
@@ -0,0 +1,63 @@
+/*
+ * e-authenticator.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_AUTHENTICATOR_H
+#define E_AUTHENTICATOR_H
+
+#include <libebackend/e-dbus-source-authenticator.h>
+
+/* Standard GObject macros */
+#define E_TYPE_AUTHENTICATOR \
+	(e_authenticator_get_type ())
+#define E_AUTHENTICATOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_AUTHENTICATOR, EAuthenticator))
+#define E_AUTHENTICATOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_AUTHENTICATOR, EAuthenticatorClass))
+#define E_IS_AUTHENTICATOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_AUTHENTICATOR))
+#define E_IS_AUTHENTICATOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_AUTHENTICATOR))
+#define E_AUTHENTICATOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_AUTHENTICATOR, EAuthenticatorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAuthenticator EAuthenticator;
+typedef struct _EAuthenticatorClass EAuthenticatorClass;
+typedef struct _EAuthenticatorPrivate EAuthenticatorPrivate;
+
+struct _EAuthenticator {
+	EDBusSourceAuthenticator parent;
+	EAuthenticatorPrivate *priv;
+};
+
+struct _EAuthenticatorClass {
+	EDBusSourceAuthenticatorClass parent_class;
+};
+
+GType		e_authenticator_get_type	(void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* E_AUTHENTICATOR_H */
+
diff --git a/services/evolution-calendar-factory/evolution-calendar-factory-migrate-basedir.c b/services/evolution-source-registry/evolution-source-registry-migrate-basedir.c
similarity index 82%
rename from services/evolution-calendar-factory/evolution-calendar-factory-migrate-basedir.c
rename to services/evolution-source-registry/evolution-source-registry-migrate-basedir.c
index d44119c..0c320a0 100644
--- a/services/evolution-calendar-factory/evolution-calendar-factory-migrate-basedir.c
+++ b/services/evolution-source-registry/evolution-source-registry-migrate-basedir.c
@@ -1,5 +1,5 @@
 /*
- * evolution-calendar-factory-migrate-basedir.c
+ * evolution-addressbook-factory-migrate-basedir.c
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,7 +21,7 @@
 #include <libedataserver/e-data-server-util.h>
 
 /* Forward Declarations */
-void evolution_calendar_factory_migrate_basedir (void);
+void evolution_source_registry_migrate_basedir (void);
 
 static gboolean
 migrate_rename (const gchar *old_filename,
@@ -221,6 +221,57 @@ migrate_fix_memos_cache_bug (const gchar *old_base_dir)
 }
 
 static void
+migrate_fix_groupwise_bug (const gchar *old_base_dir)
+{
+	GDir *dir;
+	GHashTable *corrections;
+	const gchar *basename;
+	gchar *old_data_dir;
+	gchar *old_cache_dir;
+
+	/* The groupwise backend mistakenly put its addressbook
+	 * cache files in ~/.evolution/addressbook instead of
+	 * ~/.evolution/cache/addressbook.  Fix that before
+	 * we migrate the cache directory. */
+
+	old_data_dir = g_build_filename (old_base_dir, "addressbook", NULL);
+	old_cache_dir = g_build_filename (old_base_dir, "cache", "addressbook", NULL);
+
+	dir = g_dir_open (old_data_dir, 0, NULL);
+	if (dir == NULL)
+		goto exit;
+
+	/* This is to avoid renaming files while we're iterating over the
+	 * directory.  POSIX says the outcome of that is unspecified. */
+	corrections = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_free);
+
+	while ((basename = g_dir_read_name (dir)) != NULL) {
+		gchar *old_filename;
+		gchar *new_filename;
+
+		if (!g_str_has_prefix (basename, "groupwise___"))
+			continue;
+
+		old_filename = g_build_filename (old_data_dir, basename, NULL);
+		new_filename = g_build_filename (old_cache_dir, basename, NULL);
+
+		g_hash_table_insert (corrections, old_filename, new_filename);
+	}
+
+	g_dir_close (dir);
+
+	migrate_process_corrections (corrections);
+	g_hash_table_destroy (corrections);
+
+exit:
+	g_free (old_data_dir);
+	g_free (old_cache_dir);
+}
+
+static void
 migrate_to_user_cache_dir (const gchar *old_base_dir)
 {
 	const gchar *new_cache_dir;
@@ -252,6 +303,15 @@ migrate_to_user_cache_dir (const gchar *old_base_dir)
 	 * Any name collisions will be left in the source directory.
 	 */
 
+	src_directory = g_build_filename (old_cache_dir, "addressbook", NULL);
+	dst_directory = g_build_filename (new_cache_dir, "addressbook", NULL);
+
+	migrate_move_contents (src_directory, dst_directory);
+	migrate_rmdir (src_directory);
+
+	g_free (src_directory);
+	g_free (dst_directory);
+
 	src_directory = g_build_filename (old_cache_dir, "calendar", NULL);
 	dst_directory = g_build_filename (new_cache_dir, "calendar", NULL);
 
@@ -316,6 +376,15 @@ migrate_to_user_data_dir (const gchar *old_base_dir)
 	 * Any name collisions will be left in the source directory.
 	 */
 
+	src_directory = g_build_filename (old_base_dir, "addressbook", "local", NULL);
+	dst_directory = g_build_filename (new_data_dir, "addressbook", NULL);
+
+	migrate_move_contents (src_directory, dst_directory);
+	migrate_rmdir (src_directory);
+
+	g_free (src_directory);
+	g_free (dst_directory);
+
 	src_directory = g_build_filename (old_base_dir, "calendar", "local", NULL);
 	dst_directory = g_build_filename (new_data_dir, "calendar", NULL);
 
@@ -359,12 +428,12 @@ migrate_to_user_data_dir (const gchar *old_base_dir)
 }
 
 void
-evolution_calendar_factory_migrate_basedir (void)
+evolution_source_registry_migrate_basedir (void)
 {
 	const gchar *home_dir;
 	gchar *old_base_dir;
 
-	/* XXX This blocks, but it's all just local file
+	/* XXX This blocks, but it's all just local directory
 	 *     renames so it should be nearly instantaneous. */
 
 	home_dir = g_get_home_dir ();
@@ -374,8 +443,10 @@ evolution_calendar_factory_migrate_basedir (void)
 	if (!g_file_test (old_base_dir, G_FILE_TEST_IS_DIR))
 		goto exit;
 
+	/* Miscellaneous tweaks before we start. */
 	migrate_fix_exchange_bug (old_base_dir);
 	migrate_fix_memos_cache_bug (old_base_dir);
+	migrate_fix_groupwise_bug (old_base_dir);
 
 	migrate_to_user_cache_dir (old_base_dir);
 	migrate_to_user_data_dir (old_base_dir);
diff --git a/services/evolution-source-registry/evolution-source-registry-migrate-sources.c b/services/evolution-source-registry/evolution-source-registry-migrate-sources.c
new file mode 100644
index 0000000..fa75983
--- /dev/null
+++ b/services/evolution-source-registry/evolution-source-registry-migrate-sources.c
@@ -0,0 +1,3241 @@
+/*
+ * migrate-from-gconf.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <glib/gstdio.h>
+#include <camel/camel.h>
+#include <libsoup/soup.h>
+#include <gnome-keyring.h>
+
+#include <libedataserver/e-source-address-book.h>
+#include <libedataserver/e-source-alarms.h>
+#include <libedataserver/e-source-authentication.h>
+#include <libedataserver/e-source-autocomplete.h>
+#include <libedataserver/e-source-calendar.h>
+#include <libedataserver/e-source-camel.h>
+#include <libedataserver/e-source-mail-account.h>
+#include <libedataserver/e-source-mail-composition.h>
+#include <libedataserver/e-source-mail-identity.h>
+#include <libedataserver/e-source-mail-signature.h>
+#include <libedataserver/e-source-mail-submission.h>
+#include <libedataserver/e-source-mail-transport.h>
+#include <libedataserver/e-source-mdn.h>
+#include <libedataserver/e-source-offline.h>
+#include <libedataserver/e-source-openpgp.h>
+#include <libedataserver/e-source-refresh.h>
+#include <libedataserver/e-source-security.h>
+#include <libedataserver/e-source-smime.h>
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-uid.h>
+
+/* These constants are collected from various e-source-*.h files
+ * throughout evolution-data-server and known extension packages. */
+#define E_SOURCE_GROUP_NAME			"Data Source"
+#define E_SOURCE_EXTENSION_CALDAV_BACKEND	"CalDAV Backend"
+#define E_SOURCE_EXTENSION_CONTACTS_BACKEND	"Contacts Backend"
+#define E_SOURCE_EXTENSION_LDAP_BACKEND		"LDAP Backend"
+#define E_SOURCE_EXTENSION_LOCAL_BACKEND	"Local Backend"
+#define E_SOURCE_EXTENSION_VCF_BACKEND		"VCF Backend"
+#define E_SOURCE_EXTENSION_WEATHER_BACKEND	"Weather Backend"
+#define E_SOURCE_EXTENSION_WEBDAV_BACKEND	"WebDAV Backend"
+
+/* These constants are copied from e-source-password.c. */
+#define KEYRING_ITEM_ATTRIBUTE_NAME		"e-source-uid"
+#define KEYRING_ITEM_DISPLAY_FORMAT		"Evolution Data Source %s"
+
+typedef struct _ParseData ParseData;
+
+typedef void		(*PropertyFunc)		(ParseData *parse_data,
+						 const gchar *property_name,
+						 const gchar *property_value);
+
+typedef enum {
+	PARSE_TYPE_MAIL,
+	PARSE_TYPE_ADDRESSBOOK,
+	PARSE_TYPE_CALENDAR,
+	PARSE_TYPE_TASKS,
+	PARSE_TYPE_MEMOS
+} ParseType;
+
+typedef enum {
+	PARSE_STATE_INITIAL,
+
+	PARSE_STATE_IN_GCONF,			/* GConf XML */
+	PARSE_STATE_IN_ACCOUNTS_ENTRY,		/* GConf XML */
+	PARSE_STATE_IN_ACCOUNTS_VALUE,		/* GConf XML */
+	PARSE_STATE_IN_SIGNATURES_ENTRY,	/* GConf XML */
+	PARSE_STATE_IN_SIGNATURES_VALUE,	/* GConf XML */
+	PARSE_STATE_IN_SOURCES_ENTRY,		/* GConf XML */
+	PARSE_STATE_IN_SOURCES_VALUE,		/* GConf XML */
+
+	PARSE_STATE_IN_ACCOUNT,			/* EAccount XML */
+	PARSE_STATE_IN_IDENTITY,		/* EAccount XML */
+	PARSE_STATE_IN_IDENTITY_NAME,		/* EAccount XML */
+	PARSE_STATE_IN_IDENTITY_ADDR_SPEC,	/* EAccount XML */
+	PARSE_STATE_IN_IDENTITY_REPLY_TO,	/* EAccount XML */
+	PARSE_STATE_IN_IDENTITY_ORGANIZATION,	/* EAccount XML */
+	PARSE_STATE_IN_IDENTITY_SIGNATURE,	/* EAccount XML */
+	PARSE_STATE_IN_MAIL_SOURCE,		/* EAccount XML */
+	PARSE_STATE_IN_MAIL_SOURCE_URL,		/* EAccount XML */
+	PARSE_STATE_IN_MAIL_TRANSPORT,		/* EAccount XML */
+	PARSE_STATE_IN_MAIL_TRANSPORT_URL,	/* EAccount XML */
+	PARSE_STATE_IN_AUTO_CC,			/* EAccount XML */
+	PARSE_STATE_IN_AUTO_CC_RECIPIENTS,	/* EAccount XML */
+	PARSE_STATE_IN_AUTO_BCC,		/* EAccount XML */
+	PARSE_STATE_IN_AUTO_BCC_RECIPIENTS,	/* EAccount XML */
+	PARSE_STATE_IN_DRAFTS_FOLDER,		/* EAccount XML */
+	PARSE_STATE_IN_SENT_FOLDER,		/* EAccount XML */
+	PARSE_STATE_IN_RECEIPT_POLICY,		/* EAccount XML */
+	PARSE_STATE_IN_PGP,			/* EAccount XML */
+	PARSE_STATE_IN_PGP_KEY_ID,		/* EAccount XML */
+	PARSE_STATE_IN_SMIME,			/* EAccount XML */
+	PARSE_STATE_IN_SMIME_SIGN_KEY_ID,	/* EAccount XML */
+	PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID,	/* EAccount XML */
+
+	PARSE_STATE_IN_SIGNATURE,		/* ESignature XML */
+	PARSE_STATE_IN_FILENAME,		/* ESignature XML */
+
+	PARSE_STATE_IN_GROUP,			/* ESource XML */
+	PARSE_STATE_IN_SOURCE,			/* ESource XML */
+	PARSE_STATE_IN_PROPERTIES		/* ESource XML */
+} ParseState;
+
+struct _ParseData {
+	ParseType type;
+	ParseState state;
+
+	/* Set by <account>, <source> and <signature> tags. */
+	gchar *filename;
+	GKeyFile *key_file;
+
+	/* Set by <account>/<source> tags. */
+	gboolean auto_bcc;
+	gboolean auto_cc;
+
+	/* Set by <identity> tags. */
+	gchar *identity_filename;
+	GKeyFile *identity_key_file;
+
+	/* Set by <transport> tags. */
+	gchar *transport_filename;
+	GKeyFile *transport_key_file;
+
+	/* Set by <signature> tags. */
+	GFile *signature_file;
+	gboolean is_script;
+
+	/* Set by <group> tags. */
+	gchar *base_uri;
+
+	/* Set by <source> tags. */
+	gchar *mangled_uri;
+	SoupURI *soup_uri;
+	PropertyFunc property_func;
+};
+
+static GnomeKeyringPasswordSchema schema = {
+	GNOME_KEYRING_ITEM_GENERIC_SECRET,
+	{
+		{ KEYRING_ITEM_ATTRIBUTE_NAME,
+		  GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+		{ NULL, 0 }
+	}
+};
+
+/* Forward Declarations */
+void evolution_source_registry_migrate_sources (void);
+
+static ParseData *
+parse_data_new (ParseType parse_type)
+{
+	ParseData *parse_data;
+
+	parse_data = g_slice_new0 (ParseData);
+	parse_data->type = parse_type;
+	parse_data->state = PARSE_STATE_INITIAL;
+
+	return parse_data;
+}
+
+static void
+parse_data_free (ParseData *parse_data)
+{
+	/* Normally the allocated data in ParseData is freed and the
+	 * pointers are cleared before we get here.  But if an error
+	 * occurred we may leave data behind.  This cleans it up. */
+
+	g_free (parse_data->filename);
+	g_free (parse_data->identity_filename);
+	g_free (parse_data->transport_filename);
+
+	if (parse_data->key_file != NULL)
+		g_key_file_free (parse_data->key_file);
+
+	if (parse_data->transport_key_file != NULL)
+		g_key_file_free (parse_data->transport_key_file);
+
+	if (parse_data->signature_file != NULL)
+		g_object_unref (parse_data->signature_file);
+
+	g_free (parse_data->base_uri);
+	g_free (parse_data->mangled_uri);
+
+	if (parse_data->soup_uri != NULL)
+		soup_uri_free (parse_data->soup_uri);
+
+	g_slice_free (ParseData, parse_data);
+}
+
+static gboolean
+is_true (const gchar *string)
+{
+	return  (g_ascii_strcasecmp (string, "1") == 0) ||
+		(g_ascii_strcasecmp (string, "true") == 0);
+}
+
+static gboolean
+is_false (const gchar *string)
+{
+	return  (g_ascii_strcasecmp (string, "0") == 0) ||
+		(g_ascii_strcasecmp (string, "false") == 0);
+}
+
+static void
+migrate_keyring_entry (const gchar *uid,
+                       const gchar *user,
+                       const gchar *server,
+                       const gchar *protocol)
+{
+	GnomeKeyringAttributeList *attributes;
+	GList *found_list = NULL;
+	gchar *display_name;
+
+	/* This is a best-effort routine, so we don't really care about
+	 * errors.  We leave the old keyring entry in place since it may
+	 * be reused for address book or calendar migration. */
+
+	display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
+
+	attributes = gnome_keyring_attribute_list_new ();
+
+	gnome_keyring_attribute_list_append_string (
+		attributes, "application", "Evolution");
+	if (user != NULL)
+		gnome_keyring_attribute_list_append_string (
+			attributes, "user", user);
+	if (server != NULL)
+		gnome_keyring_attribute_list_append_string (
+			attributes, "server", server);
+	if (protocol != NULL)
+		gnome_keyring_attribute_list_append_string (
+			attributes, "protocol", protocol);
+
+	gnome_keyring_find_items_sync (
+		GNOME_KEYRING_ITEM_NETWORK_PASSWORD, attributes, &found_list);
+
+	/* Pick the first match we find. */
+	if (found_list != NULL) {
+		GnomeKeyringFound *found = found_list->data;
+
+		/* Sanity check. */
+		g_return_if_fail (found->secret != NULL);
+
+		gnome_keyring_store_password_sync (
+			&schema, GNOME_KEYRING_DEFAULT, display_name,
+			found->secret, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+	}
+
+	gnome_keyring_attribute_list_free (attributes);
+	gnome_keyring_found_list_free (found_list);
+
+	g_free (display_name);
+}
+
+static gboolean
+migrate_parse_commit_changes (ParseType parse_type,
+                              const gchar *filename,
+                              GKeyFile *key_file,
+                              const gchar *mangled_uri,
+                              GError **error)
+{
+	const gchar *data_dir;
+	const gchar *cache_dir;
+	const gchar *component;
+	gchar *old_directory;
+	gchar *new_directory;
+	gchar *contents;
+	gchar *uid;
+	gsize length;
+	gboolean success;
+	gboolean old_directory_exists;
+	gboolean new_directory_exists;
+
+	data_dir = e_get_user_data_dir ();
+	cache_dir = e_get_user_cache_dir ();
+
+	uid = g_path_get_basename (filename);
+
+	g_print ("  * Source: %s\n", uid);
+
+	g_print ("    Writing key file...\n");
+
+	/* Save the key file contents to disk. */
+	contents = g_key_file_to_data (key_file, &length, NULL);
+	success = g_file_set_contents (filename, contents, length, error);
+	g_free (contents);
+
+	if (!success)
+		goto exit;
+
+	/* Rename the source's cache directory from its mangled URI
+	 * to its UID.  The key file's basename is also the UID.  All
+	 * source types but "local" should have cache directories. */
+
+	/* Mail cache directories already use UIDs. */
+	switch (parse_type) {
+		case PARSE_TYPE_ADDRESSBOOK:
+			component = "addressbook";
+			break;
+		case PARSE_TYPE_CALENDAR:
+			component = "calendar";
+			break;
+		case PARSE_TYPE_TASKS:
+			component = "tasks";
+			break;
+		case PARSE_TYPE_MEMOS:
+			component = "memos";
+			break;
+		default:
+			goto exit;
+	}
+
+	g_assert (mangled_uri != NULL);
+
+	old_directory = g_build_filename (
+		cache_dir, component, mangled_uri, NULL);
+
+	new_directory = g_build_filename (
+		cache_dir, component, uid, NULL);
+
+	old_directory_exists = g_file_test (old_directory, G_FILE_TEST_EXISTS);
+	new_directory_exists = g_file_test (new_directory, G_FILE_TEST_EXISTS);
+
+	g_print (
+		"    Checking for old cache dir '%s'... %s\n",
+		old_directory,
+		old_directory_exists ?  "found" : "not found");
+
+	if (old_directory_exists) {
+		g_print (
+			"    Checking for new cache dir '%s'... %s\n",
+			new_directory,
+			new_directory_exists ? "found" : "not found");
+
+		if (new_directory_exists)
+			g_print ("    Skipping cache directory rename.\n");
+		else {
+			g_print ("    Renaming old cache directory...\n");
+			if (g_rename (old_directory, new_directory) < 0) {
+				g_set_error (
+					error, G_FILE_ERROR,
+					g_file_error_from_errno (errno),
+					"%s", g_strerror (errno));
+				success = FALSE;
+			}
+		}
+	}
+
+	g_free (old_directory);
+	g_free (new_directory);
+
+	if (!success)
+		goto exit;
+
+	/* Rename the source's local data directory from its mangled
+	 * URI to its UID.  The key file's basename is also the UID.
+	 * Only "local" sources have local data directores. */
+
+	old_directory = g_build_filename (
+		data_dir, component, mangled_uri, NULL);
+
+	new_directory = g_build_filename (
+		data_dir, component, uid, NULL);
+
+	old_directory_exists = g_file_test (old_directory, G_FILE_TEST_EXISTS);
+	new_directory_exists = g_file_test (new_directory, G_FILE_TEST_EXISTS);
+
+	g_print (
+		"    Checking for old data dir '%s'... %s\n",
+		old_directory,
+		old_directory_exists ?  "found" : "not found");
+
+	if (old_directory_exists) {
+		g_print (
+			"    Checking for new data dir '%s'... %s\n",
+			new_directory,
+			new_directory_exists ? "found" : "not found");
+
+		if (new_directory_exists)
+			g_print ("    Skipping data directory rename.\n");
+		else {
+			g_print ("    Renaming old data directory...\n");
+			if (g_rename (old_directory, new_directory) < 0) {
+				g_set_error (
+					error, G_FILE_ERROR,
+					g_file_error_from_errno (errno),
+					"%s", g_strerror (errno));
+				success = FALSE;
+			}
+		}
+	}
+
+	g_free (old_directory);
+	g_free (new_directory);
+
+exit:
+	g_free (uid);
+
+	return success;
+}
+
+static void
+migrate_parse_account (ParseData *parse_data,
+                       const gchar *element_name,
+                       const gchar **attribute_names,
+                       const gchar **attribute_values,
+                       GError **error)
+{
+	const gchar *uid;
+	const gchar *name;
+	const gchar *config_dir;
+	gchar *directory;
+	gchar *identity_uid;
+	gchar *transport_uid;
+	gboolean enabled;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_STRING,
+		"uid", &uid,
+		G_MARKUP_COLLECT_STRING,
+		"name", &name,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"enabled", &enabled,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	config_dir = e_get_user_config_dir ();
+	directory = g_build_filename (config_dir, "sources", NULL);
+	g_mkdir_with_parents (directory, 0700);
+
+	parse_data->filename = g_build_filename (directory, uid, NULL);
+
+	/* If the file already exists, skip this source.  It may be that we
+	 * already migrated it, in which case we don't want to overwrite it. */
+	if (g_file_test (parse_data->filename, G_FILE_TEST_EXISTS))
+		return;
+
+	parse_data->key_file = g_key_file_new ();
+
+	identity_uid = e_uid_new ();
+	parse_data->identity_filename =
+		g_build_filename (directory, identity_uid, NULL);
+	parse_data->identity_key_file = g_key_file_new ();
+
+	transport_uid = e_uid_new ();
+	parse_data->transport_filename =
+		g_build_filename (directory, transport_uid, NULL);
+	parse_data->transport_key_file = g_key_file_new ();
+
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_GROUP_NAME,
+		"DisplayName", name);
+
+	g_key_file_set_boolean (
+		parse_data->key_file,
+		E_SOURCE_GROUP_NAME,
+		"Enabled", enabled);
+
+	/* Mail account source doubles as an identity source. */
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_MAIL_ACCOUNT,
+		"IdentityUid", identity_uid);
+
+	/* Mail account source references the transport source. */
+	g_key_file_set_string (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_MAIL_SUBMISSION,
+		"TransportUid", transport_uid);
+
+	/* Identity source gets the same display name. */
+	g_key_file_set_string (
+		parse_data->identity_key_file,
+		E_SOURCE_GROUP_NAME,
+		"DisplayName", name);
+
+	/* Identity source is a child of the mail account. */
+	g_key_file_set_string (
+		parse_data->identity_key_file,
+		E_SOURCE_GROUP_NAME,
+		"Parent", uid);
+
+	/* Transport source gets the same display name. */
+	g_key_file_set_string (
+		parse_data->transport_key_file,
+		E_SOURCE_GROUP_NAME,
+		"DisplayName", name);
+
+	/* Transport source is a child of the mail account. */
+	g_key_file_set_string (
+		parse_data->transport_key_file,
+		E_SOURCE_GROUP_NAME,
+		"Parent", uid);
+
+	g_free (directory);
+	g_free (identity_uid);
+	g_free (transport_uid);
+}
+
+static void
+migrate_parse_pgp (ParseData *parse_data,
+                   const gchar *element_name,
+                   const gchar **attribute_names,
+                   const gchar **attribute_values,
+                   GError **error)
+{
+	const gchar *hash_algo;
+	gboolean always_sign;
+	gboolean always_trust;
+	gboolean encrypt_to_self;
+	gboolean no_imip_sign;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"always-sign", &always_sign,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"always-trust", &always_trust,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"encrypt-to-self", &encrypt_to_self,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"no-imip-sign", &no_imip_sign,
+		G_MARKUP_COLLECT_STRING |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"hash-algo", &hash_algo,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	g_key_file_set_boolean (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_OPENPGP,
+		"AlwaysSign", always_sign);
+
+	g_key_file_set_boolean (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_OPENPGP,
+		"AlwaysTrust", always_trust);
+
+	g_key_file_set_boolean (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_OPENPGP,
+		"EncryptToSelf", encrypt_to_self);
+
+	if (hash_algo != NULL && *hash_algo != '\0')
+		g_key_file_set_string (
+			parse_data->identity_key_file,
+			E_SOURCE_EXTENSION_OPENPGP,
+			"SigningAlgorithm", hash_algo);
+
+	/* XXX Don't know why this is under the <pgp>
+	 *     element, it applies to S/MIME as well.
+	 *     Also note we're inverting the setting. */
+	g_key_file_set_boolean (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_MAIL_COMPOSITION,
+		"SignImip", !no_imip_sign);
+}
+
+static void
+migrate_parse_recipients (ParseData *parse_data,
+                          const gchar *key,
+                          const gchar *recipients)
+{
+	CamelAddress *address;
+	CamelInternetAddress *inet_address;
+	gchar **string_list;
+	gint ii, length;
+	gsize index = 0;
+
+	if (recipients == NULL || *recipients == '\0')
+		return;
+
+	inet_address = camel_internet_address_new ();
+	address = CAMEL_ADDRESS (inet_address);
+
+	if (camel_address_decode (address, recipients) == -1)
+		goto exit;
+
+	length = camel_address_length (address);
+	string_list = g_new0 (gchar *, length + 1);
+
+	for (ii = 0; ii < length; ii++) {
+		const gchar *name, *addr;
+
+		if (!camel_internet_address_get (
+			inet_address, ii, &name, &addr))
+			continue;
+
+		string_list[index++] =
+			camel_internet_address_format_address (name, addr);
+	}
+
+	g_key_file_set_string_list (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_MAIL_COMPOSITION, key,
+		(const gchar *const *) string_list, index);
+
+	g_strfreev (string_list);
+
+exit:
+	g_object_unref (inet_address);
+}
+
+static void
+migrate_parse_smime (ParseData *parse_data,
+                     const gchar *element_name,
+                     const gchar **attribute_names,
+                     const gchar **attribute_values,
+                     GError **error)
+{
+	const gchar *hash_algo;
+	gboolean encrypt_default;
+	gboolean encrypt_to_self;
+	gboolean sign_default;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"encrypt-default", &encrypt_default,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"encrypt-to-self", &encrypt_to_self,
+		G_MARKUP_COLLECT_STRING |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"hash-algo", &hash_algo,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"sign-default", &sign_default,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	g_key_file_set_boolean (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_SMIME,
+		"EncryptByDefault", encrypt_default);
+
+	g_key_file_set_boolean (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_SMIME,
+		"EncryptToSelf", encrypt_to_self);
+
+	if (hash_algo != NULL && *hash_algo != '\0')
+		g_key_file_set_string (
+			parse_data->identity_key_file,
+			E_SOURCE_EXTENSION_SMIME,
+			"SigningAlgorithm", hash_algo);
+
+	g_key_file_set_boolean (
+		parse_data->identity_key_file,
+		E_SOURCE_EXTENSION_SMIME,
+		"SignByDefault", sign_default);
+}
+
+static void
+migrate_parse_mail_source (ParseData *parse_data,
+                           const gchar *element_name,
+                           const gchar **attribute_names,
+                           const gchar **attribute_values,
+                           GError **error)
+{
+	const gchar *auto_check_timeout;
+	glong interval_minutes = 0;
+	gboolean auto_check;
+	gboolean success;
+
+	/* Disregard "keep-on-server" and "save-passwd" attributes. */
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"auto-check", &auto_check,
+		G_MARKUP_COLLECT_STRING,
+		"auto-check-timeout", &auto_check_timeout,
+		G_MARKUP_COLLECT_BOOLEAN |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"keep-on-server", NULL,
+		G_MARKUP_COLLECT_BOOLEAN |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"save-passwd", NULL,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	if (auto_check_timeout != NULL)
+		interval_minutes = strtol (auto_check_timeout, NULL, 10);
+
+	g_key_file_set_boolean (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_REFRESH,
+		"Enabled", auto_check);
+
+	if (interval_minutes > 0)
+		g_key_file_set_integer (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_REFRESH,
+			"IntervalMinutes", interval_minutes);
+}
+
+static void
+migrate_parse_url_rename_params (CamelURL *url)
+{
+	/* This list includes known URL parameters from built-in providers
+	 * in Camel, as well as from evolution-exchange, evolution-groupwise,
+	 * and evolution-mapi.  Add more as needed. */
+	static struct {
+		const gchar *url_parameter;
+		const gchar *property_name;
+	} camel_url_conversion[] = {
+		{ "ad_auth",			"gc-auth-method" },
+		{ "ad_browse",			"gc-allow-browse" },
+		{ "ad_expand_groups",		"gc-expand-groups" },
+		{ "ad_limit",			"gc-results-limit" },
+		{ "ad_server",			"gc-server-name" },
+		{ "all_headers",		"fetch-headers" },
+		{ "basic_headers",		"fetch-headers" },
+		{ "cachedconn"			"concurrent-connections" },
+		{ "check_all",			"check-all" },
+		{ "check_lsub",			"check-subscribed" },
+		{ "command",			"shell-command" },
+		{ "delete_after",		"delete-after-days" },
+		{ "delete_expunged",		"delete-expunged" },
+		{ "disable_extensions",		"disable-extensions" },
+		{ "dotfolders",			"use-dot-folders" },
+		{ "filter",			"filter-inbox" },
+		{ "filter_junk",		"filter-junk" },
+		{ "filter_junk_inbox",		"filter-junk-inbox" },
+		{ "folder_hierarchy_relative",	"folder-hierarchy-relative" },
+		{ "imap_custom_headers",	"fetch-headers-extra" },
+		{ "keep_on_server",		"keep-on-server" },
+		{ "offline_sync",		"stay-synchronized" },
+		{ "override_namespace",		"use-namespace" },
+		{ "owa_path",			"owa-path" },
+		{ "owa_url",			"owa-url" },
+		{ "password_exp_warn_period",	"password-exp-warn-period" },
+		{ "real_junk_path",		"real-junk-path" },
+		{ "real_trash_path",		"real-trash-path" },
+		{ "show_short_notation",	"short-folder-names" },
+		{ "soap_port",			"soap-port" },
+		{ "ssl",			"security-method" },
+		{ "sync_offline",		"stay-synchronized" },
+		{ "use_command",		"use-shell-command" },
+		{ "use_idle",			"use-idle" },
+		{ "use_lsub",			"use-subscriptions" },
+		{ "use_qresync",		"use-qresync" },
+		{ "use_ssl",			"security-method" },
+		{ "xstatus",			"use-xstatus-headers" }
+	};
+
+	const gchar *param;
+	const gchar *use_param;
+	gint ii;
+
+	for (ii = 0; ii < G_N_ELEMENTS (camel_url_conversion); ii++) {
+		const gchar *key;
+		gpointer value;
+
+		key = camel_url_conversion[ii].url_parameter;
+		value = g_datalist_get_data (&url->params, key);
+
+		if (value == NULL)
+			continue;
+
+		g_datalist_remove_no_notify (&url->params, key);
+
+		key = camel_url_conversion[ii].property_name;
+
+		/* Deal with a few special enum cases where
+		 * the parameter value also needs renamed. */
+
+		if (strcmp (key, "all_headers") == 0) {
+			GEnumClass *enum_class;
+			GEnumValue *enum_value;
+
+			enum_class = g_type_class_ref (
+				CAMEL_TYPE_FETCH_HEADERS_TYPE);
+			enum_value = g_enum_get_value (
+				enum_class, CAMEL_FETCH_HEADERS_ALL);
+			if (enum_value != NULL) {
+				g_free (value);
+				value = g_strdup (enum_value->value_nick);
+			} else
+				g_warn_if_reached ();
+			g_type_class_unref (enum_class);
+		}
+
+		if (strcmp (key, "basic_headers") == 0) {
+			GEnumClass *enum_class;
+			GEnumValue *enum_value;
+
+			enum_class = g_type_class_ref (
+				CAMEL_TYPE_FETCH_HEADERS_TYPE);
+			enum_value = g_enum_get_value (
+				enum_class, CAMEL_FETCH_HEADERS_BASIC);
+			if (enum_value != NULL) {
+				g_free (value);
+				value = g_strdup (enum_value->value_nick);
+			} else
+				g_warn_if_reached ();
+			g_type_class_unref (enum_class);
+		}
+
+		if (strcmp (key, "imap_custom_headers") == 0)
+			g_strdelimit (value, " ", ',');
+
+		if (strcmp (key, "security-method") == 0) {
+			CamelNetworkSecurityMethod method;
+			GEnumClass *enum_class;
+			GEnumValue *enum_value;
+
+			if (strcmp (value, "always") == 0)
+				method = CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT;
+			else if (strcmp (value, "1") == 0)
+				method = CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT;
+			else if (strcmp (value, "when-possible") == 0)
+				method = CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT;
+			else
+				method = CAMEL_NETWORK_SECURITY_METHOD_NONE;
+
+			enum_class = g_type_class_ref (
+				CAMEL_TYPE_NETWORK_SECURITY_METHOD);
+			enum_value = g_enum_get_value (enum_class, method);
+			if (enum_value != NULL) {
+				g_free (value);
+				value = g_strdup (enum_value->value_nick);
+			} else
+				g_warn_if_reached ();
+			g_type_class_unref (enum_class);
+		}
+
+		g_datalist_set_data_full (&url->params, key, value, g_free);
+	}
+
+	/* A few more adjustments...
+	 *
+	 * These are all CAMEL_PROVIDER_CONF_CHECKSPIN settings.  The spin
+	 * button value is bound to "param" and the checkbox state is bound
+	 * to "use-param".  The "use-param" settings are new.  If "param"
+	 * exists but no "use-param", then set "use-param" to "true". */
+
+	param = g_datalist_get_data (&url->params, "gc-results-limit");
+	use_param = g_datalist_get_data (&url->params, "use-gc-results-limit");
+	if (param != NULL && *param != '\0' && use_param == NULL) {
+		g_datalist_set_data_full (
+			&url->params, "use-gc-results-limit",
+			g_strdup ("true"), (GDestroyNotify) g_free);
+	}
+
+	param = g_datalist_get_data (&url->params, "kerberos");
+	if (g_strcmp0 (param, "required") == 0) {
+		g_datalist_set_data_full (
+			&url->params, "kerberos",
+			g_strdup ("true"), (GDestroyNotify) g_free);
+	}
+
+	param = g_datalist_get_data (
+		&url->params, "password-exp-warn-period");
+	use_param = g_datalist_get_data (
+		&url->params, "use-password-exp-warn-period");
+	if (param != NULL && *param != '\0' && use_param == NULL) {
+		g_datalist_set_data_full (
+			&url->params, "use-password-exp-warn-period",
+			g_strdup ("true"), (GDestroyNotify) g_free);
+	}
+
+	param = g_datalist_get_data (&url->params, "real-junk-path");
+	use_param = g_datalist_get_data (&url->params, "use-real-junk-path");
+	if (param != NULL && *param != '\0' && use_param == NULL) {
+		g_datalist_set_data_full (
+			&url->params, "use-real-junk-path",
+			g_strdup ("true"), (GDestroyNotify) g_free);
+	}
+
+	param = g_datalist_get_data (&url->params, "real-trash-path");
+	use_param = g_datalist_get_data (&url->params, "use-real-trash-path");
+	if (param != NULL && *param != '\0' && use_param == NULL) {
+		g_datalist_set_data_full (
+			&url->params, "use-real-trash-path",
+			g_strdup ("true"), (GDestroyNotify) g_free);
+	}
+}
+
+static void
+migrate_parse_url_foreach (GQuark key_id,
+                           const gchar *value,
+                           gpointer user_data)
+{
+	const gchar *param_name;
+	const gchar *key;
+
+	struct {
+		GKeyFile *key_file;
+		const gchar *group_name;
+	} *foreach_data = user_data;
+
+	g_return_if_fail (value != NULL);
+
+	param_name = g_quark_to_string (key_id);
+	key = e_source_parameter_to_key (param_name);
+
+	/* If the value is empty, then the mere
+	 * presence of the parameter implies TRUE. */
+	if (*value == '\0')
+		value = "true";
+
+	g_key_file_set_string (
+		foreach_data->key_file,
+		foreach_data->group_name,
+		key, value);
+}
+
+static void
+migrate_parse_url (GKeyFile *key_file,
+                   const gchar *filename,
+                   const gchar *group_name,
+                   const gchar *url_string,
+                   GError **error)
+{
+	CamelURL *url;
+	const gchar *value;
+	gchar *uid;
+
+	struct {
+		GKeyFile *key_file;
+		const gchar *group_name;
+	} foreach_data;
+
+	url = camel_url_new (url_string, error);
+	if (url == NULL)
+		return;
+
+	/* Rename URL params as necessary to match
+	 * their ESourceExtension property names. */
+	migrate_parse_url_rename_params (url);
+
+	/* Set authentication details. */
+
+	if (url->protocol != NULL)
+		g_key_file_set_string (
+			key_file, group_name,
+			"BackendName", url->protocol);
+
+	if (url->host != NULL)
+		g_key_file_set_string (
+			key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Host", url->host);
+
+	if (url->authmech != NULL)
+		g_key_file_set_string (
+			key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Method", url->authmech);
+
+	if (url->port > 0)
+		g_key_file_set_integer (
+			key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Port", url->port);
+
+	if (url->user != NULL)
+		g_key_file_set_string (
+			key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", url->user);
+
+	/* Pick out particular URL parameters we know about. */
+
+	/* If set, this should be "true" or "false",
+	 * but we'll just write it like it's a string. */
+	value = g_datalist_get_data (&url->params, "stay-synchronized");
+	if (value != NULL)
+		g_key_file_set_string (
+			key_file,
+			E_SOURCE_EXTENSION_OFFLINE,
+			"StaySynchronized", value);
+	g_datalist_set_data (&url->params, "stay-synchronized", NULL);
+
+	value = g_datalist_get_data (&url->params, "security-method");
+	if (value != NULL)
+		g_key_file_set_string (
+			key_file,
+			E_SOURCE_EXTENSION_SECURITY,
+			"Method", value);
+	g_datalist_set_data (&url->params, "security-method", NULL);
+
+	/* The rest of the URL parameters go in the backend group. */
+
+	group_name = e_source_camel_get_extension_name (url->protocol);
+
+	foreach_data.key_file = key_file;
+	foreach_data.group_name = group_name;
+
+	g_datalist_foreach (
+		&url->params, (GDataForeachFunc)
+		migrate_parse_url_foreach, &foreach_data);
+
+	uid = g_path_get_basename (filename);
+	migrate_keyring_entry (uid, url->user, url->host, url->protocol);
+	g_free (uid);
+
+	camel_url_free (url);
+}
+
+static void
+migrate_parse_account_xml_start_element (GMarkupParseContext *context,
+                                         const gchar *element_name,
+                                         const gchar **attribute_names,
+                                         const gchar **attribute_values,
+                                         gpointer user_data,
+                                         GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "account") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNTS_VALUE)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_ACCOUNT;
+
+		migrate_parse_account (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "addr-spec") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_IDENTITY_ADDR_SPEC;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "auto-bcc") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_AUTO_BCC;
+
+		g_markup_collect_attributes (
+			element_name,
+			attribute_names,
+			attribute_values,
+			error,
+			G_MARKUP_COLLECT_BOOLEAN,
+			"always", &parse_data->auto_bcc,
+			G_MARKUP_COLLECT_INVALID);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "auto-cc") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_AUTO_CC;
+
+		g_markup_collect_attributes (
+			element_name,
+			attribute_names,
+			attribute_values,
+			error,
+			G_MARKUP_COLLECT_BOOLEAN,
+			"always", &parse_data->auto_cc,
+			G_MARKUP_COLLECT_INVALID);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "drafts-folder") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_DRAFTS_FOLDER;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "encrypt-key-id") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_SMIME)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "identity") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_IDENTITY;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "key-id") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_PGP)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_PGP_KEY_ID;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "name") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_IDENTITY_NAME;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "reply-to") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_IDENTITY_REPLY_TO;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "organization") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_IDENTITY_ORGANIZATION;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "pgp") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_PGP;
+
+		migrate_parse_pgp (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "receipt-policy") == 0) {
+		const gchar *policy;
+		gboolean success;
+
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_RECEIPT_POLICY;
+
+		success = g_markup_collect_attributes (
+			element_name,
+			attribute_names,
+			attribute_values,
+			error,
+			G_MARKUP_COLLECT_STRING,
+			"policy", &policy,
+			G_MARKUP_COLLECT_INVALID);
+
+		/* The new enum strings match the old ones. */
+		if (success && policy != NULL)
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_MDN,
+				"ResponsePolicy", policy);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "recipients") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_AUTO_BCC) {
+			parse_data->state = PARSE_STATE_IN_AUTO_BCC_RECIPIENTS;
+			return;
+		}
+
+		if (parse_data->state == PARSE_STATE_IN_AUTO_CC) {
+			parse_data->state = PARSE_STATE_IN_AUTO_CC_RECIPIENTS;
+			return;
+		}
+
+		goto invalid_content;
+	}
+
+	if (g_strcmp0 (element_name, "sent-folder") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_SENT_FOLDER;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "signature") == 0) {
+		const gchar *uid;
+		gboolean success;
+
+		if (parse_data->state != PARSE_STATE_IN_IDENTITY)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_IDENTITY_SIGNATURE;
+
+		success = g_markup_collect_attributes (
+			element_name,
+			attribute_names,
+			attribute_values,
+			error,
+			G_MARKUP_COLLECT_STRING,
+			"uid", &uid,
+			G_MARKUP_COLLECT_INVALID);
+
+		if (success && uid != NULL)
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_MAIL_IDENTITY,
+				"SignatureUid", uid);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "sign-key-id") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_SMIME)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_SMIME_SIGN_KEY_ID;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "smime") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_SMIME;
+
+		migrate_parse_smime (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "source") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_MAIL_SOURCE;
+
+		migrate_parse_mail_source (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "transport") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "url") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE) {
+			parse_data->state = PARSE_STATE_IN_MAIL_SOURCE_URL;
+			return;
+		}
+
+		if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT) {
+			parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT_URL;
+			return;
+		}
+
+		goto invalid_content;
+	}
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+		"Unknown element <%s>", element_name);
+
+	return;
+
+invalid_content:
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+		"Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_account_xml_end_element (GMarkupParseContext *context,
+                                       const gchar *element_name,
+                                       gpointer user_data,
+                                       GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "account") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_ACCOUNT) {
+			parse_data->state = PARSE_STATE_IN_ACCOUNTS_VALUE;
+
+			/* Clean up <account> tag data. */
+
+			/* The key file will be NULL if we decided to skip it.
+			 * e.g. A file with the same UID may already exist. */
+			if (parse_data->key_file != NULL) {
+				GError *local_error = NULL;
+
+				migrate_parse_commit_changes (
+					parse_data->type,
+					parse_data->filename,
+					parse_data->key_file,
+					NULL, &local_error);
+
+				if (local_error != NULL) {
+					g_printerr (
+						"  FAILED: %s\n",
+						local_error->message);
+					g_error_free (local_error);
+				}
+
+				g_key_file_free (parse_data->key_file);
+				parse_data->key_file = NULL;
+			}
+
+			/* Same deal for the identity key file. */
+			if (parse_data->identity_key_file != NULL) {
+				GError *local_error = NULL;
+
+				migrate_parse_commit_changes (
+					parse_data->type,
+					parse_data->identity_filename,
+					parse_data->identity_key_file,
+					NULL, &local_error);
+
+				if (local_error != NULL) {
+					g_printerr (
+						"  FAILED: %s\n",
+						local_error->message);
+					g_error_free (local_error);
+				}
+
+				g_key_file_free (parse_data->identity_key_file);
+				parse_data->identity_key_file = NULL;
+			}
+
+			/* Same deal for the transport key file. */
+			if (parse_data->transport_key_file != NULL) {
+				GError *local_error = NULL;
+
+				migrate_parse_commit_changes (
+					parse_data->type,
+					parse_data->transport_filename,
+					parse_data->transport_key_file,
+					NULL, &local_error);
+
+				if (local_error != NULL) {
+					g_printerr (
+						"  FAILED: %s\n",
+						local_error->message);
+					g_error_free (local_error);
+				}
+
+				g_key_file_free (parse_data->transport_key_file);
+				parse_data->transport_key_file = NULL;
+			}
+
+			g_free (parse_data->filename);
+			parse_data->filename = NULL;
+
+			g_free (parse_data->transport_filename);
+			parse_data->transport_filename = NULL;
+		}
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "addr-spec") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_IDENTITY_ADDR_SPEC)
+			parse_data->state = PARSE_STATE_IN_IDENTITY;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "auto-bcc") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_AUTO_BCC)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "auto-cc") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_AUTO_CC)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "drafts-folder") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_DRAFTS_FOLDER)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "encrypt-key-id") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID)
+			parse_data->state = PARSE_STATE_IN_SMIME;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "identity") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_IDENTITY)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "key-id") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_PGP_KEY_ID)
+			parse_data->state = PARSE_STATE_IN_PGP;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "name") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_IDENTITY_NAME)
+			parse_data->state = PARSE_STATE_IN_IDENTITY;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "organization") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_IDENTITY_ORGANIZATION)
+			parse_data->state = PARSE_STATE_IN_IDENTITY;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "pgp") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_PGP)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "receipt-policy") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_RECEIPT_POLICY)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "recipients") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_AUTO_BCC_RECIPIENTS)
+			parse_data->state = PARSE_STATE_IN_AUTO_BCC;
+		if (parse_data->state == PARSE_STATE_IN_AUTO_CC_RECIPIENTS)
+			parse_data->state = PARSE_STATE_IN_AUTO_CC;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "reply-to") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_IDENTITY_REPLY_TO)
+			parse_data->state = PARSE_STATE_IN_IDENTITY;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "sent-folder") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SENT_FOLDER)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "signature") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_IDENTITY_SIGNATURE)
+			parse_data->state = PARSE_STATE_IN_IDENTITY;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "sign-key-id") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SMIME_SIGN_KEY_ID)
+			parse_data->state = PARSE_STATE_IN_SMIME;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "smime") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SMIME)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "source") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "transport") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT)
+			parse_data->state = PARSE_STATE_IN_ACCOUNT;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "url") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE_URL)
+			parse_data->state = PARSE_STATE_IN_MAIL_SOURCE;
+		if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT_URL)
+			parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT;
+		return;
+	}
+}
+
+static void
+migrate_parse_account_xml_text (GMarkupParseContext *context,
+                                const gchar *text,
+                                gsize text_len,
+                                gpointer user_data,
+                                GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	switch (parse_data->state) {
+		case PARSE_STATE_IN_AUTO_BCC_RECIPIENTS:
+			/* Disregard the recipient list if
+			 * we're not going to auto-BCC them. */
+			if (parse_data->auto_bcc)
+				migrate_parse_recipients (
+					parse_data, "Bcc", text);
+			break;
+
+		case PARSE_STATE_IN_AUTO_CC_RECIPIENTS:
+			/* Disregard the recipient list if
+			 * we're not going to auto-CC them. */
+			if (parse_data->auto_cc)
+				migrate_parse_recipients (
+					parse_data, "Cc", text);
+			break;
+
+		case PARSE_STATE_IN_DRAFTS_FOLDER:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_MAIL_COMPOSITION,
+				"DraftsFolder", text);
+			break;
+
+		case PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_SMIME,
+				"EncryptionCertificate", text);
+			break;
+
+		case PARSE_STATE_IN_IDENTITY_ADDR_SPEC:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_MAIL_IDENTITY,
+				"Address", text);
+			break;
+
+		case PARSE_STATE_IN_IDENTITY_NAME:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_MAIL_IDENTITY,
+				"Name", text);
+			break;
+
+		case PARSE_STATE_IN_IDENTITY_ORGANIZATION:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_MAIL_IDENTITY,
+				"Organization", text);
+			break;
+
+		case PARSE_STATE_IN_IDENTITY_REPLY_TO:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_MAIL_IDENTITY,
+				"ReplyTo", text);
+			break;
+
+		case PARSE_STATE_IN_MAIL_SOURCE_URL:
+			migrate_parse_url (
+				parse_data->key_file,
+				parse_data->filename,
+				E_SOURCE_EXTENSION_MAIL_ACCOUNT,
+				text, error);
+			break;
+
+		case PARSE_STATE_IN_MAIL_TRANSPORT_URL:
+			migrate_parse_url (
+				parse_data->transport_key_file,
+				parse_data->transport_filename,
+				E_SOURCE_EXTENSION_MAIL_TRANSPORT,
+				text, error);
+			break;
+
+		case PARSE_STATE_IN_PGP_KEY_ID:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_OPENPGP,
+				"KeyId", text);
+			break;
+
+		case PARSE_STATE_IN_SENT_FOLDER:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_MAIL_SUBMISSION,
+				"SentFolder", text);
+			break;
+
+		case PARSE_STATE_IN_SMIME_SIGN_KEY_ID:
+			g_key_file_set_string (
+				parse_data->identity_key_file,
+				E_SOURCE_EXTENSION_SMIME,
+				"SigningCertificate", text);
+			break;
+
+		default:
+			break;
+	}
+}
+
+static GMarkupParser account_xml_parser = {
+	migrate_parse_account_xml_start_element,
+	migrate_parse_account_xml_end_element,
+	migrate_parse_account_xml_text,
+	NULL,  /* passthrough */
+	NULL   /* error */
+};
+
+static void
+migrate_parse_signature (ParseData *parse_data,
+                         const gchar *element_name,
+                         const gchar **attribute_names,
+                         const gchar **attribute_values,
+                         GError **error)
+{
+	const gchar *uid;
+	const gchar *name;
+	const gchar *format;
+	const gchar *config_dir;
+	gchar *directory;
+	gchar *absolute_path;
+	gboolean autogenerated;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_STRING,
+		"uid", &uid,
+		G_MARKUP_COLLECT_STRING,
+		"name", &name,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"auto", &autogenerated,
+		G_MARKUP_COLLECT_STRING |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"format", &format,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	/* Skip the "autogenerated" signature. */
+	if (autogenerated)
+		return;
+
+	config_dir = e_get_user_config_dir ();
+
+	directory = g_build_filename (config_dir, "sources", NULL);
+	parse_data->filename = g_build_filename (directory, uid, NULL);
+	g_mkdir_with_parents (directory, 0700);
+	g_free (directory);
+
+	directory = g_build_filename (config_dir, "signatures", NULL);
+	absolute_path = g_build_filename (directory, uid, NULL);
+	parse_data->signature_file = g_file_new_for_path (absolute_path);
+	g_mkdir_with_parents (directory, 0700);
+	g_free (absolute_path);
+	g_free (directory);
+
+	/* If the file already exists, skip this source.  It may be that we
+	 * already migrated it, in which case we don't want to overwrite it. */
+	if (g_file_test (parse_data->filename, G_FILE_TEST_EXISTS))
+		return;
+
+	parse_data->key_file = g_key_file_new ();
+
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_GROUP_NAME,
+		"DisplayName", name);
+
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_MAIL_SIGNATURE,
+		"MimeType", format);
+}
+
+static void
+migrate_parse_signature_xml_start_element (GMarkupParseContext *context,
+                                           const gchar *element_name,
+                                           const gchar **attribute_names,
+                                           const gchar **attribute_values,
+                                           gpointer user_data,
+                                           GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "filename") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_SIGNATURE)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_FILENAME;
+
+		g_markup_collect_attributes (
+			element_name,
+			attribute_names,
+			attribute_values,
+			error,
+			G_MARKUP_COLLECT_BOOLEAN |
+			G_MARKUP_COLLECT_OPTIONAL,
+			"script", &parse_data->is_script,
+			G_MARKUP_COLLECT_INVALID);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "signature") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_SIGNATURES_VALUE)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_SIGNATURE;
+
+		migrate_parse_signature (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+		"Unknown element <%s>", element_name);
+
+	return;
+
+invalid_content:
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+		"Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_signature_xml_end_element (GMarkupParseContext *context,
+                                         const gchar *element_name,
+                                         gpointer user_data,
+                                         GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "filename") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_FILENAME) {
+			parse_data->state = PARSE_STATE_IN_SIGNATURE;
+
+			return;
+		}
+	}
+
+	if (g_strcmp0 (element_name, "signature") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SIGNATURE) {
+			parse_data->state = PARSE_STATE_IN_SIGNATURES_VALUE;
+
+			/* Clean up <signature> tag data. */
+
+			/* The key file will be NULL if we decided to skip it.
+			 * e.g. A file with the same UID may already exist. */
+			if (parse_data->key_file != NULL) {
+				GError *local_error = NULL;
+
+				migrate_parse_commit_changes (
+					parse_data->type,
+					parse_data->filename,
+					parse_data->key_file,
+					NULL, &local_error);
+
+				if (local_error != NULL) {
+					g_printerr (
+						"  FAILED: %s\n",
+						local_error->message);
+					g_error_free (local_error);
+				}
+
+				g_key_file_free (parse_data->key_file);
+				parse_data->key_file = NULL;
+			}
+
+			g_free (parse_data->filename);
+			parse_data->filename = NULL;
+
+			return;
+		}
+	}
+}
+
+static void
+migrate_parse_signature_xml_text (GMarkupParseContext *context,
+                                  const gchar *text,
+                                  gsize text_len,
+                                  gpointer user_data,
+                                  GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (parse_data->state == PARSE_STATE_IN_FILENAME) {
+		GFile *old_signature_file;
+		GFile *new_signature_file;
+		const gchar *data_dir;
+		gchar *absolute_path;
+
+		/* Note we're moving the signature files
+		 * from $XDG_DATA_HOME to $XDG_CONFIG_HOME. */
+		data_dir = e_get_user_data_dir ();
+
+		/* Text should be either an absolute file name
+		 * or a base file name with no path components. */
+		if (g_path_is_absolute (text))
+			absolute_path = g_strdup (text);
+		else
+			absolute_path = g_build_filename (
+				data_dir, "signatures", text, NULL);
+
+		old_signature_file = g_file_new_for_path (absolute_path);
+		new_signature_file = parse_data->signature_file;
+		parse_data->signature_file = NULL;
+
+		/* If the signature is a script, we symlink to it.
+		 * Otherwise we move and rename the regular file. */
+		if (parse_data->is_script)
+			g_file_make_symbolic_link (
+				new_signature_file,
+				absolute_path, NULL, error);
+		else
+			g_file_move (
+				old_signature_file,
+				new_signature_file,
+				G_FILE_COPY_NONE,
+				NULL, NULL, NULL, error);
+
+		g_object_unref (old_signature_file);
+		g_object_unref (new_signature_file);
+		g_free (absolute_path);
+	}
+}
+
+static GMarkupParser signature_xml_parser = {
+	migrate_parse_signature_xml_start_element,
+	migrate_parse_signature_xml_end_element,
+	migrate_parse_signature_xml_text,
+	NULL,  /* passthrough */
+	NULL   /* error */
+};
+
+static void
+migrate_parse_local_calendar_property (ParseData *parse_data,
+                                       const gchar *property_name,
+                                       const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "custom-file") == 0) {
+		gchar *uri;
+
+		/* Property value is a local filename.  Convert it to a
+		 * "file://" URI.
+		 *
+		 * Note: The key is named "CustomFile" instead of, say,
+		 * "CustomURI" because the corresponding ESourceExtension
+		 * property is a GFile.  The fact that ESource saves GFile
+		 * properties as URI strings is an implementation detail. */
+		uri = g_filename_to_uri (property_value, NULL, NULL);
+		if (uri != NULL) {
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_LOCAL_BACKEND,
+				"CustomFile", uri);
+			g_free (uri);
+		}
+	}
+}
+
+static void
+migrate_parse_local_source (ParseData *parse_data)
+{
+	if (parse_data->type != PARSE_TYPE_ADDRESSBOOK)
+		parse_data->property_func =
+			migrate_parse_local_calendar_property;
+
+	/* Local ADDRESS BOOK Backend has no special properties to parse. */
+}
+
+static void
+migrate_parse_caldav_property (ParseData *parse_data,
+                               const gchar *property_name,
+                               const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "autoschedule") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CALDAV_BACKEND,
+			"Autoschedule",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "usermail") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CALDAV_BACKEND,
+			"Email", property_value);
+	}
+}
+
+static void
+migrate_parse_caldav_source (ParseData *parse_data)
+{
+	if (parse_data->soup_uri->host != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Host", parse_data->soup_uri->host);
+
+	/* We may override this later if we see an "ssl" property. */
+	if (parse_data->soup_uri->port == 0)
+		parse_data->soup_uri->port = 80;
+
+	g_key_file_set_integer (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_AUTHENTICATION,
+		"Port", parse_data->soup_uri->port);
+
+	if (parse_data->soup_uri->user != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", parse_data->soup_uri->user);
+
+	if (parse_data->soup_uri->path != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CALDAV_BACKEND,
+			"Path", parse_data->soup_uri->path);
+
+	parse_data->property_func = migrate_parse_caldav_property;
+}
+
+static void
+migrate_parse_google_calendar_property (ParseData *parse_data,
+                                        const gchar *property_name,
+                                        const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "username") == 0) {
+		gchar *path;
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", property_value);
+
+		path = g_strdup_printf (
+			"/calendar/dav/%s/events", property_value);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CALDAV_BACKEND,
+			"Path", path);
+
+		g_free (path);
+	}
+}
+
+static void
+migrate_parse_google_contacts_property (ParseData *parse_data,
+                                        const gchar *property_name,
+                                        const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "refresh-interval") == 0) {
+		guint64 interval_seconds;
+
+		interval_seconds =
+			g_ascii_strtoull (property_value, NULL, 10);
+
+		if (interval_seconds >= 60) {
+			g_key_file_set_boolean (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_REFRESH,
+				"Enabled", TRUE);
+			g_key_file_set_uint64 (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_REFRESH,
+				"IntervalMinutes",
+				interval_seconds / 60);
+		}
+
+	} else if (g_strcmp0 (property_name, "username") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", property_value);
+	}
+}
+
+static void
+migrate_parse_google_source (ParseData *parse_data)
+{
+	if (parse_data->type == PARSE_TYPE_ADDRESSBOOK)
+		parse_data->property_func =
+			migrate_parse_google_contacts_property;
+
+	else {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Host", "www.google.com");
+
+		g_key_file_set_integer (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Port", 443);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_SECURITY,
+			"Method", "tls");
+
+		parse_data->property_func =
+			migrate_parse_google_calendar_property;
+	}
+}
+
+static void
+migrate_parse_ldap_property (ParseData *parse_data,
+                             const gchar *property_name,
+                             const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "can-browse") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"CanBrowse",
+			is_true (property_value));
+
+	/* This is an integer value, but we can use the string as is. */
+	} else if (g_strcmp0 (property_name, "limit") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"Limit", property_value);
+	}
+}
+
+static void
+migrate_parse_ldap_source (ParseData *parse_data)
+{
+	if (parse_data->soup_uri->host != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Host", parse_data->soup_uri->host);
+
+	if (parse_data->soup_uri->port != 0)
+		g_key_file_set_integer (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Port", parse_data->soup_uri->port);
+
+	if (parse_data->soup_uri->user != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", parse_data->soup_uri->user);
+
+	/* Skip the leading slash on the URI path to get the RootDn. */
+	if (parse_data->soup_uri->path != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"RootDn", parse_data->soup_uri->path + 1);
+
+	if (g_strcmp0 (parse_data->soup_uri->query, "?sub?") == 0)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"Scope", "subtree");
+
+	if (g_strcmp0 (parse_data->soup_uri->query, "?one?") == 0)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"Scope", "onelevel");
+
+	parse_data->property_func = migrate_parse_ldap_property;
+}
+
+static void
+migrate_parse_vcf_source (ParseData *parse_data)
+{
+	if (parse_data->soup_uri->path != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_VCF_BACKEND,
+			"Path", parse_data->soup_uri->path);
+
+	/* VCF Backend has no special properties to parse. */
+}
+
+static void
+migrate_parse_weather_property (ParseData *parse_data,
+                                const gchar *property_name,
+                                const gchar *property_value)
+{
+	/* XXX Temperature property was replaced by units... I think. */
+	if (g_strcmp0 (property_name, "temperature") == 0) {
+		gboolean metric;
+
+		metric = (g_strcmp0 (property_value, "fahrenheit") != 0);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEATHER_BACKEND,
+			"Units", metric ? "metric" : "imperial");
+
+	} else if (g_strcmp0 (property_name, "units") == 0) {
+		gboolean metric;
+
+		metric = (g_strcmp0 (property_value, "metric") == 0);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEATHER_BACKEND,
+			"Units", metric ? "metric" : "imperial");
+	}
+}
+
+static void
+migrate_parse_weather_source (ParseData *parse_data)
+{
+	/* Oh man, we actually try to shove a weather location into
+	 * a URI!  The station code winds up as the host component,
+	 * and the location name winds up as the path component. */
+	if (parse_data->soup_uri->host != NULL) {
+		gchar *location;
+
+		if (parse_data->soup_uri->path != NULL)
+			location = g_strconcat (
+				parse_data->soup_uri->host,
+				parse_data->soup_uri->path, NULL);
+		else
+			location = g_strdup (parse_data->soup_uri->host);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEATHER_BACKEND,
+			"Location", location);
+
+		g_free (location);
+	}
+
+
+	parse_data->property_func = migrate_parse_weather_property;
+}
+
+static void
+migrate_parse_webcal_source (ParseData *parse_data)
+{
+	if (parse_data->soup_uri->host != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Host", parse_data->soup_uri->host);
+
+	/* We may override this later if we see an "ssl" property. */
+	if (parse_data->soup_uri->port == 0)
+		parse_data->soup_uri->port = 80;
+
+	g_key_file_set_integer (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_AUTHENTICATION,
+		"Port", parse_data->soup_uri->port);
+
+	if (parse_data->soup_uri->user != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", parse_data->soup_uri->user);
+
+	if (parse_data->soup_uri->path != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CALDAV_BACKEND,
+			"Path", parse_data->soup_uri->path);
+
+	/* Webcal Backend has no special properties to parse. */
+}
+
+static void
+migrate_parse_webdav_property (ParseData *parse_data,
+                               const gchar *property_name,
+                               const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "avoid_ifmatch") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+			"AvoidIfmatch",
+			is_true (property_value));
+	}
+}
+
+static void
+migrate_parse_webdav_source (ParseData *parse_data)
+{
+	if (parse_data->soup_uri->host != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Host", parse_data->soup_uri->host);
+
+	if (parse_data->soup_uri->port != 0)
+		g_key_file_set_integer (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Port", parse_data->soup_uri->port);
+
+	if (parse_data->soup_uri->user != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", parse_data->soup_uri->user);
+
+	if (parse_data->soup_uri->path != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+			"Path", parse_data->soup_uri->path);
+
+	parse_data->property_func = migrate_parse_webdav_property;
+}
+
+static void
+migrate_parse_group (ParseData *parse_data,
+                     const gchar *element_name,
+                     const gchar **attribute_names,
+                     const gchar **attribute_values,
+                     GError **error)
+{
+	const gchar *base_uri;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_STRING,
+		"uid", NULL,
+		G_MARKUP_COLLECT_STRING,
+		"name", NULL,
+		G_MARKUP_COLLECT_STRING,
+		"base_uri", &base_uri,
+		G_MARKUP_COLLECT_BOOLEAN |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"readonly", NULL,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	/* Convert "file://" schemes to "local:". */
+	if (g_strcmp0 (base_uri, "file://") == 0)
+		base_uri = "local:";
+
+	parse_data->base_uri = g_strdup (base_uri);
+}
+
+static void
+migrate_parse_source (ParseData *parse_data,
+                      const gchar *element_name,
+                      const gchar **attribute_names,
+                      const gchar **attribute_values,
+                      GError **error)
+{
+	const gchar *uid;
+	const gchar *name;
+	const gchar *color_spec;
+	const gchar *config_dir;
+	const gchar *group_name;
+	const gchar *absolute_uri;
+	const gchar *relative_uri;
+	gchar *directory;
+	gchar *backend_name;
+	gchar *parent_name;
+	gchar *uri_string;
+	gchar *cp;
+	gboolean success;
+	gboolean is_google_calendar;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_STRING,
+		"uid", &uid,
+		G_MARKUP_COLLECT_STRING,
+		"name", &name,
+		G_MARKUP_COLLECT_STRING |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"color_spec", &color_spec,
+		G_MARKUP_COLLECT_STRING,
+		"relative_uri", &relative_uri,
+		G_MARKUP_COLLECT_STRING |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"uri", &absolute_uri,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	/* Don't try and migrate the "system" sources, as
+	 * we'll defer to the built-in "system-*" key files. */
+	if (g_strcmp0 (relative_uri, "system") == 0)
+		return;
+
+	/* Also skip any sources with a "contacts://" base URI, which
+	 * should just be "Birthdays & Anniversaries".  We'll reset to
+	 * the built-in key file. */
+	if (g_strcmp0 (parse_data->base_uri, "contacts://") == 0)
+		return;
+
+	switch (parse_data->type) {
+		case PARSE_TYPE_ADDRESSBOOK:
+			group_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+			break;
+		case PARSE_TYPE_CALENDAR:
+			group_name = E_SOURCE_EXTENSION_CALENDAR;
+			break;
+		case PARSE_TYPE_TASKS:
+			group_name = E_SOURCE_EXTENSION_TASK_LIST;
+			break;
+		case PARSE_TYPE_MEMOS:
+			group_name = E_SOURCE_EXTENSION_MEMO_LIST;
+			break;
+		default:
+			g_return_if_reached ();
+	}
+
+	config_dir = e_get_user_config_dir ();
+	directory = g_build_filename (config_dir, "sources", NULL);
+	g_mkdir_with_parents (directory, 0700);
+
+	parse_data->filename = g_build_filename (directory, uid, NULL);
+
+	g_free (directory);
+
+	/* If the file already exists, skip this source.  It may be that we
+	 * already migrated it, in which case we don't want to overwrite it. */
+	if (g_file_test (parse_data->filename, G_FILE_TEST_EXISTS))
+		return;
+
+	parse_data->key_file = g_key_file_new ();
+
+	/* Trim ':' or '://' off the base_uri to get the backend name. */
+	backend_name = g_strdup (parse_data->base_uri);
+	if ((cp = strchr (backend_name, ':')) != NULL)
+		*cp = '\0';
+
+	/* The parent name is generally the backend name + "-stub". */
+	parent_name = g_strdup_printf ("%s-stub", backend_name);
+
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_GROUP_NAME,
+		"DisplayName", name);
+
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_GROUP_NAME,
+		"Parent", parent_name);
+
+	if (color_spec != NULL)
+		g_key_file_set_string (
+			parse_data->key_file, group_name,
+			"Color", color_spec);
+
+	is_google_calendar =
+		(parse_data->type == PARSE_TYPE_CALENDAR) &&
+		(g_strcmp0 (parse_data->base_uri, "google://") == 0);
+
+	/* For Google Calendar sources we override the backend name. */
+	if (is_google_calendar)
+		g_key_file_set_string (
+			parse_data->key_file, group_name,
+			"BackendName", "caldav");
+	else
+		g_key_file_set_string (
+			parse_data->key_file, group_name,
+			"BackendName", backend_name);
+
+	g_free (backend_name);
+	g_free (parent_name);
+
+	/* Prefer absolute URIs over relative URIs.  All these
+	 * other strange rules are for backward-compatibility. */
+	if (absolute_uri != NULL)
+		uri_string = g_strdup (absolute_uri);
+	else if (g_str_has_suffix (parse_data->base_uri, "/"))
+		uri_string = g_strconcat (
+			parse_data->base_uri, relative_uri, NULL);
+	else if (g_strcmp0 (parse_data->base_uri, "local:") == 0)
+		uri_string = g_strconcat (
+			parse_data->base_uri, relative_uri, NULL);
+	else
+		uri_string = g_strconcat (
+			parse_data->base_uri, "/", relative_uri, NULL);
+
+	parse_data->soup_uri = soup_uri_new (uri_string);
+
+	/* Mangle the URI to not contain invalid characters.  We'll need
+	 * this later to rename the source's cache and data directories. */
+	parse_data->mangled_uri = g_strdelimit (uri_string, ":/", '_');
+
+	/* g_strdelimit() modifies the input string in place, so ParseData
+	 * now owns 'uri_string'.  Clear the pointer to emphasize that. */
+	uri_string = NULL;
+
+	if (parse_data->soup_uri == NULL) {
+		g_warning (
+			"  Failed to parse source URI: %s",
+			(absolute_uri != NULL) ? absolute_uri : relative_uri);
+		g_key_file_free (parse_data->key_file);
+		parse_data->key_file = NULL;
+		return;
+	}
+
+	if (g_strcmp0 (parse_data->base_uri, "local:") == 0)
+		migrate_parse_local_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "caldav://") == 0)
+		migrate_parse_caldav_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "google://") == 0)
+		migrate_parse_google_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "ldap://";) == 0)
+		migrate_parse_ldap_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "vcf://") == 0)
+		migrate_parse_vcf_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "weather://") == 0)
+		migrate_parse_weather_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "webcal://") == 0)
+		migrate_parse_webcal_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "webdav://") == 0)
+		migrate_parse_webdav_source (parse_data);
+
+	migrate_keyring_entry (
+		uid,
+		parse_data->soup_uri->user,
+		parse_data->soup_uri->host,
+		parse_data->soup_uri->scheme);
+}
+
+static void
+migrate_parse_property (ParseData *parse_data,
+                        const gchar *element_name,
+                        const gchar **attribute_names,
+                        const gchar **attribute_values,
+                        GError **error)
+{
+	const gchar *property_name;
+	const gchar *property_value;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_STRING,
+		"name", &property_name,
+		G_MARKUP_COLLECT_STRING,
+		"value", &property_value,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	if (g_strcmp0 (property_name, "alarm") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_ALARMS,
+			"IncludeMe",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "auth") == 0) {
+		if (is_true (property_value))
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_AUTHENTICATION,
+				"Method", "plain/password");
+		else if (is_false (property_value))
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_AUTHENTICATION,
+				"Method", "none");
+		else
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_AUTHENTICATION,
+				"Method", property_value);
+
+	} else if (g_strcmp0 (property_name, "completion") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTOCOMPLETE,
+			"IncludeMe",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "last-notified") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_ALARMS,
+			"LastNotified", property_value);
+
+	} else if (g_strcmp0 (property_name, "offline_sync") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_OFFLINE,
+			"StaySynchronized",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "refresh") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_REFRESH,
+			"Enabled", TRUE);
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_REFRESH,
+			"IntervalMinutes", property_value);
+
+	} else if (g_strcmp0 (property_name, "remember_password") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"RememberPassword",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "ssl") == 0) {
+		if (is_true (property_value))
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_SECURITY,
+				"Method", "tls");
+		else if (is_false (property_value))
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_SECURITY,
+				"Method", "none");
+
+		/* These next two are LDAP-specific. */
+		else if (g_strcmp0 (property_value, "always") == 0)
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_SECURITY,
+				"Method", "starttls");
+		else if (g_strcmp0 (property_value, "whenever_possible") == 0)
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_SECURITY,
+				"Method", "ldaps");
+
+		/* For WebDAV-based backends we set the port to 80
+		 * (http://) by default.  If we see that and we're
+		 * using a secure connection, bump the port to 443
+		 * (https://). */
+		if (parse_data->soup_uri->port == 80)
+			if (is_true (property_value))
+				g_key_file_set_integer (
+					parse_data->key_file,
+					E_SOURCE_EXTENSION_AUTHENTICATION,
+					"Port", 443);
+	} else if (g_strcmp0 (property_name, "use_ssl") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_SECURITY,
+			"Method",
+			is_true (property_value) ?
+			"tls" : "none");
+
+		/* For WebDAV-based backends we set the port to 80
+		 * (http://) by default.  If we see that and we're
+		 * using a secure connection, bump the port to 443
+		 * (https://). */
+		if (parse_data->soup_uri->port == 80)
+			if (is_true (property_value))
+				g_key_file_set_integer (
+					parse_data->key_file,
+					E_SOURCE_EXTENSION_AUTHENTICATION,
+					"Port", 443);
+
+	} else if (g_strcmp0 (property_name, "use-in-contacts-calendar") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CONTACTS_BACKEND,
+			"IncludeMe",
+			is_true (property_value));
+
+	} else if (parse_data->property_func != NULL) {
+		parse_data->property_func (
+			parse_data, property_name, property_value);
+	}
+}
+
+static void
+migrate_parse_source_xml_start_element (GMarkupParseContext *context,
+                                        const gchar *element_name,
+                                        const gchar **attribute_names,
+                                        const gchar **attribute_values,
+                                        gpointer user_data,
+                                        GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "group") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_SOURCES_VALUE)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_GROUP;
+
+		migrate_parse_group (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "source") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_GROUP)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_SOURCE;
+
+		migrate_parse_source (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "properties") == 0) {
+		/* Disregard group properties, we're only
+		 * interested in source properties. */
+		if (parse_data->state == PARSE_STATE_IN_GROUP)
+			return;
+
+		if (parse_data->state != PARSE_STATE_IN_SOURCE)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_PROPERTIES;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "property") == 0) {
+		/* Disregard group properties, we're only
+		 * interested in source properties. */
+		if (parse_data->state == PARSE_STATE_IN_GROUP)
+			return;
+
+		if (parse_data->state != PARSE_STATE_IN_PROPERTIES)
+			goto invalid_content;
+
+		/* The key file will be NULL if we decided to skip it.
+		 * e.g. A file with the same UID may already exist. */
+		if (parse_data->key_file != NULL)
+			migrate_parse_property (
+				parse_data,
+				element_name,
+				attribute_names,
+				attribute_values,
+				error);
+
+		return;
+	}
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+		"Unknown element <%s>", element_name);
+
+	return;
+
+invalid_content:
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+		"Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_source_xml_end_element (GMarkupParseContext *context,
+                                      const gchar *element_name,
+                                      gpointer user_data,
+                                      GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "group") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_GROUP) {
+			parse_data->state = PARSE_STATE_IN_SOURCES_VALUE;
+
+			/* Clean up <group> tag data. */
+
+			g_free (parse_data->base_uri);
+			parse_data->base_uri = NULL;
+		}
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "source") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SOURCE) {
+			parse_data->state = PARSE_STATE_IN_GROUP;
+
+			/* Clean up <source> tag data. */
+
+			/* The key file will be NULL if we decided to skip it.
+			 * e.g. A file with the same UID may already exist. */
+			if (parse_data->key_file != NULL) {
+				GError *local_error = NULL;
+
+				migrate_parse_commit_changes (
+					parse_data->type,
+					parse_data->filename,
+					parse_data->key_file,
+					parse_data->mangled_uri,
+					&local_error);
+
+				if (local_error != NULL) {
+					g_printerr (
+						"  FAILED: %s\n",
+						local_error->message);
+					g_error_free (local_error);
+				}
+
+				g_key_file_free (parse_data->key_file);
+				parse_data->key_file = NULL;
+			}
+
+			g_free (parse_data->filename);
+			parse_data->filename = NULL;
+
+			g_free (parse_data->mangled_uri);
+			parse_data->mangled_uri = NULL;
+
+			if (parse_data->soup_uri != NULL) {
+				soup_uri_free (parse_data->soup_uri);
+				parse_data->soup_uri = NULL;
+			}
+
+			parse_data->property_func = NULL;
+		}
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "properties") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_PROPERTIES)
+			parse_data->state = PARSE_STATE_IN_SOURCE;
+		return;
+	}
+}
+
+static GMarkupParser source_xml_parser = {
+	migrate_parse_source_xml_start_element,
+	migrate_parse_source_xml_end_element,
+	NULL,  /* text */
+	NULL,  /* passthrough */
+	NULL   /* error */
+};
+
+static void
+migrate_parse_gconf_xml_start_element (GMarkupParseContext *context,
+                                       const gchar *element_name,
+                                       const gchar **attribute_names,
+                                       const gchar **attribute_values,
+                                       gpointer user_data,
+                                       GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "gconf") == 0) {
+		if (parse_data->state != PARSE_STATE_INITIAL)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_GCONF;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "entry") == 0) {
+		const gchar *name;
+		gboolean success;
+
+		if (parse_data->state != PARSE_STATE_IN_GCONF)
+			goto invalid_content;
+
+		success = g_markup_collect_attributes (
+			element_name,
+			attribute_names,
+			attribute_values,
+			error,
+			G_MARKUP_COLLECT_STRING,
+			"name", &name,
+			G_MARKUP_COLLECT_STRING,
+			"mtime", NULL,
+			G_MARKUP_COLLECT_STRING |
+			G_MARKUP_COLLECT_OPTIONAL,
+			"type", NULL,
+			G_MARKUP_COLLECT_STRING |
+			G_MARKUP_COLLECT_OPTIONAL,
+			"ltype", NULL,
+			G_MARKUP_COLLECT_STRING |
+			G_MARKUP_COLLECT_OPTIONAL,
+			"schema", NULL,
+			G_MARKUP_COLLECT_STRING |
+			G_MARKUP_COLLECT_OPTIONAL,
+			"value", NULL,
+			G_MARKUP_COLLECT_INVALID);
+
+		if (success && g_strcmp0 (name, "accounts") == 0)
+			parse_data->state = PARSE_STATE_IN_ACCOUNTS_ENTRY;
+
+		if (success && g_strcmp0 (name, "signatues") == 0)
+			parse_data->state = PARSE_STATE_IN_SIGNATURES_ENTRY;
+
+		if (success && g_strcmp0 (name, "sources") == 0)
+			parse_data->state = PARSE_STATE_IN_SOURCES_ENTRY;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "li") == 0)
+		return;
+
+	if (g_strcmp0 (element_name, "stringvalue") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_ENTRY)
+			parse_data->state = PARSE_STATE_IN_ACCOUNTS_VALUE;
+
+		if (parse_data->state == PARSE_STATE_IN_SIGNATURES_ENTRY)
+			parse_data->state = PARSE_STATE_IN_SIGNATURES_VALUE;
+
+		if (parse_data->state == PARSE_STATE_IN_SOURCES_ENTRY)
+			parse_data->state = PARSE_STATE_IN_SOURCES_VALUE;
+
+		return;
+	}
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+		"Unknown element <%s>", element_name);
+
+	return;
+
+invalid_content:
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+		"Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_gconf_xml_end_element (GMarkupParseContext *context,
+                                     const gchar *element_name,
+                                     gpointer user_data,
+                                     GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "gconf") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_GCONF)
+			parse_data->state = PARSE_STATE_INITIAL;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "entry") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_ENTRY)
+			parse_data->state = PARSE_STATE_IN_GCONF;
+
+		if (parse_data->state == PARSE_STATE_IN_SIGNATURES_ENTRY)
+			parse_data->state = PARSE_STATE_IN_GCONF;
+
+		if (parse_data->state == PARSE_STATE_IN_SOURCES_ENTRY)
+			parse_data->state = PARSE_STATE_IN_GCONF;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "stringvalue") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_VALUE)
+			parse_data->state = PARSE_STATE_IN_ACCOUNTS_ENTRY;
+
+		if (parse_data->state == PARSE_STATE_IN_SIGNATURES_VALUE)
+			parse_data->state = PARSE_STATE_IN_SIGNATURES_ENTRY;
+
+		if (parse_data->state == PARSE_STATE_IN_SOURCES_VALUE)
+			parse_data->state = PARSE_STATE_IN_SOURCES_ENTRY;
+
+		return;
+	}
+}
+
+static void
+migrate_parse_gconf_xml_text (GMarkupParseContext *context,
+                              const gchar *text,
+                              gsize length,
+                              gpointer user_data,
+                              GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	/* The account and signature data is encoded XML stuffed into
+	 * GConf XML (yuck!).  Fortunately GMarkupParseContext decodes
+	 * the XML for us, so we just have to feed it to a nested
+	 * GMarkupParseContext. */
+
+	switch (parse_data->state) {
+		case PARSE_STATE_IN_ACCOUNTS_VALUE:
+			context = g_markup_parse_context_new (
+				&account_xml_parser, 0, parse_data, NULL);
+			break;
+
+		case PARSE_STATE_IN_SIGNATURES_VALUE:
+			context = g_markup_parse_context_new (
+				&signature_xml_parser, 0, parse_data, NULL);
+			break;
+
+		case PARSE_STATE_IN_SOURCES_VALUE:
+			context = g_markup_parse_context_new (
+				&source_xml_parser, 0, parse_data, NULL);
+			break;
+
+		default:
+			return;
+	}
+
+	if (g_markup_parse_context_parse (context, text, length, error))
+		g_markup_parse_context_end_parse (context, error);
+
+	g_markup_parse_context_free (context);
+}
+
+static GMarkupParser gconf_xml_parser = {
+	migrate_parse_gconf_xml_start_element,
+	migrate_parse_gconf_xml_end_element,
+	migrate_parse_gconf_xml_text,
+	NULL,  /* passthrough */
+	NULL   /* error */
+};
+
+static gboolean
+migrate_parse_gconf_xml (ParseType parse_type,
+                         const gchar *contents,
+                         gsize length,
+                         GError **error)
+{
+	GMarkupParseContext *context;
+	ParseData *parse_data;
+	gboolean success = FALSE;
+
+	parse_data = parse_data_new (parse_type);
+
+	context = g_markup_parse_context_new (
+		&gconf_xml_parser, 0, parse_data,
+		(GDestroyNotify) parse_data_free);
+
+	if (g_markup_parse_context_parse (context, contents, length, error))
+		if (g_markup_parse_context_end_parse (context, error))
+			success = TRUE;
+
+	g_markup_parse_context_free (context);
+
+	return success;
+}
+
+static void
+migrate_remove_gconf_xml (const gchar *gconf_key,
+                          const gchar *gconf_xml)
+{
+	/* Remove the GConf string list so the user is not haunted by
+	 * old data sources being resurrected from leftover GConf data.
+	 * Also delete the %gconf.xml file itself.  If gconfd is running
+	 * then it will just recreate the file from memory when it exits
+	 * (which is why we invoke gconftool-2), otherwise the file will
+	 * stay deleted. */
+
+	gchar *path_to_program;
+
+	path_to_program = g_find_program_in_path ("gconftool-2");
+
+	if (path_to_program != NULL) {
+		gchar *command_line;
+		GError *error = NULL;
+
+		command_line = g_strjoin (
+			" ",
+			path_to_program,
+			"--set",
+			"--type=list",
+			"--list-type=string",
+			gconf_key, "[]", NULL);
+
+		/* We don't really care if the command worked or not,
+		 * just check that the program got spawned successfully. */
+		if (!g_spawn_command_line_async (command_line, &error)) {
+			g_printerr (
+				"Failed to spawn '%s': %s\n",
+				path_to_program, error->message);
+			g_error_free (error);
+		}
+
+		g_free (path_to_program);
+		g_free (command_line);
+	}
+
+	if (g_file_test (gconf_xml, G_FILE_TEST_IS_REGULAR)) {
+		if (g_remove (gconf_xml) == -1) {
+			g_printerr (
+				"Failed to remove '%s': %s\n",
+				gconf_xml, g_strerror (errno));
+		}
+	}
+}
+
+void
+evolution_source_registry_migrate_sources (void)
+{
+	gchar *base_dir;
+	gchar *contents;
+	gchar *gconf_xml;
+	gsize length;
+	const gchar *gconf_key;
+	GError *error = NULL;
+
+	base_dir = g_build_filename (
+		g_get_home_dir (), ".gconf", "apps", "evolution", NULL);
+
+	/* ------------------------------------------------------------------*/
+
+	g_print ("Migrating mail accounts from GConf...\n");
+
+	gconf_xml = g_build_filename (
+		base_dir, "mail", "%gconf.xml", NULL);
+	g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+	if (error == NULL) {
+		migrate_parse_gconf_xml (
+			PARSE_TYPE_MAIL,
+			contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error == NULL) {
+		gconf_key = "/apps/evolution/mail/accounts";
+		migrate_remove_gconf_xml (gconf_key, gconf_xml);
+	} else {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_free (gconf_xml);
+
+	/* ------------------------------------------------------------------*/
+
+	g_print ("Migrating addressbook sources from GConf...\n");
+
+	gconf_xml = g_build_filename (
+		base_dir, "addressbook", "%gconf.xml", NULL);
+	g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+	if (error == NULL) {
+		migrate_parse_gconf_xml (
+			PARSE_TYPE_ADDRESSBOOK,
+			contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error == NULL) {
+		gconf_key = "/apps/evolution/addressbook/sources";
+		migrate_remove_gconf_xml (gconf_key, gconf_xml);
+	} else {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_free (gconf_xml);
+
+	/* ------------------------------------------------------------------*/
+
+	g_print ("Migrating calendar sources from GConf...\n");
+
+	gconf_xml = g_build_filename (
+		base_dir, "calendar", "%gconf.xml", NULL);
+	g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+	if (error == NULL) {
+		migrate_parse_gconf_xml (
+			PARSE_TYPE_CALENDAR,
+			contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error == NULL) {
+		gconf_key = "/apps/evolution/calendar/sources";
+		migrate_remove_gconf_xml (gconf_key, gconf_xml);
+	} else {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_free (gconf_xml);
+
+	/* ------------------------------------------------------------------*/
+
+	g_print ("Migrating task list sources from GConf...\n");
+
+	gconf_xml = g_build_filename (
+		base_dir, "tasks", "%gconf.xml", NULL);
+	g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+	if (error == NULL) {
+		migrate_parse_gconf_xml (
+			PARSE_TYPE_TASKS,
+			contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error == NULL) {
+		gconf_key = "/apps/evolution/tasks/sources";
+		migrate_remove_gconf_xml (gconf_key, gconf_xml);
+	} else {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_free (gconf_xml);
+
+	/* ------------------------------------------------------------------*/
+
+	g_print ("Migrating memo list sources from GConf...\n");
+
+	gconf_xml = g_build_filename (
+		base_dir, "memos", "%gconf.xml", NULL);
+	g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+	if (error == NULL) {
+		migrate_parse_gconf_xml (
+			PARSE_TYPE_MEMOS,
+			contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error == NULL) {
+		gconf_key = "/apps/evolution/memos/sources";
+		migrate_remove_gconf_xml (gconf_key, gconf_xml);
+	} else {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_free (gconf_xml);
+
+	/* ------------------------------------------------------------------*/
+
+	g_free (base_dir);
+}
diff --git a/services/evolution-source-registry/evolution-source-registry.c b/services/evolution-source-registry/evolution-source-registry.c
new file mode 100644
index 0000000..361ca01
--- /dev/null
+++ b/services/evolution-source-registry/evolution-source-registry.c
@@ -0,0 +1,86 @@
+/*
+ * e-source-registry.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <config.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+
+#include <libebackend/e-dbus-source-server.h>
+
+#include "e-authenticator.h"
+
+/* Forward Declarations */
+void evolution_source_registry_migrate_basedir (void);
+void evolution_source_registry_migrate_sources (void);
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	EDBusServer *server;
+	GError *error = NULL;
+
+	setlocale (LC_ALL, "");
+	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+	gtk_init (&argc, &argv);
+
+	/* Migrate user data from ~/.evolution to XDG base directories. */
+	evolution_source_registry_migrate_basedir ();
+
+	/* Migrate ESource data from GConf XML blobs to key files.
+	 * Do this AFTER XDG base directory migration since the key
+	 * files are saved according to XDG base directory settings. */
+	evolution_source_registry_migrate_sources ();
+
+	server = e_dbus_source_server_new ();
+
+	e_dbus_source_server_set_authenticator_type (
+		E_DBUS_SOURCE_SERVER (server),
+		E_TYPE_AUTHENTICATOR);
+
+	/* Failure here is fatal.  Don't even try to keep going. */
+	e_dbus_source_server_load_all (
+		E_DBUS_SOURCE_SERVER (server), &error);
+
+	if (error != NULL) {
+		g_printerr ("%s\n", error->message);
+		g_object_unref (server);
+		exit (EXIT_FAILURE);
+	}
+
+	g_print ("Server is up and running...\n");
+
+	/* Keep the server from quitting on its own.
+	 * We don't have a way of tracking number of
+	 * active clients, so once the server is up,
+	 * it's up until the session bus closes. */
+	e_dbus_server_hold (server);
+
+	e_dbus_server_run (server, FALSE);
+
+	g_object_unref (server);
+
+	g_print ("Bye.\n");
+
+	return 0;
+}
diff --git a/services/evolution-source-registry/org.gnome.evolution.dataserver.Sources.service.in b/services/evolution-source-registry/org.gnome.evolution.dataserver.Sources.service.in
new file mode 100644
index 0000000..1db5de8
--- /dev/null
+++ b/services/evolution-source-registry/org.gnome.evolution.dataserver.Sources.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name= SOURCES_DBUS_SERVICE_NAME@
+Exec= libexecdir@/e-source-registry



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