[evolution-exchange] Move server code over from evolution-data-server.



commit 4b2c20de87bc899f1ade4bb098e5d06fcb9b425f
Author: Matthew Barnes <mbarnes redhat com>
Date:   Thu Oct 8 11:41:46 2009 -0500

    Move server code over from evolution-data-server.

 Makefile.am                                      |   15 +
 addressbook/Makefile.am                          |   42 +-
 calendar/Makefile.am                             |   16 +-
 camel/Makefile.am                                |   20 +-
 configure.ac                                     |  201 +-
 docs/reference/Makefile.am                       |    9 +-
 docs/reference/tmpl/e2k-action.sgml              |  227 ++
 docs/reference/tmpl/e2k-autoconfig.sgml          |  179 ++
 docs/reference/tmpl/e2k-context-private.sgml     |   67 +
 docs/reference/tmpl/e2k-context.sgml             |  324 +++
 docs/reference/tmpl/e2k-freebusy.sgml            |   94 +
 docs/reference/tmpl/e2k-global-catalog.sgml      |  160 ++
 docs/reference/tmpl/e2k-http-utils.sgml          |   80 +
 docs/reference/tmpl/e2k-kerberos.sgml            |   37 +
 docs/reference/tmpl/e2k-operation-private.sgml   |   29 +
 docs/reference/tmpl/e2k-operation.sgml           |   39 +
 docs/reference/tmpl/e2k-properties.sgml          |  318 +++
 docs/reference/tmpl/e2k-restriction-vapor.sgml   |   37 +
 docs/reference/tmpl/e2k-restriction.sgml         |  246 ++
 docs/reference/tmpl/e2k-result-private.sgml      |   86 +
 docs/reference/tmpl/e2k-result.sgml              |   60 +
 docs/reference/tmpl/e2k-rule.sgml                |  311 +++
 docs/reference/tmpl/e2k-security-descriptor.sgml |  200 ++
 docs/reference/tmpl/e2k-sid.sgml                 |  122 +
 docs/reference/tmpl/e2k-uri.sgml                 |  103 +
 docs/reference/tmpl/e2k-user-dialog.sgml         |   28 +
 docs/reference/tmpl/e2k-utils.sgml               |  186 ++
 docs/reference/tmpl/e2k-xml-utils.sgml           |   67 +
 docs/reference/tmpl/mapi.sgml                    |  144 ++
 docs/reference/tmpl/test-utils.sgml              |   61 +
 docs/reference/tmpl/xntlm-des.sgml               |   25 +
 docs/reference/tmpl/xntlm-md4.sgml               |   10 +
 docs/reference/tmpl/xntlm.sgml                   |   34 +
 eplugin/Makefile.am                              |   93 +-
 eplugin/org-gnome-exchange-operations.eplug      |    2 +-
 po/POTFILES.in                                   |   22 +
 server/ChangeLog                                 | 1262 ++++++++++
 server/Makefile.am                               |    3 +
 server/docs/ChangeLog                            |    3 +
 server/docs/openldap-ntlm.diff                   |  199 ++
 server/lib/Makefile.am                           |  129 +
 server/lib/actest.c                              |  201 ++
 server/lib/cptest.c                              |  120 +
 server/lib/davcat.c                              |  184 ++
 server/lib/e2k-action.c                          |  800 +++++++
 server/lib/e2k-action.h                          |   53 +
 server/lib/e2k-autoconfig.c                      | 1750 ++++++++++++++
 server/lib/e2k-autoconfig.h                      |   81 +
 server/lib/e2k-context.c                         | 2764 ++++++++++++++++++++++
 server/lib/e2k-context.h                         |  213 ++
 server/lib/e2k-freebusy.c                        |  555 +++++
 server/lib/e2k-freebusy.h                        |   64 +
 server/lib/e2k-global-catalog-ldap.h             |   31 +
 server/lib/e2k-global-catalog.c                  | 1247 ++++++++++
 server/lib/e2k-global-catalog.h                  |  117 +
 server/lib/e2k-http-utils.c                      |  185 ++
 server/lib/e2k-http-utils.h                      |   62 +
 server/lib/e2k-kerberos.c                        |  183 ++
 server/lib/e2k-kerberos.h                        |   33 +
 server/lib/e2k-marshal.c                         |  126 +
 server/lib/e2k-marshal.h                         |   30 +
 server/lib/e2k-marshal.list                      |    2 +
 server/lib/e2k-operation.c                       |  171 ++
 server/lib/e2k-operation.h                       |   38 +
 server/lib/e2k-path.c                            |  251 ++
 server/lib/e2k-path.h                            |   39 +
 server/lib/e2k-properties.c                      |  798 +++++++
 server/lib/e2k-properties.h                      |  121 +
 server/lib/e2k-propnames.c.in                    |   26 +
 server/lib/e2k-propnames.h.in                    |  224 ++
 server/lib/e2k-proptags.h.in                     |    9 +
 server/lib/e2k-restriction.c                     | 1068 +++++++++
 server/lib/e2k-restriction.h                     |   97 +
 server/lib/e2k-result.c                          |  629 +++++
 server/lib/e2k-result.h                          |   62 +
 server/lib/e2k-rule-xml.c                        |  932 ++++++++
 server/lib/e2k-rule-xml.h                        |   12 +
 server/lib/e2k-rule.c                            |  686 ++++++
 server/lib/e2k-rule.h                            |  251 ++
 server/lib/e2k-security-descriptor.c             |  959 ++++++++
 server/lib/e2k-security-descriptor.h             |   80 +
 server/lib/e2k-sid.c                             |  317 +++
 server/lib/e2k-sid.h                             |   68 +
 server/lib/e2k-types.h                           |   93 +
 server/lib/e2k-uri.c                             |  418 ++++
 server/lib/e2k-uri.h                             |   41 +
 server/lib/e2k-utils.c                           |  668 ++++++
 server/lib/e2k-utils.h                           |   46 +
 server/lib/e2k-validate.h                        |   55 +
 server/lib/e2k-xml-utils.c                       |  255 ++
 server/lib/e2k-xml-utils.h                       |   26 +
 server/lib/ebrowse.c                             |  713 ++++++
 server/lib/fbtest.c                              |  146 ++
 server/lib/gctest.c                              |  250 ++
 server/lib/mapi-properties                       | 1549 ++++++++++++
 server/lib/mapi.h                                |  122 +
 server/lib/ruletest.c                            |   90 +
 server/lib/test-utils.c                          |  259 ++
 server/lib/test-utils.h                          |   24 +
 server/lib/urltest.c                             |  158 ++
 server/storage/Makefile.am                       |   77 +
 server/storage/e-folder-exchange.c               | 1039 ++++++++
 server/storage/e-folder-exchange.h               |  153 ++
 server/storage/e-folder-tree.c                   |  449 ++++
 server/storage/e-folder-tree.h                   |   58 +
 server/storage/e-folder-type-registry.c          |  409 ++++
 server/storage/e-folder-type-registry.h          |   89 +
 server/storage/e-folder.c                        |  462 ++++
 server/storage/e-folder.h                        |  100 +
 server/storage/e-storage.c                       |  828 +++++++
 server/storage/e-storage.h                       |  208 ++
 server/storage/exchange-account.c                | 2398 +++++++++++++++++++
 server/storage/exchange-account.h                |  202 ++
 server/storage/exchange-constants.h              |   24 +
 server/storage/exchange-esource.c                |  422 ++++
 server/storage/exchange-esource.h                |   24 +
 server/storage/exchange-folder-size.c            |  251 ++
 server/storage/exchange-folder-size.h            |   49 +
 server/storage/exchange-hierarchy-favorites.c    |  341 +++
 server/storage/exchange-hierarchy-favorites.h    |   47 +
 server/storage/exchange-hierarchy-foreign.c      |  598 +++++
 server/storage/exchange-hierarchy-foreign.h      |   51 +
 server/storage/exchange-hierarchy-gal.c          |   68 +
 server/storage/exchange-hierarchy-gal.h          |   35 +
 server/storage/exchange-hierarchy-somedav.c      |  259 ++
 server/storage/exchange-hierarchy-somedav.h      |   46 +
 server/storage/exchange-hierarchy-webdav.c       |  944 ++++++++
 server/storage/exchange-hierarchy-webdav.h       |   69 +
 server/storage/exchange-hierarchy.c              |  377 +++
 server/storage/exchange-hierarchy.h              |  101 +
 server/storage/exchange-oof.c                    |  206 ++
 server/storage/exchange-oof.h                    |   21 +
 server/storage/exchange-types.h                  |   66 +
 server/xntlm/ChangeLog                           |   61 +
 server/xntlm/Makefile.am                         |   16 +
 server/xntlm/xntlm-des.c                         |  346 +++
 server/xntlm/xntlm-des.h                         |   20 +
 server/xntlm/xntlm-md4.c                         |  175 ++
 server/xntlm/xntlm-md4.h                         |   10 +
 server/xntlm/xntlm.c                             |  323 +++
 server/xntlm/xntlm.h                             |   16 +
 tools/Makefile.am                                |   44 +-
 142 files changed, 37150 insertions(+), 174 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index c584ad1..3b7447e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,4 +1,5 @@
 SUBDIRS = \
+	server \
 	eplugin \
 	tools \
 	camel \
@@ -12,6 +13,20 @@ DISTCLEANFILES = \
 	intltool-update \
 	intltool-merge
 
+MAINTAINERCLEANFILES = \
+	$(srcdir)/INSTALL \
+	$(srcdir)/aclocal.m4 \
+	$(srcdir)/autoscan.log \
+	$(srcdir)/config.guess \
+	$(srcdir)/config.h.in \
+	$(srcdir)/config.sub \
+	$(srcdir)/configure.scan \
+	$(srcdir)/depcomp \
+	$(srcdir)/install-sh \
+	$(srcdir)/ltmain.sh \
+	$(srcdir)/missing \
+	$(srcdir)/mkinstalldirs
+
 EXTRA_DIST = \
 	MAINTAINERS \
 	intltool-extract.in \
diff --git a/addressbook/Makefile.am b/addressbook/Makefile.am
index b1e9711..5fbefae 100644
--- a/addressbook/Makefile.am
+++ b/addressbook/Makefile.am
@@ -1,8 +1,10 @@
 AM_CPPFLAGS = \
 	-I$(top_srcdir) \
+	-I$(top_srcdir)/server/lib \
+	-I$(top_srcdir)/server/storage \
+	-I$(top_builddir)/server/lib \
 	-DG_LOG_DOMAIN=\"e-book-backend-exchange\" \
-	$(ADDRESSBOOK_CFLAGS) \
-	$(LIBEXCHANGE_CFLAGS) \
+	$(EVOLUTION_DATA_SERVER_CFLAGS) \
 	$(LDAP_CFLAGS)
 
 if HAVE_LIBDB
@@ -11,7 +13,7 @@ endif
 
 # GConf schemas
 
-schemadir   = $(GCONF_SCHEMA_FILE_DIR)
+schemadir = $(GCONF_SCHEMA_FILE_DIR)
 schema_in_files = apps_exchange_addressbook.schemas.in.in
 schema_DATA = $(schema_in_files:.schemas.in.in=-$(BASE_VERSION).schemas)
 %-$(BASE_VERSION).schemas.in: %.schemas.in.in
@@ -21,19 +23,19 @@ schema_DATA = $(schema_in_files:.schemas.in.in=-$(BASE_VERSION).schemas)
 
 install-data-local:
 if OS_WIN32
-	if test -z "$(DESTDIR)" ; then  \
-		for p in $(schema_DATA) ; do  \
-			(echo set GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE);  \
-			 echo $(GCONFTOOL) --makefile-install-rule $$p) >_temp.bat;  \
-			cmd /c _temp.bat;  \
-			rm _temp.bat;  \
-		done  \
+	if test -z "$(DESTDIR)" ; then \
+		for p in $(schema_DATA) ; do \
+			(echo set GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE); \
+			 echo $(GCONFTOOL) --makefile-install-rule $$p) >_temp.bat; \
+			cmd /c _temp.bat; \
+			rm _temp.bat; \
+		done \
 	fi
 else
-	if test -z "$(DESTDIR)" ; then  \
-		for p in $(schema_DATA) ; do  \
-			GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $$p;  \
-		done  \
+	if test -z "$(DESTDIR)" ; then \
+		for p in $(schema_DATA) ; do \
+			GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $$p; \
+		done \
 	fi
 endif
 
@@ -46,15 +48,15 @@ libebookbackendexchange_la_SOURCES = \
 	e-book-backend-gal.c \
 	e-book-backend-gal.h
 
+libebookbackendexchange_la_LIBADD = \
+	$(top_builddir)/server/storage/libexchange-storage.la \
+	$(EVOLUTION_DATA_SERVER_LIBS)
+
 if HAVE_LIBDB
-libebookbackendexchange_la_SOURCES += \
+libebookbackendexchange_la_SOURCES = \
 	e-book-backend-db-cache.c \
 	e-book-backend-db-cache.h
 
-libebookbackendexchange_la_LIBADD =  				\
-	$(top_builddir)/tools/libevolution-exchange-shared.la	\
-	$(DB_LIBS)
-
 libebookbackendexchange_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
 endif
@@ -62,7 +64,7 @@ endif
 EXTRA_DIST = \
 	$(schema_in_files)
 
-CLEANFILES =  \
+CLEANFILES = \
 	apps_exchange_addressbook-$(BASE_VERSION).schemas
 
 -include $(top_srcdir)/git.mk
diff --git a/calendar/Makefile.am b/calendar/Makefile.am
index 2664176..cac0b07 100644
--- a/calendar/Makefile.am
+++ b/calendar/Makefile.am
@@ -1,9 +1,11 @@
 AM_CPPFLAGS = \
 	-I$(top_srcdir) \
-	$(CALENDAR_CFLAGS) \
-	$(LIBEXCHANGE_CFLAGS) \
-	$(LDAP_CFLAGS) \
-	-DG_LOG_DOMAIN=\"e-cal-backend-exchange\"
+	-I$(top_srcdir)/server/lib \
+	-I$(top_srcdir)/server/storage \
+	-I$(top_builddir)/server/lib \
+	-DG_LOG_DOMAIN=\"e-cal-backend-exchange\" \
+	$(EVOLUTION_DATA_SERVER_CFLAGS) \
+	$(LDAP_CFLAGS)
 
 extension_LTLIBRARIES = \
         libecalbackendexchange.la
@@ -25,10 +27,8 @@ libecalbackendexchange_la_SOURCES = \
 libecalbackendexchange_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
 
-libecalbackendexchange_la_LIBADD =  				\
-	$(top_builddir)/tools/libevolution-exchange-shared.la	\
-	$(CALENDAR_LIBS)					\
-	$(LIBEXCHANGE_LIBS)					\
+libecalbackendexchange_la_LIBADD = \
+	$(EVOLUTION_DATA_SERVER_LIBS) \
 	$(LDAP_LIBS)
 
 -include $(top_srcdir)/git.mk
diff --git a/camel/Makefile.am b/camel/Makefile.am
index 1b8b629..ead9f83 100644
--- a/camel/Makefile.am
+++ b/camel/Makefile.am
@@ -5,10 +5,11 @@ CAMEL_provider_DATA = libcamelexchange.urls
 
 AM_CPPFLAGS = \
 	$(WARN_CFLAGS) \
-	$(GLIB_CFLAGS) \
-	$(CAMEL_CFLAGS) \
-	$(SHELL_CFLAGS) \
-	$(LIBEXCHANGE_CFLAGS) \
+	$(EVOLUTION_PLUGIN_CFLAGS) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/server/lib \
+	-I$(top_srcdir)/server/storage \
+	-I$(top_builddir)/server/lib \
 	-DPREFIX=\"$(prefix)\" \
 	-DCONNECTOR_LOCALEDIR=\"$(localedir)\" \
 	-DG_LOG_DOMAIN=\"camel-exchange-provider\"
@@ -37,12 +38,11 @@ noinst_HEADERS = \
 libcamelexchange_la_LDFLAGS = \
 	-avoid-version -module $(NO_UNDEFINED)
 
-libcamelexchange_la_LIBADD =		\
-	$(top_builddir)/tools/libevolution-exchange-shared.la \
-	$(LDAP_LIBS)			\
-	$(LIBEXCHANGE_LIBS)		\
-	$(EXCHANGE_STORAGE_LIBS)	\
-	$(SOCKET_LIBS)			\
+libcamelexchange_la_LIBADD = \
+	$(top_builddir)/server/storage/libexchange-storage.la \
+	$(EVOLUTION_PLUGIN_LIBS) \
+	$(LDAP_LIBS) \
+	$(SOCKET_LIBS) \
 	$(PTHREAD_LIB)
 
 EXTRA_DIST = libcamelexchange.urls
diff --git a/configure.ac b/configure.ac
index 4872904..5bb3fc0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -33,9 +33,16 @@ esac
 dnl Update these for every new development release of evolution. These numbers
 dnl actually correspond to the next stable release number
 EVOLUTION_API_VERSION=2.30
-EDS_API_VERSION=1.2
 EDS_BASE_VERSION=2.30
 
+LIBEXCHANGE_STORAGE_CURRENT=3
+LIBEXCHANGE_STORAGE_REVISION=0
+LIBEXCHANGE_STORAGE_AGE=0
+
+AC_SUBST(LIBEXCHANGE_STORAGE_CURRENT)
+AC_SUBST(LIBEXCHANGE_STORAGE_REVISION)
+AC_SUBST(LIBEXCHANGE_STORAGE_AGE)
+
 AC_MSG_CHECKING(Evolution version)
 EVOLUTION_VERSION=`pkg-config --modversion evolution-shell 2>/dev/null`
 if test -z "$EVOLUTION_VERSION"; then
@@ -51,9 +58,6 @@ BASE_VERSION=$EVOLUTION_API_VERSION
 AC_DEFINE_UNQUOTED(BASE_VERSION, "$BASE_VERSION", Connector base version)
 AC_SUBST(BASE_VERSION)
 
-API_VERSION=$EDS_API_VERSION
-AC_DEFINE_UNQUOTED(API_VERSION, "$API_VERSION", Eds api version)
-AC_SUBST(API_VERSION)
 AC_SUBST(EDS_BASE_VERSION)
 
 dnl Initialize maintainer mode
@@ -85,7 +89,7 @@ AS_COMPILER_FLAGS(WARNING_FLAGS,
 	-Wno-unused-parameter
 	-Wdeclaration-after-statement
 	-Werror-implicit-function-declaration
-	-Wformat-nonliteral -Wformat-security -Winit-self
+	-Wformat-security -Winit-self
 	-Wmissing-declarations -Wmissing-include-dirs
 	-Wmissing-noreturn -Wnested-externs -Wpointer-arith
 	-Wredundant-decls -Wundef -Wwrite-strings")
@@ -94,6 +98,7 @@ AC_SUBST(WARNING_FLAGS)
 # Other useful compiler warnings for test builds only.
 # These may produce warnings we have no control over.
 #
+#	-Wformat-nonliteral
 #	-Wmissing-format-attribute
 #	-Wshadow
 
@@ -143,13 +148,12 @@ PKG_CHECK_MODULES(GNOME_PLATFORM,
 	 libsoup-2.4 >= libsoup_minimum_version])
 
 PKG_CHECK_MODULES(EVOLUTION_DATA_SERVER,
-	[libebook-$EDS_API_VERSION >= eds_minimum_version
-	 libecal-$EDS_API_VERSION >= eds_minimum_version
-	 libedataserver-$EDS_API_VERSION >= eds_minimum_version
-	 libedataserverui-$EDS_API_VERSION >= eds_minimum_version
-	 libegroupwise-$EDS_API_VERSION >= eds_minimum_version
-	 libebackend-$EDS_API_VERSION >= eds_minimum_version
-	 libexchange-storage-$EDS_API_VERSION >= eds_minimum_version])
+	[libebook-1.2 >= eds_minimum_version
+	 libecal-1.2 >= eds_minimum_version
+	 libedataserver-1.2 >= eds_minimum_version
+	 libedataserverui-1.2 >= eds_minimum_version
+	 libegroupwise-1.2 >= eds_minimum_version
+	 libebackend-1.2 >= eds_minimum_version])
 
 dnl *************************
 dnl CFLAGS and LIBS and stuff
@@ -162,11 +166,6 @@ case $CFLAGS in
 	CFLAGS="$CFLAGS -Wno-sign-compare"
 	;;
 esac
-dnl AC_DEFINE(G_DISABLE_DEPRECATED,1,[No deprecated glib functions])
-dnl AC_DEFINE(GDK_DISABLE_DEPRECATED,1,[No deprecated gdk functions])
-dnl AC_DEFINE(GTK_DISABLE_DEPRECATED,1,[No deprecated gtk functions])
-dnl AC_DEFINE(GNOME_DISABLE_DEPRECATED,1,[No deprecated gnome functions])
-dnl AC_DEFINE(GCONF_DISABLE_DEPRECATED,1,[No deprecated gconf functions])
 
 AC_EGREP_HEADER(socklen_t, sys/socket.h, :, AC_DEFINE(socklen_t, int, [Define to "int" if socklen_t is not defined]))
 
@@ -178,69 +177,24 @@ AM_GCONF_SOURCE_2
 CONNECTOR_DATADIR='$(datadir)/evolution-exchange/$(BASE_VERSION)'
 AC_SUBST(CONNECTOR_DATADIR)
 
-dnl Check that e-d-s was built with Exchange support.
-EXCHANGE_PACKAGE="`pkg-config --exists libexchange-storage-$EDS_API_VERSION`"
-if test "x$EXCHANGE_PACKAGE" != "x"; then
-	echo "
-		You probably have not built evolution-data-server with 
-		Exchange support or fix your PKG_CONFIG_PATH to point 
-		it to the proper location.
-		"
-	exit 1
-fi
-
 plugindir=`$PKG_CONFIG --variable=plugindir evolution-plugin`
 AC_SUBST(plugindir)
 
 EVOLUTION_PLUGIN_errordir="`pkg-config --variable=errordir evolution-plugin`"
 AC_SUBST(EVOLUTION_PLUGIN_errordir)
 
-extensiondir="`pkg-config --variable=extensiondir evolution-data-server-$EDS_API_VERSION`"
+extensiondir="`pkg-config --variable=extensiondir evolution-data-server-1.2`"
 AC_SUBST(extensiondir)
 
-EVOLUTION_idldir="`pkg-config --variable=idldir evolution-shell`"
-AC_SUBST(EVOLUTION_idldir)
-
-EVOLUTION_IDL_INCLUDES="`pkg-config --variable=IDL_INCLUDES evolution-shell`"
-AC_SUBST(EVOLUTION_IDL_INCLUDES)
-
-EVOLUTION_privlibdir="`pkg-config --variable=privlibdir evolution-shell`"
-AC_SUBST(EVOLUTION_privlibdir)
-
-EVOLUTION_privlibexecdir="`pkg-config --variable=privlibexecdir evolution-shell`"
-AC_SUBST(EVOLUTION_privlibexecdir)
-
 EVOLUTION_imagesdir="`pkg-config --variable=imagesdir evolution-shell`"
 AC_SUBST(EVOLUTION_imagesdir)
 
-CAMEL_providerdir="`pkg-config --variable=camel_providerdir camel-provider-$EDS_API_VERSION`"
+CAMEL_providerdir="`pkg-config --variable=camel_providerdir camel-provider-1.2`"
 AC_SUBST(CAMEL_providerdir)
 
-
-PKG_CHECK_MODULES(ADDRESSBOOK, libedata-book-$EDS_API_VERSION camel-provider-$EDS_API_VERSION)
-AC_SUBST(ADDRESSBOOK_CFLAGS)
-
-PKG_CHECK_MODULES(CALENDAR, libedata-cal-$EDS_API_VERSION)
-AC_SUBST(CALENDAR_CFLAGS)
-AC_SUBST(CALENDAR_LIBS)
-
-PKG_CHECK_MODULES(CAMEL, camel-provider-$EDS_API_VERSION)
-AC_SUBST(CAMEL_CFLAGS)
-
-PKG_CHECK_MODULES(MAIL, libecal-$EDS_API_VERSION)
-AC_SUBST(MAIL_CFLAGS)
-
-PKG_CHECK_MODULES(EXCHANGE_STORAGE, libedataserverui-$EDS_API_VERSION libedata-book-$EDS_API_VERSION libedata-cal-$EDS_API_VERSION libsoup-2.4 libxml-2.0 camel-provider-$EDS_API_VERSION)
-AC_SUBST(EXCHANGE_STORAGE_CFLAGS)
-AC_SUBST(EXCHANGE_STORAGE_LIBS)
-
-PKG_CHECK_MODULES(LIBEXCHANGE, libsoup-2.4 evolution-shell libedataserverui-$EDS_API_VERSION libebackend-$EDS_API_VERSION libexchange-storage-$EDS_API_VERSION)
-AC_SUBST(LIBEXCHANGE_CFLAGS)
-AC_SUBST(LIBEXCHANGE_LIBS)
-
-dnl ****************************
-dnl Check for evolution plugins 
-dnl ****************************
+dnl ***************************
+dnl Check for evolution plugins
+dnl ***************************
 PKG_CHECK_MODULES(EVOLUTION_PLUGIN, evolution-plugin >= evo_minimum_version)
 AC_SUBST(EVOLUTION_PLUGIN_CFLAGS)
 AC_SUBST(EVOLUTION_PLUGIN_LIBS)
@@ -282,9 +236,27 @@ no)
 	case $with_openldap in
 	no)
 		AC_ERROR(LDAP support is required for Connector)
-	;;
+		;;
+	*)
+		case $with_static_ldap in
+		yes)
+			msg_ldap="$with_openldap (static)"
+			;;
+		*)
+			msg_ldap="$with_openldap (dynamic)"
+			;;
+		esac
 	esac
 	;;
+*)
+	case $with_static_sunldap in
+	yes)
+		msg_ldap="$with_sunldap (static)"
+		;;
+	*)
+		msg_ldap="$with_sunldap (dynamic)"
+		;;
+	esac
 esac
 
 SAVE_CFLAGS="$CFLAGS"
@@ -347,7 +319,7 @@ else
 	DB_CFLAGS="$CFLAGS -I /usr/include"
 	DB_LIBS="$LIBS -ldb"
 fi
-	
+
 AC_MSG_CHECKING([Berkeley DB])
 save_cflags=$CFLAGS; CFLAGS=$DB_CFLAGS
 save_libs=$LIBS; LIBS="$DB_LIBS"
@@ -373,6 +345,87 @@ LIBS=$save_libs
 AC_SUBST(DB_CFLAGS)
 AC_SUBST(DB_LIBS)
 
+dnl ****************
+dnl Kerberos Support
+dnl ****************
+
+AC_ARG_WITH(krb5,
+  AC_HELP_STRING( [--with-krb5=DIR],
+		  [Location of Kerberos 5 install dir]),
+  with_krb5="$withval", with_krb5="no")
+
+AC_ARG_WITH(krb5-libs,
+  AC_HELP_STRING( [--with-krb5-libs=DIR],
+		  [Location of Kerberos 5 libraries]),
+  with_krb5_libs="$withval", with_krb5_libs="$with_krb5/lib")
+
+AC_ARG_WITH(krb5-includes,
+  AC_HELP_STRING( [--with-krb5-includes=DIR],
+		  [Location of Kerberos 5 headers]),
+  with_krb5_includes="$withval", with_krb5_includes="")
+
+msg_krb5="no"
+if test "x${with_krb5}" != "xno"; then
+	LIBS_save="$LIBS"
+
+	mitlibs="-lkrb5 -lk5crypto -lcom_err -lgssapi_krb5"
+	heimlibs="-lkrb5 -lcrypto -lasn1 -lcom_err -lroken -lgssapi"
+	sunlibs="-lkrb5 -lgss"
+	AC_CACHE_CHECK([for Kerberos 5], ac_cv_lib_kerberos5,
+	[
+		LIBS="$LIBS -L$with_krb5_libs $mitlibs"
+		AC_TRY_LINK([#include <krb5.h>],krb5_init_context, ac_cv_lib_kerberos5="$mitlibs",
+		[
+			LIBS="$LIBS_save -L$with_krb5_libs $heimlibs"
+			AC_TRY_LINK_FUNC(krb5_init_context, ac_cv_lib_kerberos5="$heimlibs",
+			[
+				LIBS="$LIBS_save -L$with_krb5_libs $sunlibs"
+				AC_TRY_LINK_FUNC(krb5_init_context, ac_cv_lib_kerberos5="$sunlibs", ac_cv_lib_kerberos5="no")
+			])
+		])
+		LIBS="$LIBS_save"
+	])
+	if test "$ac_cv_lib_kerberos5" != "no"; then
+		AC_DEFINE(HAVE_KRB5,1,[Define if you have Krb5])
+		if test "$ac_cv_lib_kerberos5" = "$mitlibs"; then
+			AC_DEFINE(HAVE_MIT_KRB5,1,[Define if you have MIT Krb5])
+			if test -z "$with_krb5_includes"; then
+				KRB5_CFLAGS="-I$with_krb5/include"
+			else
+				KRB5_CFLAGS="-I$with_krb5_includes"
+			fi
+			msg_krb5="yes (MIT)"
+		else
+			if test "$ac_cv_lib_kerberos5" = "$heimlibs"; then
+				AC_DEFINE(HAVE_HEIMDAL_KRB5,1,[Define if you have Heimdal])
+				if test -z "$with_krb5_includes"; then
+					KRB5_CFLAGS="-I$with_krb5/include/heimdal"
+				else
+					KRB5_CFLAGS="-I$with_krb5_includes"
+				fi
+				msg_krb5="yes (Heimdal)"
+			else
+				AC_DEFINE(HAVE_SUN_KRB5,1,[Define if you have Sun Kerberosv5])
+				if test -z "$with_krb5_includes"; then
+					KRB5_CFLAGS="-I$with_krb5/include/kerberosv5"
+				else
+					KRB5_CFLAGS="-I$with_krb5_includes"
+				fi
+				msg_krb5="yes (Sun)"
+			fi
+		fi
+		KRB5_LDFLAGS="-L$with_krb5_libs $ac_cv_lib_kerberos5"
+		AC_MSG_RESULT(msg_krb5)
+	else
+		dnl AC_MSG_CHECKING([for Kerberos 5])
+		AC_MSG_RESULT([no])
+		AC_MSG_ERROR([You specified with krb5, but it was not found.])
+	fi
+else
+	AC_MSG_WARN([krb5 support disabled])
+fi
+
+AM_CONDITIONAL(ENABLE_KRB5, test x$with_krb5 != xno)
 
 dnl ******************************
 dnl Makefiles
@@ -389,8 +442,18 @@ tools/Makefile
 docs/Makefile
 docs/reference/Makefile
 po/Makefile.in
+server/Makefile
+server/lib/Makefile
+server/storage/Makefile
+server/xntlm/Makefile
 ])
 
+echo "
+	evolution-exchange has been configured as follows:
+	Kerberos 5:       $msg_krb5
+	LDAP support:     $msg_ldap
+"
+
 case $ac_cv_func_ldap_ntlm_bind in
 no)
 	echo ""
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index f694c02..eca0b6e 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -36,13 +36,14 @@ CFILE_GLOB=$(top_srcdir)/*/*.c
 # CFLAGS and LDFLAGS for compiling scan program. Only needed
 # if $(DOC_MODULE).types is non-empty.
 INCLUDES = \
-	-I$(top_srcdir)/lib \
-	-I$(top_builddir)/lib \
-	$(LIBEXCHANGE_CFLAGS) \
+	-I$(top_srcdir)/server/lib \
+	-I$(top_srcdir)/server/storage \
+	$(GNOME_PLATFORM_CFLAGS) \
 	$(LDAP_CFLAGS)
 
 GTKDOC_LIBS = \
-	$(LIBEXCHANGE_LIBS) \
+	$(top_builddir)/server/storage/libexchange-storage.la \
+	$(GNOME_PLATFORM_LIBS) \
 	$(LDAP_LIBS)
 
 # Extra options to supply to gtkdoc-mkdb
diff --git a/docs/reference/tmpl/e2k-action.sgml b/docs/reference/tmpl/e2k-action.sgml
index b5ee967..b7021d0 100644
--- a/docs/reference/tmpl/e2k-action.sgml
+++ b/docs/reference/tmpl/e2k-action.sgml
@@ -22,3 +22,230 @@ information in this file about how server-side rules work is wrong.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kAction ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### ENUM E2kActionType ##### -->
+<para>
+
+</para>
+
+ E2K_ACTION_MOVE: 
+ E2K_ACTION_COPY: 
+ E2K_ACTION_REPLY: 
+ E2K_ACTION_OOF_REPLY: 
+ E2K_ACTION_DEFER: 
+ E2K_ACTION_BOUNCE: 
+ E2K_ACTION_FORWARD: 
+ E2K_ACTION_DELEGATE: 
+ E2K_ACTION_TAG: 
+ E2K_ACTION_DELETE: 
+ E2K_ACTION_MARK_AS_READ: 
+
+<!-- ##### FUNCTION e2k_action_move ##### -->
+<para>
+
+</para>
+
+ store_entryid: 
+ folder_source_key: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_action_copy ##### -->
+<para>
+
+</para>
+
+ store_entryid: 
+ folder_source_key: 
+ Returns: 
+
+
+<!-- ##### ENUM E2kActionReplyFlavor ##### -->
+<para>
+
+</para>
+
+ E2K_ACTION_REPLY_FLAVOR_NOT_ORIGINATOR: 
+ E2K_ACTION_REPLY_FLAVOR_STOCK_TEMPLATE: 
+
+<!-- ##### FUNCTION e2k_action_reply ##### -->
+<para>
+
+</para>
+
+ template_entryid: 
+ template_guid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_action_oof_reply ##### -->
+<para>
+
+</para>
+
+ template_entryid: 
+ template_guid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_action_defer ##### -->
+<para>
+
+</para>
+
+ data: 
+ Returns: 
+
+
+<!-- ##### ENUM E2kActionBounceCode ##### -->
+<para>
+
+</para>
+
+ E2K_ACTION_BOUNCE_CODE_TOO_LARGE: 
+ E2K_ACTION_BOUNCE_CODE_FORM_MISMATCH: 
+ E2K_ACTION_BOUNCE_CODE_ACCESS_DENIED: 
+
+<!-- ##### FUNCTION e2k_action_bounce ##### -->
+<para>
+
+</para>
+
+ bounce_code: 
+ Returns: 
+
+
+<!-- ##### STRUCT E2kAddrList ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### ENUM E2kActionForwardFlavor ##### -->
+<para>
+
+</para>
+
+ E2K_ACTION_FORWARD_FLAVOR_PRESERVE_SENDER: 
+ E2K_ACTION_FORWARD_FLAVOR_DO_NOT_MUNGE: 
+ E2K_ACTION_FORWARD_FLAVOR_REDIRECT: 
+ E2K_ACTION_FORWARD_FLAVOR_AS_ATTACHMENT: 
+
+<!-- ##### FUNCTION e2k_action_forward ##### -->
+<para>
+
+</para>
+
+ list: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_action_delegate ##### -->
+<para>
+
+</para>
+
+ list: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_action_tag ##### -->
+<para>
+
+</para>
+
+ propname: 
+ type: 
+ value: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_action_delete ##### -->
+<para>
+
+</para>
+
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_actions_extract ##### -->
+<para>
+
+</para>
+
+ data: 
+ len: 
+ actions: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_actions_append ##### -->
+<para>
+
+</para>
+
+ ba: 
+ actions: 
+
+
+<!-- ##### FUNCTION e2k_actions_free ##### -->
+<para>
+
+</para>
+
+ actions: 
+
+
+<!-- ##### FUNCTION e2k_action_free ##### -->
+<para>
+
+</para>
+
+ act: 
+
+
+<!-- ##### FUNCTION e2k_addr_list_new ##### -->
+<para>
+
+</para>
+
+ nentries: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_addr_list_set_local ##### -->
+<para>
+
+</para>
+
+ list: 
+ entry_num: 
+ display_name: 
+ exchange_dn: 
+ email: 
+
+
+<!-- ##### FUNCTION e2k_addr_list_set_oneoff ##### -->
+<para>
+
+</para>
+
+ list: 
+ entry_num: 
+ display_name: 
+ email: 
+
+
+<!-- ##### FUNCTION e2k_addr_list_free ##### -->
+<para>
+
+</para>
+
+ list: 
+
+
diff --git a/docs/reference/tmpl/e2k-autoconfig.sgml b/docs/reference/tmpl/e2k-autoconfig.sgml
index 21a14a9..d7c5e67 100644
--- a/docs/reference/tmpl/e2k-autoconfig.sgml
+++ b/docs/reference/tmpl/e2k-autoconfig.sgml
@@ -19,3 +19,182 @@ details about the organization of the company&rsquo;s Exchange servers.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kAutoconfig ##### -->
+<para>
+
+</para>
+
+ owa_uri: 
+ gc_server: 
+ username: 
+ password: 
+ gal_limit: 
+ gal_auth: 
+ version: 
+ display_name: 
+ email: 
+ account_uri: 
+ exchange_server: 
+ timezone: 
+ nt_domain: 
+ w2k_domain: 
+ home_uri: 
+ exchange_dn: 
+ pf_server: 
+ auth_pref: 
+ require_ntlm: 
+ use_ntlm: 
+ saw_basic: 
+ saw_ntlm: 
+ nt_domain_defaulted: 
+ gc_server_autodetected: 
+
+<!-- ##### ENUM E2kExchangeVersion ##### -->
+<para>
+
+</para>
+
+ E2K_EXCHANGE_UNKNOWN: 
+ E2K_EXCHANGE_2000: 
+ E2K_EXCHANGE_2003: 
+ E2K_EXCHANGE_FUTURE: 
+
+<!-- ##### ENUM E2kAutoconfigAuthPref ##### -->
+<para>
+
+</para>
+
+ E2K_AUTOCONFIG_USE_BASIC: 
+ E2K_AUTOCONFIG_USE_NTLM: 
+ E2K_AUTOCONFIG_USE_EITHER: 
+
+<!-- ##### ENUM E2kAutoconfigResult ##### -->
+<para>
+
+</para>
+
+ E2K_AUTOCONFIG_OK: 
+ E2K_AUTOCONFIG_REDIRECT: 
+ E2K_AUTOCONFIG_TRY_SSL: 
+ E2K_AUTOCONFIG_AUTH_ERROR: 
+ E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN: 
+ E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC: 
+ E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM: 
+ E2K_AUTOCONFIG_EXCHANGE_5_5: 
+ E2K_AUTOCONFIG_NOT_EXCHANGE: 
+ E2K_AUTOCONFIG_NO_OWA: 
+ E2K_AUTOCONFIG_NO_MAILBOX: 
+ E2K_AUTOCONFIG_CANT_BPROPFIND: 
+ E2K_AUTOCONFIG_CANT_RESOLVE: 
+ E2K_AUTOCONFIG_CANT_CONNECT: 
+ E2K_AUTOCONFIG_CANCELLED: 
+ E2K_AUTOCONFIG_FAILED: 
+
+<!-- ##### FUNCTION e2k_autoconfig_new ##### -->
+<para>
+
+</para>
+
+ owa_uri: 
+ username: 
+ password: 
+ auth_pref: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_free ##### -->
+<para>
+
+</para>
+
+ ac: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_set_owa_uri ##### -->
+<para>
+
+</para>
+
+ ac: 
+ owa_uri: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_set_gc_server ##### -->
+<para>
+
+</para>
+
+ ac: 
+ gc_server: 
+ gal_limit: 
+ gal_auth: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_set_username ##### -->
+<para>
+
+</para>
+
+ ac: 
+ username: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_set_password ##### -->
+<para>
+
+</para>
+
+ ac: 
+ password: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_get_context ##### -->
+<para>
+
+</para>
+
+ ac: 
+ op: 
+ result: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_check_exchange ##### -->
+<para>
+
+</para>
+
+ ac: 
+ op: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_get_global_catalog ##### -->
+<para>
+
+</para>
+
+ ac: 
+ op: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_check_global_catalog ##### -->
+<para>
+
+</para>
+
+ ac: 
+ op: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_autoconfig_lookup_option ##### -->
+<para>
+
+</para>
+
+ option: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-context-private.sgml b/docs/reference/tmpl/e2k-context-private.sgml
index a9efb81..332b28d 100644
--- a/docs/reference/tmpl/e2k-context-private.sgml
+++ b/docs/reference/tmpl/e2k-context-private.sgml
@@ -19,3 +19,70 @@ currently also used by mail-stub-exchange.c.)
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### FUNCTION e2k_soup_message_new ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ uri: 
+ method: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_soup_message_new_full ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ uri: 
+ method: 
+ content_type: 
+ use: 
+ body: 
+ length: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_queue_message ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ msg: 
+ callback: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_context_send_message ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ msg: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_fba ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ failed_msg: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_get_last_timestamp ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-context.sgml b/docs/reference/tmpl/e2k-context.sgml
index c837cd3..5fbfeb8 100644
--- a/docs/reference/tmpl/e2k-context.sgml
+++ b/docs/reference/tmpl/e2k-context.sgml
@@ -22,3 +22,327 @@ E2kContext API</link>.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kContext ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SIGNAL E2kContext::redirect ##### -->
+<para>
+
+</para>
+
+ e2kcontext: the object which received the signal.
+ arg1: 
+ arg2: 
+ arg3: 
+
+<!-- ##### FUNCTION e2k_context_new ##### -->
+<para>
+
+</para>
+
+ uri: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_set_auth ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ username: 
+ domain: 
+ authmech: 
+ password: 
+
+
+<!-- ##### FUNCTION e2k_context_get ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ content_type: 
+ response: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_get_owa ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ claim_ie: 
+ response: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_put ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ content_type: 
+ body: 
+ length: 
+ repl_uid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_post ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ content_type: 
+ body: 
+ length: 
+ location: 
+ repl_uid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_proppatch ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ props: 
+ create: 
+ repl_uid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_bproppatch_start ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ hrefs: 
+ nhrefs: 
+ props: 
+ create: 
+ Returns: 
+
+
+<!-- ##### USER_FUNCTION E2kContextTestCallback ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ test_name: 
+ user_data: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_put_new ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ folder_uri: 
+ object_name: 
+ test_callback: 
+ user_data: 
+ content_type: 
+ body: 
+ length: 
+ location: 
+ repl_uid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_proppatch_new ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ folder_uri: 
+ object_name: 
+ test_callback: 
+ user_data: 
+ props: 
+ location: 
+ repl_uid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_propfind ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ props: 
+ nprops: 
+ results: 
+ nresults: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_bpropfind_start ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ hrefs: 
+ nhrefs: 
+ props: 
+ nprops: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_search_start ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ props: 
+ nprops: 
+ rn: 
+ orderby: 
+ ascending: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_delete ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_bdelete_start ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ hrefs: 
+ nhrefs: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_mkcol ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ uri: 
+ props: 
+ permanent_url: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_transfer_start ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ source_folder: 
+ dest_folder: 
+ source_hrefs: 
+ delete_originals: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_context_transfer_dir ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ source_href: 
+ dest_href: 
+ delete_original: 
+ permanent_url: 
+ Returns: 
+
+
+<!-- ##### ENUM E2kContextChangeType ##### -->
+<para>
+
+</para>
+
+ E2K_CONTEXT_OBJECT_CHANGED: 
+ E2K_CONTEXT_OBJECT_ADDED: 
+ E2K_CONTEXT_OBJECT_REMOVED: 
+ E2K_CONTEXT_OBJECT_MOVED: 
+
+<!-- ##### USER_FUNCTION E2kContextChangeCallback ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ uri: 
+ type: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_context_subscribe ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ uri: 
+ type: 
+ min_interval: 
+ callback: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_context_unsubscribe ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ uri: 
+
+
diff --git a/docs/reference/tmpl/e2k-freebusy.sgml b/docs/reference/tmpl/e2k-freebusy.sgml
index c609623..b33cf8d 100644
--- a/docs/reference/tmpl/e2k-freebusy.sgml
+++ b/docs/reference/tmpl/e2k-freebusy.sgml
@@ -17,3 +17,97 @@ This code is not currently used.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kFreebusy ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### ENUM E2kBusyStatus ##### -->
+<para>
+
+</para>
+
+ E2K_BUSYSTATUS_FREE: 
+ E2K_BUSYSTATUS_TENTATIVE: 
+ E2K_BUSYSTATUS_BUSY: 
+ E2K_BUSYSTATUS_OOF: 
+ E2K_BUSYSTATUS_MAX: 
+ E2K_BUSYSTATUS_ALL: 
+
+<!-- ##### STRUCT E2kFreebusyEvent ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION e2k_freebusy_new ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ public_uri: 
+ dn: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_freebusy_reset ##### -->
+<para>
+
+</para>
+
+ fb: 
+ nmonths: 
+
+
+<!-- ##### FUNCTION e2k_freebusy_add_interval ##### -->
+<para>
+
+</para>
+
+ fb: 
+ busystatus: 
+ start: 
+ end: 
+
+
+<!-- ##### FUNCTION e2k_freebusy_clear_interval ##### -->
+<para>
+
+</para>
+
+ fb: 
+ start: 
+ end: 
+
+
+<!-- ##### FUNCTION e2k_freebusy_add_from_calendar_uri ##### -->
+<para>
+
+</para>
+
+ fb: 
+ uri: 
+ start_tt: 
+ end_tt: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_freebusy_save ##### -->
+<para>
+
+</para>
+
+ fb: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_freebusy_destroy ##### -->
+<para>
+
+</para>
+
+ fb: 
+
+
diff --git a/docs/reference/tmpl/e2k-global-catalog.sgml b/docs/reference/tmpl/e2k-global-catalog.sgml
index cde98ea..0662c04 100644
--- a/docs/reference/tmpl/e2k-global-catalog.sgml
+++ b/docs/reference/tmpl/e2k-global-catalog.sgml
@@ -34,3 +34,163 @@ Exchange server their mail is on, or what their SID is.</para></listitem>
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kGlobalCatalog ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION e2k_global_catalog_new ##### -->
+<para>
+
+</para>
+
+ server: 
+ response_limit: 
+ user: 
+ domain: 
+ password: 
+ use_auth: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_global_catalog_get_ldap ##### -->
+<para>
+
+</para>
+
+ gc: 
+ op: 
+ ldap_error: 
+ Returns: 
+
+
+<!-- ##### ENUM E2kGlobalCatalogStatus ##### -->
+<para>
+
+</para>
+
+ E2K_GLOBAL_CATALOG_OK: 
+ E2K_GLOBAL_CATALOG_NO_SUCH_USER: 
+ E2K_GLOBAL_CATALOG_NO_DATA: 
+ E2K_GLOBAL_CATALOG_BAD_DATA: 
+ E2K_GLOBAL_CATALOG_EXISTS: 
+ E2K_GLOBAL_CATALOG_AUTH_FAILED: 
+ E2K_GLOBAL_CATALOG_CANCELLED: 
+ E2K_GLOBAL_CATALOG_ERROR: 
+
+<!-- ##### ENUM E2kGlobalCatalogLookupType ##### -->
+<para>
+
+</para>
+
+ E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL: 
+ E2K_GLOBAL_CATALOG_LOOKUP_BY_DN: 
+ E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN: 
+
+<!-- ##### ENUM E2kGlobalCatalogLookupFlags ##### -->
+<para>
+
+</para>
+
+ E2K_GLOBAL_CATALOG_LOOKUP_SID: 
+ E2K_GLOBAL_CATALOG_LOOKUP_EMAIL: 
+ E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX: 
+ E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN: 
+ E2K_GLOBAL_CATALOG_LOOKUP_DELEGATES: 
+ E2K_GLOBAL_CATALOG_LOOKUP_DELEGATORS: 
+ E2K_GLOBAL_CATALOG_LOOKUP_QUOTA: 
+ E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL: 
+
+<!-- ##### STRUCT E2kGlobalCatalogEntry ##### -->
+<para>
+
+</para>
+
+ dn: 
+ display_name: 
+ sid: 
+ email: 
+ exchange_server: 
+ mailbox: 
+ legacy_exchange_dn: 
+ delegates: 
+ delegators: 
+ quota_warn: 
+ quota_nosend: 
+ quota_norecv: 
+ user_account_control: 
+ mask: 
+
+<!-- ##### FUNCTION e2k_global_catalog_lookup ##### -->
+<para>
+
+</para>
+
+ gc: 
+ op: 
+ type: 
+ key: 
+ flags: 
+ entry_p: 
+ Returns: 
+
+
+<!-- ##### MACRO e2k_global_catalog_entry_free ##### -->
+<para>
+
+</para>
+
+ gc: 
+ entry: 
+
+
+<!-- ##### USER_FUNCTION E2kGlobalCatalogCallback ##### -->
+<para>
+
+</para>
+
+ gc: 
+ status: 
+ entry: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_global_catalog_async_lookup ##### -->
+<para>
+
+</para>
+
+ gc: 
+ op: 
+ type: 
+ key: 
+ flags: 
+ callback: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_global_catalog_add_delegate ##### -->
+<para>
+
+</para>
+
+ gc: 
+ op: 
+ self_dn: 
+ delegate_dn: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_global_catalog_remove_delegate ##### -->
+<para>
+
+</para>
+
+ gc: 
+ op: 
+ self_dn: 
+ delegate_dn: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-http-utils.sgml b/docs/reference/tmpl/e2k-http-utils.sgml
index a6005e0..30f9814 100644
--- a/docs/reference/tmpl/e2k-http-utils.sgml
+++ b/docs/reference/tmpl/e2k-http-utils.sgml
@@ -17,3 +17,83 @@ HTTP utility functions
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### TYPEDEF E2kHTTPStatus ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### MACRO E2K_HTTP_STATUS_IS_TRANSPORT_ERROR ##### -->
+<para>
+
+</para>
+
+ status: 
+
+
+<!-- ##### MACRO E2K_HTTP_STATUS_IS_INFORMATIONAL ##### -->
+<para>
+
+</para>
+
+ status: 
+
+
+<!-- ##### MACRO E2K_HTTP_STATUS_IS_SUCCESSFUL ##### -->
+<para>
+
+</para>
+
+ status: 
+
+
+<!-- ##### MACRO E2K_HTTP_STATUS_IS_REDIRECTION ##### -->
+<para>
+
+</para>
+
+ status: 
+
+
+<!-- ##### MACRO E2K_HTTP_STATUS_IS_CLIENT_ERROR ##### -->
+<para>
+
+</para>
+
+ status: 
+
+
+<!-- ##### MACRO E2K_HTTP_STATUS_IS_SERVER_ERROR ##### -->
+<para>
+
+</para>
+
+ status: 
+
+
+<!-- ##### FUNCTION e2k_http_parse_date ##### -->
+<para>
+
+</para>
+
+ date: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_http_parse_status ##### -->
+<para>
+
+</para>
+
+ status_line: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_http_accept_language ##### -->
+<para>
+
+</para>
+
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-kerberos.sgml b/docs/reference/tmpl/e2k-kerberos.sgml
index e3af579..72e7360 100644
--- a/docs/reference/tmpl/e2k-kerberos.sgml
+++ b/docs/reference/tmpl/e2k-kerberos.sgml
@@ -17,3 +17,40 @@ Kerberos utilities
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### ENUM E2kKerberosResult ##### -->
+<para>
+
+</para>
+
+ E2K_KERBEROS_OK: 
+ E2K_KERBEROS_USER_UNKNOWN: 
+ E2K_KERBEROS_PASSWORD_INCORRECT: 
+ E2K_KERBEROS_PASSWORD_EXPIRED: 
+ E2K_KERBEROS_PASSWORD_TOO_WEAK: 
+ E2K_KERBEROS_KDC_UNREACHABLE: 
+ E2K_KERBEROS_TIME_SKEW: 
+ E2K_KERBEROS_FAILED: 
+
+<!-- ##### FUNCTION e2k_kerberos_change_password ##### -->
+<para>
+
+</para>
+
+ user: 
+ domain: 
+ old_password: 
+ new_password: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_kerberos_check_password ##### -->
+<para>
+
+</para>
+
+ user: 
+ domain: 
+ password: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-operation-private.sgml b/docs/reference/tmpl/e2k-operation-private.sgml
index 0be291f..72c5112 100644
--- a/docs/reference/tmpl/e2k-operation-private.sgml
+++ b/docs/reference/tmpl/e2k-operation-private.sgml
@@ -18,3 +18,32 @@ These are the #E2kOperation methods used internally by #E2kContext and
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### USER_FUNCTION E2kOperationCancelFunc ##### -->
+<para>
+
+</para>
+
+ op: 
+ owner: 
+ data: 
+
+
+<!-- ##### FUNCTION e2k_operation_start ##### -->
+<para>
+
+</para>
+
+ op: 
+ canceller: 
+ owner: 
+ data: 
+
+
+<!-- ##### FUNCTION e2k_operation_finish ##### -->
+<para>
+
+</para>
+
+ op: 
+
+
diff --git a/docs/reference/tmpl/e2k-operation.sgml b/docs/reference/tmpl/e2k-operation.sgml
index fd76563..ffeded9 100644
--- a/docs/reference/tmpl/e2k-operation.sgml
+++ b/docs/reference/tmpl/e2k-operation.sgml
@@ -19,3 +19,42 @@ cancellable steps).
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kOperation ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION e2k_operation_init ##### -->
+<para>
+
+</para>
+
+ op: 
+
+
+<!-- ##### FUNCTION e2k_operation_cancel ##### -->
+<para>
+
+</para>
+
+ op: 
+
+
+<!-- ##### FUNCTION e2k_operation_is_cancelled ##### -->
+<para>
+
+</para>
+
+ op: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_operation_free ##### -->
+<para>
+
+</para>
+
+ op: 
+
+
diff --git a/docs/reference/tmpl/e2k-properties.sgml b/docs/reference/tmpl/e2k-properties.sgml
index 2769202..209376d 100644
--- a/docs/reference/tmpl/e2k-properties.sgml
+++ b/docs/reference/tmpl/e2k-properties.sgml
@@ -17,3 +17,321 @@ WebDAV properties
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### TYPEDEF E2kProperties ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION e2k_properties_new ##### -->
+<para>
+
+</para>
+
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_properties_copy ##### -->
+<para>
+
+</para>
+
+ props: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_properties_empty ##### -->
+<para>
+
+</para>
+
+ props: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_properties_free ##### -->
+<para>
+
+</para>
+
+ props: 
+
+
+<!-- ##### FUNCTION e2k_properties_get_prop ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_properties_remove ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+
+
+<!-- ##### ENUM E2kPropType ##### -->
+<para>
+
+</para>
+
+ E2K_PROP_TYPE_UNKNOWN: 
+ E2K_PROP_TYPE_STRING: 
+ E2K_PROP_TYPE_BINARY: 
+ E2K_PROP_TYPE_STRING_ARRAY: 
+ E2K_PROP_TYPE_BINARY_ARRAY: 
+ E2K_PROP_TYPE_XML: 
+ E2K_PROP_TYPE_INT: 
+ E2K_PROP_TYPE_INT_ARRAY: 
+ E2K_PROP_TYPE_BOOL: 
+ E2K_PROP_TYPE_FLOAT: 
+ E2K_PROP_TYPE_DATE: 
+
+<!-- ##### MACRO E2K_PROP_TYPE_IS_ARRAY ##### -->
+<para>
+
+</para>
+
+ type: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_string ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_string_array ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_binary ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_binary_array ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_int ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_int_array ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_xml ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_bool ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_float ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_date ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_type_as_string ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ type: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_properties_set_type_as_string_array ##### -->
+<para>
+
+</para>
+
+ props: 
+ propname: 
+ type: 
+ value: 
+
+
+<!-- ##### USER_FUNCTION E2kPropertiesForeachFunc ##### -->
+<para>
+
+</para>
+
+ propname: 
+ type: 
+ value: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_properties_foreach ##### -->
+<para>
+
+</para>
+
+ props: 
+ callback: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_properties_foreach_removed ##### -->
+<para>
+
+</para>
+
+ props: 
+ callback: 
+ user_data: 
+
+
+<!-- ##### USER_FUNCTION E2kPropertiesForeachNamespaceFunc ##### -->
+<para>
+
+</para>
+
+ namespace: 
+ abbrev: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_properties_foreach_namespace ##### -->
+<para>
+
+</para>
+
+ props: 
+ callback: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_prop_namespace_name ##### -->
+<para>
+
+</para>
+
+ prop: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_prop_namespace_abbrev ##### -->
+<para>
+
+</para>
+
+ prop: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_prop_property_name ##### -->
+<para>
+
+</para>
+
+ prop: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_prop_proptag ##### -->
+<para>
+
+</para>
+
+ prop: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_proptag_prop ##### -->
+<para>
+
+</para>
+
+ proptag: 
+ Returns: 
+
+
+<!-- ##### MACRO E2K_PROPTAG_TYPE ##### -->
+<para>
+
+</para>
+
+ proptag: 
+
+
+<!-- ##### MACRO E2K_PROPTAG_ID ##### -->
+<para>
+
+</para>
+
+ proptag: 
+
+
diff --git a/docs/reference/tmpl/e2k-restriction-vapor.sgml b/docs/reference/tmpl/e2k-restriction-vapor.sgml
index f545fc6..c9bc295 100644
--- a/docs/reference/tmpl/e2k-restriction-vapor.sgml
+++ b/docs/reference/tmpl/e2k-restriction-vapor.sgml
@@ -17,3 +17,40 @@ E2kRestriction (vaporware)
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### ENUM E2kRestrictionType ##### -->
+<para>
+
+</para>
+
+ E2K_RESTRICTION_AND: 
+ E2K_RESTRICTION_OR: 
+ E2K_RESTRICTION_NOT: 
+ E2K_RESTRICTION_CONTENT: 
+ E2K_RESTRICTION_PROPERTY: 
+ E2K_RESTRICTION_COMPAREPROPS: 
+ E2K_RESTRICTION_BITMASK: 
+ E2K_RESTRICTION_SIZE: 
+ E2K_RESTRICTION_EXIST: 
+ E2K_RESTRICTION_SUBRESTRICTION: 
+ E2K_RESTRICTION_COMMENT: 
+
+<!-- ##### FUNCTION e2k_restriction_extract ##### -->
+<para>
+
+</para>
+
+ data: 
+ len: 
+ rn: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_append ##### -->
+<para>
+
+</para>
+
+ ba: 
+ rn: 
+
+
diff --git a/docs/reference/tmpl/e2k-restriction.sgml b/docs/reference/tmpl/e2k-restriction.sgml
index f71ae79..6e6623c 100644
--- a/docs/reference/tmpl/e2k-restriction.sgml
+++ b/docs/reference/tmpl/e2k-restriction.sgml
@@ -17,3 +17,249 @@ Search criteria
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kRestriction ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION e2k_restriction_and ##### -->
+<para>
+
+</para>
+
+ nrns: 
+ rns: 
+ unref: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_andv ##### -->
+<para>
+
+</para>
+
+ rn: 
+ Varargs: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_or ##### -->
+<para>
+
+</para>
+
+ nrns: 
+ rns: 
+ unref: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_orv ##### -->
+<para>
+
+</para>
+
+ rn: 
+ Varargs: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_not ##### -->
+<para>
+
+</para>
+
+ rn: 
+ unref: 
+ Returns: 
+
+
+<!-- ##### ENUM E2kRestrictionFuzzyLevel ##### -->
+<para>
+
+</para>
+
+ E2K_FL_FULLSTRING: 
+ E2K_FL_SUBSTRING: 
+ E2K_FL_PREFIX: 
+ E2K_FL_SUFFIX: 
+ E2K_FL_IGNORECASE: 
+ E2K_FL_IGNORENONSPACE: 
+ E2K_FL_LOOSE: 
+
+<!-- ##### MACRO E2K_FL_MATCH_TYPE ##### -->
+<para>
+
+</para>
+
+ fl: 
+
+
+<!-- ##### FUNCTION e2k_restriction_content ##### -->
+<para>
+
+</para>
+
+ propname: 
+ fuzzy_level: 
+ value: 
+ Returns: 
+
+
+<!-- ##### ENUM E2kRestrictionRelop ##### -->
+<para>
+
+</para>
+
+ E2K_RELOP_LT: 
+ E2K_RELOP_LE: 
+ E2K_RELOP_GT: 
+ E2K_RELOP_GE: 
+ E2K_RELOP_EQ: 
+ E2K_RELOP_NE: 
+ E2K_RELOP_RE: 
+ E2K_RELOP_DL_MEMBER: 
+
+<!-- ##### FUNCTION e2k_restriction_prop_bool ##### -->
+<para>
+
+</para>
+
+ propname: 
+ relop: 
+ value: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_prop_int ##### -->
+<para>
+
+</para>
+
+ propname: 
+ relop: 
+ value: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_prop_date ##### -->
+<para>
+
+</para>
+
+ propname: 
+ relop: 
+ value: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_prop_string ##### -->
+<para>
+
+</para>
+
+ propname: 
+ relop: 
+ value: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_prop_binary ##### -->
+<para>
+
+</para>
+
+ propname: 
+ relop: 
+ data: 
+ len: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_compare ##### -->
+<para>
+
+</para>
+
+ propname1: 
+ relop: 
+ propname2: 
+ Returns: 
+
+
+<!-- ##### ENUM E2kRestrictionBitop ##### -->
+<para>
+
+</para>
+
+ E2K_BMR_EQZ: 
+ E2K_BMR_NEZ: 
+
+<!-- ##### FUNCTION e2k_restriction_bitmask ##### -->
+<para>
+
+</para>
+
+ propname: 
+ bitop: 
+ mask: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_size ##### -->
+<para>
+
+</para>
+
+ propname: 
+ relop: 
+ size: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_exist ##### -->
+<para>
+
+</para>
+
+ propname: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_sub ##### -->
+<para>
+
+</para>
+
+ subtable: 
+ rn: 
+ unref: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_ref ##### -->
+<para>
+
+</para>
+
+ rn: 
+
+
+<!-- ##### FUNCTION e2k_restriction_unref ##### -->
+<para>
+
+</para>
+
+ rn: 
+
+
+<!-- ##### FUNCTION e2k_restriction_to_sql ##### -->
+<para>
+
+</para>
+
+ rn: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-result-private.sgml b/docs/reference/tmpl/e2k-result-private.sgml
index 7201420..5a45c74 100644
--- a/docs/reference/tmpl/e2k-result-private.sgml
+++ b/docs/reference/tmpl/e2k-result-private.sgml
@@ -17,3 +17,89 @@ These are the #E2kOperation methods used internally by #E2kContext.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### FUNCTION e2k_results_array_new ##### -->
+<para>
+
+</para>
+
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_results_array_add_from_multistatus ##### -->
+<para>
+
+</para>
+
+ results_array: 
+ msg: 
+
+
+<!-- ##### FUNCTION e2k_results_array_free ##### -->
+<para>
+
+</para>
+
+ results_array: 
+ free_results: 
+
+
+<!-- ##### FUNCTION e2k_results_from_multistatus ##### -->
+<para>
+
+</para>
+
+ msg: 
+ results: 
+ nresults: 
+
+
+<!-- ##### FUNCTION e2k_results_copy ##### -->
+<para>
+
+</para>
+
+ results: 
+ nresults: 
+ Returns: 
+
+
+<!-- ##### USER_FUNCTION E2kResultIterFetchFunc ##### -->
+<para>
+
+</para>
+
+ iter: 
+ ctx: 
+ op: 
+ results: 
+ nresults: 
+ first: 
+ total: 
+ user_data: 
+ Returns: 
+
+
+<!-- ##### USER_FUNCTION E2kResultIterFreeFunc ##### -->
+<para>
+
+</para>
+
+ iter: 
+ user_data: 
+
+
+<!-- ##### FUNCTION e2k_result_iter_new ##### -->
+<para>
+
+</para>
+
+ ctx: 
+ op: 
+ ascending: 
+ total: 
+ fetch_func: 
+ free_func: 
+ user_data: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-result.sgml b/docs/reference/tmpl/e2k-result.sgml
index 853f99b..3aa40aa 100644
--- a/docs/reference/tmpl/e2k-result.sgml
+++ b/docs/reference/tmpl/e2k-result.sgml
@@ -17,3 +17,63 @@ WebDAV Multi-Status results
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kResult ##### -->
+<para>
+
+</para>
+
+ href: 
+ status: 
+ props: 
+
+<!-- ##### FUNCTION e2k_results_free ##### -->
+<para>
+
+</para>
+
+ results: 
+ nresults: 
+
+
+<!-- ##### TYPEDEF E2kResultIter ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION e2k_result_iter_next ##### -->
+<para>
+
+</para>
+
+ iter: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_result_iter_get_total ##### -->
+<para>
+
+</para>
+
+ iter: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_result_iter_get_index ##### -->
+<para>
+
+</para>
+
+ iter: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_result_iter_free ##### -->
+<para>
+
+</para>
+
+ iter: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-rule.sgml b/docs/reference/tmpl/e2k-rule.sgml
index a09cf8c..0a65d4d 100644
--- a/docs/reference/tmpl/e2k-rule.sgml
+++ b/docs/reference/tmpl/e2k-rule.sgml
@@ -22,3 +22,314 @@ information in this file about how server-side rules work is wrong.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kRule ##### -->
+<para>
+
+</para>
+
+ name: 
+ sequence: 
+ state: 
+ user_flags: 
+ level: 
+ condition_lcid: 
+ condition: 
+ actions: 
+ provider: 
+ provider_data: 
+
+<!-- ##### STRUCT E2kRules ##### -->
+<para>
+
+</para>
+
+ version: 
+ codepage: 
+ rules: 
+
+<!-- ##### ENUM E2kRuleState ##### -->
+<para>
+
+</para>
+
+ E2K_RULE_STATE_DISABLED: 
+ E2K_RULE_STATE_ENABLED: 
+ E2K_RULE_STATE_ERROR: 
+ E2K_RULE_STATE_ONLY_WHEN_OOF: 
+ E2K_RULE_STATE_KEEP_OOF_HISTORY: 
+ E2K_RULE_STATE_EXIT_LEVEL: 
+ E2K_RULE_STATE_CLEAR_OOF_HISTORY: 
+
+<!-- ##### FUNCTION e2k_rules_from_binary ##### -->
+<para>
+
+</para>
+
+ rules_data: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rules_to_binary ##### -->
+<para>
+
+</para>
+
+ rules: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rules_to_xml ##### -->
+<para>
+
+</para>
+
+ rules: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rules_free ##### -->
+<para>
+
+</para>
+
+ rules: 
+
+
+<!-- ##### FUNCTION e2k_rule_free ##### -->
+<para>
+
+</para>
+
+ rule: 
+
+
+<!-- ##### STRUCT E2kRuleProp ##### -->
+<para>
+
+</para>
+
+ name: 
+ proptag: 
+
+<!-- ##### FUNCTION e2k_rule_prop_set ##### -->
+<para>
+
+</para>
+
+ prop: 
+ propname: 
+
+
+<!-- ##### STRUCT E2kPropValue ##### -->
+<para>
+
+</para>
+
+ prop: 
+ type: 
+ value: 
+
+<!-- ##### FUNCTION e2k_rule_append_proptag ##### -->
+<para>
+
+</para>
+
+ ba: 
+ prop: 
+
+
+<!-- ##### FUNCTION e2k_rule_extract_proptag ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ len: 
+ prop: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_append_propvalue ##### -->
+<para>
+
+</para>
+
+ ba: 
+ pv: 
+
+
+<!-- ##### FUNCTION e2k_rule_extract_propvalue ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ len: 
+ pv: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_free_propvalue ##### -->
+<para>
+
+</para>
+
+ pv: 
+
+
+<!-- ##### FUNCTION e2k_rule_write_uint32 ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ val: 
+
+
+<!-- ##### FUNCTION e2k_rule_append_uint32 ##### -->
+<para>
+
+</para>
+
+ ba: 
+ val: 
+
+
+<!-- ##### FUNCTION e2k_rule_read_uint32 ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_extract_uint32 ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ len: 
+ val: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_write_uint16 ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ val: 
+
+
+<!-- ##### FUNCTION e2k_rule_append_uint16 ##### -->
+<para>
+
+</para>
+
+ ba: 
+ val: 
+
+
+<!-- ##### FUNCTION e2k_rule_read_uint16 ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_extract_uint16 ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ len: 
+ val: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_append_byte ##### -->
+<para>
+
+</para>
+
+ ba: 
+ val: 
+
+
+<!-- ##### FUNCTION e2k_rule_extract_byte ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ len: 
+ val: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_append_string ##### -->
+<para>
+
+</para>
+
+ ba: 
+ str: 
+
+
+<!-- ##### FUNCTION e2k_rule_extract_string ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ len: 
+ str: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_append_unicode ##### -->
+<para>
+
+</para>
+
+ ba: 
+ str: 
+
+
+<!-- ##### FUNCTION e2k_rule_extract_unicode ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ len: 
+ str: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_rule_append_binary ##### -->
+<para>
+
+</para>
+
+ ba: 
+ data: 
+
+
+<!-- ##### FUNCTION e2k_rule_extract_binary ##### -->
+<para>
+
+</para>
+
+ ptr: 
+ len: 
+ data: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-security-descriptor.sgml b/docs/reference/tmpl/e2k-security-descriptor.sgml
index e50af8e..d2c8dc8 100644
--- a/docs/reference/tmpl/e2k-security-descriptor.sgml
+++ b/docs/reference/tmpl/e2k-security-descriptor.sgml
@@ -38,3 +38,203 @@ parent in order to be able to access the object.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kSecurityDescriptor ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION e2k_security_descriptor_new ##### -->
+<para>
+
+</para>
+
+ xml_form: 
+ binary_form: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_security_descriptor_to_binary ##### -->
+<para>
+
+</para>
+
+ sd: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_security_descriptor_get_sids ##### -->
+<para>
+
+</para>
+
+ sd: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_security_descriptor_get_default ##### -->
+<para>
+
+</para>
+
+ sd: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_security_descriptor_remove_sid ##### -->
+<para>
+
+</para>
+
+ sd: 
+ sid: 
+
+
+<!-- ##### ENUM E2kPermissionsRole ##### -->
+<para>
+
+</para>
+
+ E2K_PERMISSIONS_ROLE_OWNER: 
+ E2K_PERMISSIONS_ROLE_PUBLISHING_EDITOR: 
+ E2K_PERMISSIONS_ROLE_EDITOR: 
+ E2K_PERMISSIONS_ROLE_PUBLISHING_AUTHOR: 
+ E2K_PERMISSIONS_ROLE_AUTHOR: 
+ E2K_PERMISSIONS_ROLE_NON_EDITING_AUTHOR: 
+ E2K_PERMISSIONS_ROLE_REVIEWER: 
+ E2K_PERMISSIONS_ROLE_CONTRIBUTOR: 
+ E2K_PERMISSIONS_ROLE_NONE: 
+ E2K_PERMISSIONS_ROLE_NUM_ROLES: 
+ E2K_PERMISSIONS_ROLE_CUSTOM: 
+
+<!-- ##### FUNCTION e2k_permissions_role_get_name ##### -->
+<para>
+
+</para>
+
+ role: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_permissions_role_get_perms ##### -->
+<para>
+
+</para>
+
+ role: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_permissions_role_find ##### -->
+<para>
+
+</para>
+
+ perms: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_security_descriptor_get_permissions ##### -->
+<para>
+
+</para>
+
+ sd: 
+ sid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_security_descriptor_set_permissions ##### -->
+<para>
+
+</para>
+
+ sd: 
+ sid: 
+ perms: 
+
+
+<!-- ##### MACRO E2K_PERMISSION_READ_ANY ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_CREATE ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_EDIT_OWNED ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_DELETE_OWNED ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_EDIT_ANY ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_DELETE_ANY ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_CREATE_SUBFOLDER ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_OWNER ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_CONTACT ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_FOLDER_VISIBLE ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_EDIT_MASK ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_PERMISSION_DELETE_MASK ##### -->
+<para>
+
+</para>
+
+
+
diff --git a/docs/reference/tmpl/e2k-sid.sgml b/docs/reference/tmpl/e2k-sid.sgml
index d3035c4..73fdc22 100644
--- a/docs/reference/tmpl/e2k-sid.sgml
+++ b/docs/reference/tmpl/e2k-sid.sgml
@@ -32,3 +32,125 @@ e2k_global_catalog_lookup(), #E2kSecurityDescriptor
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kSid ##### -->
+<para>
+
+</para>
+
+ parent: 
+ priv: 
+
+<!-- ##### ENUM E2kSidType ##### -->
+<para>
+
+</para>
+
+ E2K_SID_TYPE_INVALID: 
+ E2K_SID_TYPE_USER: 
+ E2K_SID_TYPE_ALIAS: 
+ E2K_SID_TYPE_GROUP: 
+ E2K_SID_TYPE_WELL_KNOWN_GROUP: 
+ E2K_SID_TYPE_DOMAIN: 
+ E2K_SID_TYPE_DELETED_ACCOUNT: 
+ E2K_SID_TYPE_UNKNOWN: 
+ E2K_SID_TYPE_COMPUTER: 
+
+<!-- ##### FUNCTION e2k_sid_new_from_string_sid ##### -->
+<para>
+
+</para>
+
+ type: 
+ string_sid: 
+ display_name: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_sid_new_from_binary_sid ##### -->
+<para>
+
+</para>
+
+ type: 
+ binary_sid: 
+ display_name: 
+ Returns: 
+
+
+<!-- ##### MACRO E2K_SID_WKS_EVERYONE ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### MACRO E2K_SID_WKS_ANONYMOUS ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### FUNCTION e2k_sid_get_sid_type ##### -->
+<para>
+
+</para>
+
+ sid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_sid_get_string_sid ##### -->
+<para>
+
+</para>
+
+ sid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_sid_get_binary_sid ##### -->
+<para>
+
+</para>
+
+ sid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_sid_get_display_name ##### -->
+<para>
+
+</para>
+
+ sid: 
+ Returns: 
+
+
+<!-- ##### MACRO E2K_SID_BINARY_SID_LEN ##### -->
+<para>
+
+</para>
+
+ bsid: 
+
+
+<!-- ##### FUNCTION e2k_sid_binary_sid_hash ##### -->
+<para>
+
+</para>
+
+ key: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_sid_binary_sid_equal ##### -->
+<para>
+
+</para>
+
+ a: 
+ b: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-uri.sgml b/docs/reference/tmpl/e2k-uri.sgml
index 804f8c9..2424505 100644
--- a/docs/reference/tmpl/e2k-uri.sgml
+++ b/docs/reference/tmpl/e2k-uri.sgml
@@ -17,3 +17,106 @@ URI utility routines
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kUri ##### -->
+<para>
+
+</para>
+
+ protocol: 
+ user: 
+ domain: 
+ authmech: 
+ passwd: 
+ host: 
+ port: 
+ path: 
+ params: 
+ query: 
+ fragment: 
+
+<!-- ##### FUNCTION e2k_uri_new ##### -->
+<para>
+
+</para>
+
+ uri_string: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_uri_free ##### -->
+<para>
+
+</para>
+
+ uri: 
+
+
+<!-- ##### FUNCTION e2k_uri_get_param ##### -->
+<para>
+
+</para>
+
+ uri: 
+ name: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_uri_decode ##### -->
+<para>
+
+</para>
+
+ part: 
+
+
+<!-- ##### FUNCTION e2k_uri_encode ##### -->
+<para>
+
+</para>
+
+ in: 
+ wss_encode: 
+ extra_enc_chars: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_uri_append_encoded ##### -->
+<para>
+
+</para>
+
+ str: 
+ in: 
+ wss_encode: 
+ extra_enc_chars: 
+
+
+<!-- ##### FUNCTION e2k_uri_path ##### -->
+<para>
+
+</para>
+
+ uri_string: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_uri_concat ##### -->
+<para>
+
+</para>
+
+ uri_prefix: 
+ tail: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_uri_relative ##### -->
+<para>
+
+</para>
+
+ uri_prefix: 
+ uri: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-user-dialog.sgml b/docs/reference/tmpl/e2k-user-dialog.sgml
index 9358f1f..7542752 100644
--- a/docs/reference/tmpl/e2k-user-dialog.sgml
+++ b/docs/reference/tmpl/e2k-user-dialog.sgml
@@ -18,3 +18,31 @@ addressbook's "selectnames" interface.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### STRUCT E2kUserDialog ##### -->
+<para>
+
+</para>
+
+ parent: 
+ priv: 
+
+<!-- ##### FUNCTION e2k_user_dialog_new ##### -->
+<para>
+
+</para>
+
+ parent_window: 
+ label_text: 
+ section_name: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_user_dialog_get_user ##### -->
+<para>
+
+</para>
+
+ dialog: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-utils.sgml b/docs/reference/tmpl/e2k-utils.sgml
index 135aa08..e9619ae 100644
--- a/docs/reference/tmpl/e2k-utils.sgml
+++ b/docs/reference/tmpl/e2k-utils.sgml
@@ -17,3 +17,189 @@ Random utility functions
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### FUNCTION e2k_parse_timestamp ##### -->
+<para>
+
+</para>
+
+ timestamp: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_make_timestamp ##### -->
+<para>
+
+</para>
+
+ when: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_make_timestamp_rfc822 ##### -->
+<para>
+
+</para>
+
+ when: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_systime_to_time_t ##### -->
+<para>
+
+</para>
+
+ systime: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_systime_from_time_t ##### -->
+<para>
+
+</para>
+
+ tt: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_filetime_to_time_t ##### -->
+<para>
+
+</para>
+
+ filetime: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_filetime_from_time_t ##### -->
+<para>
+
+</para>
+
+ tt: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_lf_to_crlf ##### -->
+<para>
+
+</para>
+
+ in: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_crlf_to_lf ##### -->
+<para>
+
+</para>
+
+ in: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_strdup_with_trailing_slash ##### -->
+<para>
+
+</para>
+
+ path: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_entryid_to_dn ##### -->
+<para>
+
+</para>
+
+ entryid: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_ascii_strcase_equal ##### -->
+<para>
+
+</para>
+
+ v: 
+ v2: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_ascii_strcase_hash ##### -->
+<para>
+
+</para>
+
+ v: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_restriction_folders_only ##### -->
+<para>
+
+</para>
+
+ rn: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_entryid_generate_oneoff ##### -->
+<para>
+
+</para>
+
+ display_name: 
+ email: 
+ unicode: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_entryid_generate_local ##### -->
+<para>
+
+</para>
+
+ exchange_dn: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_entryid_generate_contact ##### -->
+<para>
+
+</para>
+
+ contact_entryid: 
+ nth_address: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_search_key_generate ##### -->
+<para>
+
+</para>
+
+ addrtype: 
+ address: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_entryid_to_permanenturl ##### -->
+<para>
+
+</para>
+
+ entryid: 
+ base_uri: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_permanenturl_to_entryid ##### -->
+<para>
+
+</para>
+
+ permanenturl: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/e2k-xml-utils.sgml b/docs/reference/tmpl/e2k-xml-utils.sgml
index fb6658b..2b3087f 100644
--- a/docs/reference/tmpl/e2k-xml-utils.sgml
+++ b/docs/reference/tmpl/e2k-xml-utils.sgml
@@ -17,3 +17,70 @@ XML utility functions
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### FUNCTION e2k_parse_xml ##### -->
+<para>
+
+</para>
+
+ buf: 
+ len: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_parse_html ##### -->
+<para>
+
+</para>
+
+ buf: 
+ len: 
+ Returns: 
+
+
+<!-- ##### MACRO E2K_IS_NODE ##### -->
+<para>
+
+</para>
+
+ node: 
+ nspace: 
+ nname: 
+
+
+<!-- ##### MACRO E2K_XML_HEADER ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### FUNCTION e2k_g_string_append_xml_escaped ##### -->
+<para>
+
+</para>
+
+ string: 
+ value: 
+
+
+<!-- ##### FUNCTION e2k_xml_find ##### -->
+<para>
+
+</para>
+
+ node: 
+ name: 
+ Returns: 
+
+
+<!-- ##### FUNCTION e2k_xml_find_in ##### -->
+<para>
+
+</para>
+
+ node: 
+ top: 
+ name: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/mapi.sgml b/docs/reference/tmpl/mapi.sgml
index 4e4f033..4d0f8cc 100644
--- a/docs/reference/tmpl/mapi.sgml
+++ b/docs/reference/tmpl/mapi.sgml
@@ -17,3 +17,147 @@ MAPI and CDO constants
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### ENUM MapiAccess ##### -->
+<para>
+
+</para>
+
+ MAPI_ACCESS_MODIFY: 
+ MAPI_ACCESS_READ: 
+ MAPI_ACCESS_DELETE: 
+ MAPI_ACCESS_CREATE_HIERARCHY: 
+ MAPI_ACCESS_CREATE_CONTENTS: 
+ MAPI_ACCESS_CREATE_ASSOCIATED: 
+
+<!-- ##### ENUM CdoInstanceTypes ##### -->
+<para>
+
+</para>
+
+ cdoSingle: 
+ cdoMaster: 
+ cdoInstance: 
+ cdoException: 
+
+<!-- ##### ENUM MapiObjectType ##### -->
+<para>
+
+</para>
+
+ MAPI_STORE: 
+ MAPI_ADDRBOOK: 
+ MAPI_FOLDER: 
+ MAPI_ABCONT: 
+ MAPI_MESSAGE: 
+ MAPI_MAILUSER: 
+ MAPI_ATTACH: 
+ MAPI_DISTLIST: 
+ MAPI_PROFSECT: 
+ MAPI_STATUS: 
+ MAPI_SESSION: 
+ MAPI_FORMINFO: 
+
+<!-- ##### ENUM MapiPrDisplayType ##### -->
+<para>
+
+</para>
+
+ DT_MAILUSER: 
+ DT_DISTLIST: 
+ DT_FORUM: 
+ DT_AGENT: 
+ DT_ORGANIZATION: 
+ DT_PRIVATE_DISTLIST: 
+ DT_REMOTE_MAILUSER: 
+ DT_MODIFIABLE: 
+ DT_GLOBAL: 
+ DT_LOCAL: 
+ DT_WAN: 
+ DT_NOT_SPECIFIC: 
+ DT_FOLDER: 
+ DT_FOLDER_LINK: 
+ DT_FOLDER_SPECIAL: 
+
+<!-- ##### ENUM MapiPrRecipientType ##### -->
+<para>
+
+</para>
+
+ MAPI_ORIG: 
+ MAPI_TO: 
+ MAPI_CC: 
+ MAPI_BCC: 
+
+<!-- ##### ENUM MapiPrMessageFlags ##### -->
+<para>
+
+</para>
+
+ MAPI_MSGFLAG_READ: 
+ MAPI_MSGFLAG_UNMODIFIED: 
+ MAPI_MSGFLAG_SUBMIT: 
+ MAPI_MSGFLAG_UNSENT: 
+ MAPI_MSGFLAG_HASATTACH: 
+ MAPI_MSGFLAG_FROMME: 
+ MAPI_MSGFLAG_ASSOCIATED: 
+ MAPI_MSGFLAG_RESEND: 
+ MAPI_MSGFLAG_RN_PENDING: 
+ MAPI_MSGFLAG_NRN_PENDING: 
+ MAPI_MSGFLAG_ORIGIN_X400: 
+ MAPI_MSGFLAG_ORIGIN_INTERNET: 
+ MAPI_MSGFLAG_ORIGIN_MISC_EXT: 
+
+<!-- ##### ENUM MapiPrAction ##### -->
+<para>
+
+</para>
+
+ MAPI_ACTION_REPLIED: 
+ MAPI_ACTION_FORWARDED: 
+
+<!-- ##### ENUM MapiPrActionFlag ##### -->
+<para>
+
+</para>
+
+ MAPI_ACTION_FLAG_REPLIED_TO_SENDER: 
+ MAPI_ACTION_FLAG_REPLIED_TO_ALL: 
+ MAPI_ACTION_FLAG_FORWARDED: 
+
+<!-- ##### ENUM MapiPrFlagStatus ##### -->
+<para>
+
+</para>
+
+ MAPI_FOLLOWUP_UNFLAGGED: 
+ MAPI_FOLLOWUP_COMPLETED: 
+ MAPI_FOLLOWUP_FLAGGED: 
+
+<!-- ##### ENUM MapiPrPriority ##### -->
+<para>
+
+</para>
+
+ MAPI_PRIO_URGENT: 
+ MAPI_PRIO_NORMAL: 
+ MAPI_PRIO_NONURGENT: 
+
+<!-- ##### ENUM MapiPrSensitivity ##### -->
+<para>
+
+</para>
+
+ MAPI_SENSITIVITY_NONE: 
+ MAPI_SENSITIVITY_PERSONAL: 
+ MAPI_SENSITIVITY_PRIVATE: 
+ MAPI_SENSITIVITY_COMPANY_CONFIDENTIAL: 
+
+<!-- ##### ENUM MapiPrImportance ##### -->
+<para>
+
+</para>
+
+ MAPI_IMPORTANCE_LOW: 
+ MAPI_IMPORTANCE_NORMAL: 
+ MAPI_IMPORTANCE_HIGH: 
+
diff --git a/docs/reference/tmpl/test-utils.sgml b/docs/reference/tmpl/test-utils.sgml
index cf22ccf..9d4b131 100644
--- a/docs/reference/tmpl/test-utils.sgml
+++ b/docs/reference/tmpl/test-utils.sgml
@@ -17,3 +17,64 @@ Utility routines for libexchange test programs
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### FUNCTION test_main ##### -->
+<para>
+
+</para>
+
+ argc: 
+ argv: 
+
+
+<!-- ##### FUNCTION test_quit ##### -->
+<para>
+
+</para>
+
+
+
+<!-- ##### FUNCTION test_abort_if_http_error ##### -->
+<para>
+
+</para>
+
+ status: 
+
+
+<!-- ##### FUNCTION test_ask_password ##### -->
+<para>
+
+</para>
+
+ prompt: 
+ Returns: 
+
+
+<!-- ##### FUNCTION test_get_password ##### -->
+<para>
+
+</para>
+
+ user: 
+ host: 
+ Returns: 
+
+
+<!-- ##### FUNCTION test_get_context ##### -->
+<para>
+
+</para>
+
+ uri: 
+ Returns: 
+
+
+<!-- ##### FUNCTION test_get_gc ##### -->
+<para>
+
+</para>
+
+ server: 
+ Returns: 
+
+
diff --git a/docs/reference/tmpl/xntlm-des.sgml b/docs/reference/tmpl/xntlm-des.sgml
index 303d392..5af8f83 100644
--- a/docs/reference/tmpl/xntlm-des.sgml
+++ b/docs/reference/tmpl/xntlm-des.sgml
@@ -18,3 +18,28 @@ primarly for xntlm-internal use, but can also be used by other code.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### TYPEDEF XNTLM_DES_KS ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION xntlm_deskey ##### -->
+<para>
+
+</para>
+
+ ks: 
+ key: 
+ decrypt: 
+
+
+<!-- ##### FUNCTION xntlm_des ##### -->
+<para>
+
+</para>
+
+ ks: 
+ block: 
+
+
diff --git a/docs/reference/tmpl/xntlm-md4.sgml b/docs/reference/tmpl/xntlm-md4.sgml
index 7ce2ed9..12c1c54 100644
--- a/docs/reference/tmpl/xntlm-md4.sgml
+++ b/docs/reference/tmpl/xntlm-md4.sgml
@@ -17,3 +17,13 @@ This is here for use by xntlm, but can also be used by other code.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### FUNCTION xntlm_md4sum ##### -->
+<para>
+
+</para>
+
+ in: 
+ nbytes: 
+ digest: 
+
+
diff --git a/docs/reference/tmpl/xntlm.sgml b/docs/reference/tmpl/xntlm.sgml
index 8a2326b..7c6ccf3 100644
--- a/docs/reference/tmpl/xntlm.sgml
+++ b/docs/reference/tmpl/xntlm.sgml
@@ -17,3 +17,37 @@ These functions are the main interface to the xntlm library.
 <!-- ##### SECTION Stability_Level ##### -->
 
 
+<!-- ##### FUNCTION xntlm_negotiate ##### -->
+<para>
+
+</para>
+
+ Returns: 
+
+
+<!-- ##### FUNCTION xntlm_parse_challenge ##### -->
+<para>
+
+</para>
+
+ challenge: 
+ len: 
+ nonce: 
+ nt_domain: 
+ w2k_domain: 
+ Returns: 
+
+
+<!-- ##### FUNCTION xntlm_authenticate ##### -->
+<para>
+
+</para>
+
+ nonce: 
+ domain: 
+ user: 
+ password: 
+ workstation: 
+ Returns: 
+
+
diff --git a/eplugin/Makefile.am b/eplugin/Makefile.am
index 116b449..01a4a0a 100644
--- a/eplugin/Makefile.am
+++ b/eplugin/Makefile.am
@@ -1,10 +1,10 @@
 ### EVO_PLUGIN_RULE ### begin ###
 
 %.eplug: %.eplug.in
-	sed -e 's|\ PLUGINDIR\@|$(plugindir)|'		\
-	-e 's|\ SOEXT\@|$(SOEXT)|'			\
-	-e 's|\ GETTEXT_PACKAGE\@|$(GETTEXT_PACKAGE)|'	\
-	-e 's|\ LOCALEDIR\@|$(localedir)|' $< > $@
+	sed -e 's| \ PLUGINDIR \@|$(plugindir)|' \
+	-e 's| \ SOEXT \@|$(SOEXT)|' \
+	-e 's| \ GETTEXT_PACKAGE \@|$(GETTEXT_PACKAGE)|' \
+	-e 's| \ LOCALEDIR \@|$(localedir)|' $< > $@
 
 %.eplug.in: %.eplug.xml
 	LC_ALL=C $(INTLTOOL_MERGE) -x -u /tmp $< $@
@@ -19,55 +19,56 @@ plugin_DATA = org-gnome-exchange-operations.eplug
 
 plugin_LTLIBRARIES = liborg-gnome-exchange-operations.la
 
-liborg_gnome_exchange_operations_la_SOURCES = 		\
-	exchange-operations.c				\
-	exchange-operations.h				\
-	exchange-config-listener.c			\
-	exchange-config-listener.h			\
-	exchange-calendar.c				\
-	exchange-contacts.c				\
-	exchange-change-password.c			\
-	exchange-change-password.h			\
-	exchange-delegates-user.c			\
-	exchange-delegates-user.h			\
-	exchange-delegates.c				\
-	exchange-delegates.h				\
-	exchange-user-dialog.c				\
-	exchange-user-dialog.h				\
-	exchange-folder-size-display.c			\
-	exchange-folder-size-display.h			\
-	exchange-account-setup.c			\
-	exchange-permissions-dialog.c			\
-	exchange-permissions-dialog.h			\
-	exchange-folder-permission.c			\
-	exchange-folder-subscription.c			\
-	exchange-folder-subscription.h			\
-	exchange-folder.c				\
-	exchange-mail-send-options.c			\
-	exchange-send-options.c				\
+liborg_gnome_exchange_operations_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir)/server/lib \
+	-I$(top_srcdir)/server/storage \
+	-I$(top_builddir)/server/lib \
+	$(EVOLUTION_DATA_SERVER_CFLAGS) \
+	$(EVOLUTION_PLUGIN_CFLAGS) \
+	$(CAMEL_EXCHANGE_CFLAGS)
+
+liborg_gnome_exchange_operations_la_SOURCES = \
+	exchange-operations.c \
+	exchange-operations.h \
+	exchange-config-listener.c \
+	exchange-config-listener.h \
+	exchange-calendar.c \
+	exchange-contacts.c \
+	exchange-change-password.c \
+	exchange-change-password.h \
+	exchange-delegates-user.c \
+	exchange-delegates-user.h \
+	exchange-delegates.c \
+	exchange-delegates.h \
+	exchange-user-dialog.c \
+	exchange-user-dialog.h \
+	exchange-folder-size-display.c \
+	exchange-folder-size-display.h \
+	exchange-account-setup.c \
+	exchange-permissions-dialog.c \
+	exchange-permissions-dialog.h \
+	exchange-folder-permission.c \
+	exchange-folder-subscription.c \
+	exchange-folder-subscription.h \
+	exchange-folder.c \
+	exchange-mail-send-options.c \
+	exchange-send-options.c \
 	exchange-send-options.h
 
-liborg_gnome_exchange_operations_la_CPPFLAGS =	\
-	$(AM_CPPFLAGS)				\
-	-I .					\
-	$(EVOLUTION_DATA_SERVER_CFLAGS)		\
-	$(EVOLUTION_PLUGIN_CFLAGS)		\
-	$(CAMEL_EXCHANGE_CFLAGS)		\
-	$(EVOLUTION_MAIL_CFLAGS)
+liborg_gnome_exchange_operations_la_LIBADD = \
+	$(EVOLUTION_DATA_SERVER_LIBS) \
+	$(EVOLUTION_PLUGIN_LIBS) \
+	$(CAMEL_EXCHANGE_LIBS)
 
-liborg_gnome_exchange_operations_la_LIBADD =	\
-	$(EVOLUTION_DATA_SERVER_LIBS)		\
-	$(EVOLUTION_PLUGIN_LIBS)		\
-	$(CAMEL_EXCHANGE_LIBS)			\
-	$(EVOLUTION_MAIL_LIBS)
-
-liborg_gnome_exchange_operations_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED)
+liborg_gnome_exchange_operations_la_LDFLAGS = \
+	-module -avoid-version $(NO_UNDEFINED)
 
 error_DATA = org-gnome-exchange-operations.error
 errordir = $(EVOLUTION_PLUGIN_errordir)
 
-EXTRA_DIST = 							\
-	org-gnome-exchange-operations.eplug.xml			\
+EXTRA_DIST = \
+	org-gnome-exchange-operations.eplug.xml \
 	org-gnome-exchange-operations.error.xml
 
 BUILT_SOURCES = $(error_DATA) org-gnome-exchange-operations.eplug 
diff --git a/eplugin/org-gnome-exchange-operations.eplug b/eplugin/org-gnome-exchange-operations.eplug
index a1a9485..49c0fab 100644
--- a/eplugin/org-gnome-exchange-operations.eplug
+++ b/eplugin/org-gnome-exchange-operations.eplug
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <e-plugin-list>
-  <e-plugin type="shlib" system_plugin="true" location="/build/local/lib/evolution/2.30/plugins/liborg-gnome-exchange-operations" load-on-startup="true" id="org.gnome.evolution.plugin.exchange-operations" name="Exchange Operations">
+  <e-plugin type="shlib" system_plugin="true" location="@PLUGINDIR@/liborg-gnome-exchange-operations SOEXT@" load-on-startup="true" id="org.gnome.evolution.plugin.exchange-operations" name="Exchange Operations">
     <author name="Sushma Rai" email="rsushma novell com"/>
     <author name="Praveen Kumar" email="kpraveen novell com"/>
     <author name="Shakti Sen" email="shprasad novell com"/>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d9a9de1..6121ea5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -12,6 +12,28 @@ camel/camel-exchange-store.c
 camel/camel-exchange-transport.c
 camel/camel-exchange-utils.c
 camel/mail-utils.c
+eplugin/exchange-account-setup.c
+eplugin/exchange-calendar.c
+eplugin/exchange-change-password.c
+eplugin/exchange-config-listener.c
+eplugin/exchange-contacts.c
+eplugin/exchange-delegates-user.c
+eplugin/exchange-delegates.c
+eplugin/exchange-folder-permission.c
+eplugin/exchange-folder-size-display.c
+eplugin/exchange-folder-subscription.c
+eplugin/exchange-folder.c
+eplugin/exchange-operations.c
+eplugin/exchange-permissions-dialog.c
+eplugin/exchange-send-options.c
+eplugin/exchange-user-dialog.c
+eplugin/org-gnome-exchange-operations.eplug.xml
+eplugin/org-gnome-exchange-operations.error.xml
+server/lib/e2k-autoconfig.c
+server/lib/e2k-security-descriptor.c
+server/storage/e-storage.c
+server/storage/exchange-account.c
+server/storage/exchange-hierarchy-foreign.c
 tools/exchange-autoconfig-wizard.c
 tools/exchange-share-config-listener.c
 tools/ximian-connector-setup.c
diff --git a/server/ChangeLog b/server/ChangeLog
new file mode 100644
index 0000000..79c6b40
--- /dev/null
+++ b/server/ChangeLog
@@ -0,0 +1,1262 @@
+2009-04-24  Milan Crha  <mcrha redhat com>
+
+	** Part of fix for bug #569652
+
+	* storage/exchange-esource.c: (find_account_group),
+	(add_folder_esource): Do not use deprecated
+	e_source_list_peek_group_by_name and add
+	account's uid as a group property.
+
+2009-04-08  Milan Crha  <mcrha redhat com>
+
+	* storage/exchange-esource.c: (add_folder_esource):
+	Do not disable alarm notifications for foreign calendars fully,
+	only do not notify for them by default.
+
+2009-02-26  Matthew Barnes  <mbarnes redhat com>
+
+	** Fixes part of bug #572950
+
+	* lib/Makefile.am:
+	* storage/Makefile.am:
+	Fix ordering of -I compiler directives.  Patch by Daniel Macks.
+
+2009-02-10  Matthew Barnes  <mbarnes redhat com>
+
+	** Fixes bug #570653
+
+	* storage/exchange-hierarchy-foreign.c:
+	Kill n_std_folders and use G_N_ELEMENTS (std_folders) instead.
+
+2009-02-09  Milan Crha  <mcrha redhat com>
+
+	** Part of fix for bug #555888
+
+	* lib/e2k-context.c: (proxy_settings_changed), (e2k_context_set_auth):
+	EProxy API changed, use e_proxy_peek_uri_for now.
+
+2009-01-14  Matthew Barnes  <mbarnes redhat com>
+
+	** Fixes part of bug #564543
+
+	* lib/e2k-context.c (e2k_context_set_auth):
+	Call soup_session_add_feature() instead of soup_logger_attach().
+
+	* lib/e2k-context.c (e2k_context_fba):
+	Call soup_form_encode_hash() instead of soup_form_encode_urlencoded().
+
+2009-01-12  Chow Loong Jin  <hyperair gmail com>
+
+	** Fix for bug #518920
+
+	* servers/exchange/lib/e2k-context.c (e2k_context_fba):
+	Handle relative URIs in value of action attribute of form in 
+	form-based authentication.
+
+2008-12-02  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #562228
+
+	* lib/e2k-autoconfig.c: (validate): Always store correct owa_path when
+	validating, without trailing slash and without mailbox name. 
+
+2008-10-19  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #553944
+
+	* lib/e2k-context.c: (unsubscribe_internal), (destroy_sub_list),
+	(e2k_context_unsubscribe): Do not unsubscribe from the server when
+	destroying context and make copy of the uri, because it comes from
+	the structure we are going to free.
+
+2008-10-19  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #544141
+
+	* storage/exchange-account.c: (set_sf_prop): Do not store invalid
+	values in 'standard_uris', it leads to crash later.
+
+2008-09-25  Milan Crha  <mcrha redhat com>
+
+	* storage/exchange-account.c: Removed unused header.
+
+2008-08-18  Milan Crha  <mcrha redhat com>
+
+	** Part of fix for bug #324203
+
+	* storage/exchange-account.h: (exchange_account_get_account_uri_param):
+	* storage/exchange-account.c: (exchange_account_get_account_uri_param):
+	New function to retrieve account's source url params easily.
+	* storage/exchange-esource.c: (add_folder_esource):
+	Update GAL source's "can-browse" property properly.
+
+2008-08-14  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #337503
+
+	* storage/exchange-hierarchy-webdav.c: (xfer_folder):
+	Do not unref new folder, it's owned by the hierarchy.
+	* storage/exchange-account.c: (struct _ExchangeAccountPrivate), (init),
+	(dispose), (exchange_account_rescan_tree), (hierarchy_new_folder),
+	(exchange_account_get_folders), (exchange_account_get_folder_tree):
+	Removed unused variable and related code.
+	* storage/exchange-account.c: (hierarchy_removed_folder):
+	Do not unref more times than we actually reffed it before.
+
+2008-08-12  Milan Crha  <mcrha redhat com>
+
+	** Part of fix for bug #547308
+
+	* lib/e2k-global-catalog.c: (connect_ldap): Better warning on console.
+	* lib/e2k-global-catalog-ldap.h: (e2k_global_catalog_get_ldap):
+	* lib/e2k-global-catalog.c: (e2k_global_catalog_get_ldap):
+	Return also ldap error if requested.
+
+2008-07-30  Milan Crha  <mcrha redhat com>
+
+	* lib/e2k-global-catalog.c: (connect_ldap):
+	Removed debug printf added in the last commit.
+
+2008-07-30  Milan Crha  <mcrha redhat com>
+
+	** Part of fix for bug #500389
+
+	* lib/actest.c: (test_main):
+	* lib/test-utils.c: (test_get_gc):
+	* lib/e2k-validate.h: (enum E2kAutoconfigGalAuthPref),
+	(struct ExchangeParams): New enum to setup GAL authentication method.
+	* storage/exchange-account.c: (struct _ExchangeAccountPrivate),
+	(exchange_account_connect), (exchange_account_new):
+	* lib/e2k-autoconfig.c: (e2k_autoconfig_new),
+	(e2k_autoconfig_set_owa_uri), (e2k_autoconfig_set_gc_server),
+	(e2k_autoconfig_get_global_catalog), (set_account_uri_string):
+	* lib/e2k-autoconfig.h: (struct E2kAutoconfig),
+	(e2k_autoconfig_set_gc_server):
+	* lib/e2k-global-catalog.h: (e2k_global_catalog_new):
+	* lib/e2k-global-catalog.c: (struct _E2kGlobalCatalogPrivate),
+	(connect_ldap), (e2k_global_catalog_new):
+	New ability to set different authentication type to GAL and OWA.
+
+	* lib/e2k-context.c: (e2k_soup_message_new_full):
+	Do not crash on invalid uri.
+
+2008-06-16  Milan Crha  <mcrha redhat com>
+
+	** Part of fix for bug #273627
+
+	* lib/e2k-autoconfig.c: (validate):
+	Use mailbox value from ExchangeParams structure if set, otherwise
+	extract mailbox from the home_uri as before.
+
+2008-05-26  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #473658
+
+	* lib/e2k-properties.c: (copy_prop), (properties_free_cb),
+	(get_propinfo), (foreach_callback), (foreach_namespace_callback),
+	(e2k_prop_namespace_name), (e2k_prop_namespace_abbrev): Added locks
+	to guard global variables 'known_properties' and 'namespaces'.
+
+2008-05-02  Bharath Acharya  <abharath novell com>
+
+	** Fix for bug #530763
+
+	* lib/e2k-context.c: (e2k_context_transfer_start):
+	* lib/e2k-utils.c: (e2k_strdup_with_trailing_slash): Return NULL if the
+	value of dest_folder is disturbed to prevent the crash.
+
+2008-04-29  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #502899
+
+	* storage/exchange-account.c: (struct _ExchangeAccountPrivate), (init),
+	(dispose), (finalize), (exchange_account_rescan_tree), (get_folder),
+	(hierarchy_new_folder), (hierarchy_removed_folder), (context_redirect),
+	(get_parent_and_name), (exchange_account_get_folder),
+	(exchange_account_get_folders), (exchange_account_get_folder_tree):
+	Guard private folder's hash tables with a lock to prevent access to
+	them from different threads in same the time.
+
+2008-03-27  Matthew Barnes  <mbarnes redhat com>
+
+	** Fixes bug #500389
+
+	* lib/e2k-global-catalog.c (connect_ldap):
+	Fall back to simple binds if the global catalog server
+	does not support NTLM binds.
+
+2008-03-25  Veerapuram Varadhan  <vvaradhan novell com>
+
+	** Added configurable Proxy settings for Evolution.
+
+	* lib/e2k-context.c: (proxy_settings_changed), (init), (dispose),
+	(e2k_context_set_auth):
+
+2008-03-24  Matthew Barnes  <mbarnes redhat com>
+
+	** Fixes bug #523023
+
+	* storage/exchange-hierarchy-webdav.c (scan_subtree):
+	Fix a severe EFolder reference count leak.
+
+2008-02-25  Matthew Barnes  <mbarnes redhat com>
+
+	* lib/e2k-kerberos.c (get_init_cred):
+	Fix a couple discarded const warnings.
+
+2008-02-12  Matthew Barnes  <mbarnes redhat com>
+
+	* storage/exchange-account.c:
+	Fix another G_GNUC_PRETTY_FUNCTION straggler.
+
+2008-02-06  Matthew Barnes  <mbarnes redhat com>
+
+	** Fixes part of bug #514682
+
+	* lib/Makefile.am:
+	* storage/Makefile.am:
+	Fix a compilation error that occurs when building in a
+	remote object directory (#514682, patch by Paul Smith).
+
+2008-02-04  Dan Winship  <danw gnome org>
+
+	* lib/e2k-context.c (session_authenticate): Only authenticate the
+	auth once; if we're called again, let it fail, since the cached
+	password must be incorrect. #513646.
+
+2008-01-30  Milan Crha  <mcrha redhat com>
+
+	** Part of fix for bug #395939
+
+	* lib/e2k-context.c: (dispose): Memory leak fix.
+
+2008-01-27  Dan Winship  <danw gnome org>
+
+	* lib/e2k-context.c (e2k_context_fba): Fix a double free. #511301.
+
+2008-01-24  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #511235
+
+	* lib/e2k-context.c: (session_authenticate):
+	Changed prototype to reflect actual libsoup.
+	Thanks to Dan Winship for pointing this out.
+
+2008-01-17  Kjartan Maraas  <kmaraas gnome org>
+
+	* lib/e2k-context.h: Correct the function signature of
+	e2k_soup_message_new_full() so things build.
+
+2008-01-15  Dan Winship  <danw gnome org>
+
+	* lib/e2k-autoconfig.c: Update for libsoup 2.4
+
+	* lib/e2k-context.c: Update for libsoup 2.4
+	(e2k_context_fba): Use soup-forms methods.
+	(setup_message): Rewrite the old SoupMessageFilter-based stuff to
+	use SoupSession::request-started instead.
+	(e2k_context_get, e2k_context_get_owa): Return a SoupBuffer
+	directly rather than returning a char * and length.
+
+	* lib/e2k-http-utils.c (e2k_http_get_headers): kludge to get
+	around the auto-header-merging behavior of SoupMessageHeaders,
+	which makes it hard to parse WWW-Authenticate and Set-Cookie.
+
+	* storage/exchange-oof.c (find_str_case, exchange_oof_get):
+	Update for e2k_context_get_owa() change. Rewrite to not modify the
+	response body, to avoid needing an extra strdup.
+
+2008-01-12  Srinivasa Ragavan  <sragavan novell com>
+
+	** Fix for bug #361972
+
+	* lib/e2k-context.c: (e2k_context_fba): No point crashing if the
+	action is not recognised by soup. So if the message is null return.
+
+2007-12-31  Sushma Rai  <rsushma novell com>
+
+	** Fixes bug #327965
+
+	* storage/exchange-account.[ch] (exchange_account_get_windows_domain):
+	Added new, to return the windows domain value.
+
+	* storage/exchange-esource.c (add_folder_esource): Setting the e-source
+	property username with the domain, if the domain is specified. 
+	
+2007-12-06  Tobias Mueller <tobiasmue svn gnome org>
+	Patch by Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #462593
+
+	* lib/e2k-context.c: (e2k_soup_message_new): Print message on console
+	if invalid uri has been passed to soup_message_new.
+	* lib/e2k-autoconfig.c: (e2k_autoconfig_get_context):
+	Prevent from crash on invalid uri.
+
+2007-11-15  Matthew Barnes  <mbarnes redhat com>
+
+	* lib/e2k-autoconfig.c (get_ctx_auth_handler):
+	* lib/e2k-result.c (prop_get_binary_array), (prop_get_binary):
+	Initialize 'length' before calling g_base64_decode().
+
+2007-10-27  Matthew Barnes  <mbarnes redhat com>
+
+	* lib/e2k-user-dialog.c:
+	* lib/e2k-user-dialog.h:
+	Remove these dead files from SCM.
+
+2007-10-03  Srinivasa Ragavan  <sragavan novell com>
+
+	** Fix for BNC bug #203480
+
+	* storage/exchange-account.c: Compiler warning for usage of
+	unintialized variable.
+
+2007-09-28  Matthew Barnes  <mbarnes redhat com>
+
+	* lib/e2k-context.c (write_prop):
+	* lib/e2k-autoconfig.c (get_ctx_auth_handler):
+	* lib/e2k-result.c (prop_get_binary_array), (prop_get_string_array):
+	Use GLib's Base64 API instead of e2k_base64_encode() and
+	e2k_base64_decode().  Straggler from bug #474000.
+
+2007-09-28  Matthew Barnes  <mbarnes redhat com>
+
+	* storage/exchange-account.c:
+	* lib/e2k-context.c:
+	* lib/e2k-autoconfig.c:
+	* lib/e2k-result.c:
+	Remove #include "e2k-encoding-utils.h"
+
+2007-09-27  Matthew Barnes  <mbarnes redhat com>
+
+	** Fixes part of bug #474000
+
+	* lib/Makefile.am:
+	* lib/e2k-encoding-utils.c:
+	* lib/e2k-encoding-utils.h:
+	Remove redundant Base64 codec implementation.
+
+2007-09-03  Veerapuram Varadhan  <vvaradhan novell com>
+
+	* storage/exchange-hierarchy-webdav.c: Check for validity of
+	deleted_items_uri before using it.  Fixes a crash that happens
+	when started in calendar mode.  Have seen some BGO traces showing
+	it - but do not have the bug-id atm.  Will close them later.
+	
+2007-09-03  Veerapuram Varadhan  <vvaradhan novell com>
+
+	** Fixes #290330 (bnc)
+	* storage/exchange-hierarchy-webdav.c: Fetch FOLDER_CLASS and
+	PERMANENT_URL properties for public folders as well.
+	
+2007-08-29  Chenthill Palanisamy  <pchenthill novell com>
+
+	Fixes #301263 (bnc)
+	* storage/exchange-account.[ch]: Added a new function
+	to scan foriegn folder hierarchy. 
+	* storage/exchange-esource.c: (add_folder_esource):
+	Set the foriegn folder property.
+
+2007-08-24  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #381548 from John D. Ramsdell
+
+	* lib/e2k-context.c: (e2k_context_fba):
+	Make fail with zero-length action string like with NULL.
+
+2007-08-10  Milan Crha  <mcrha redhat com>
+
+	** Fix for bug #327977
+
+	* lib/e2k-validate.h: (e2k_validate_user):
+	* lib/e2k-autoconfig.c: (e2k_validate_user):
+	Added parameter for parent window to pass to password dialog.
+
+2007-08-03  Jeff Cai<jeff cai sun com>
+
+	* storage/exchange-account.c:
+	Not print debug information.
+	Fix #439147
+
+2007-07-09  Chenthill Palanisamy  <pchenthill novell com>
+
+	* storage/exchange-account.c:
+	* storage/exchange-account.h: (exchange_account_fetch): Fetch
+	the EAaccount from the exchange account.								
+	* storage/exchange-esource.c: (add_folder_esource): Added the subscriber's
+	email id to corresponding esource.							    
+	
+	Committing on behalf of Suman Manjunath <msuman novell com> and 
+	Bharath Acharya <abharath novell com>	
+
+2007-07-03  Srinivasa Ragavan  <sragavan novell com>
+
+	
+	* lib/e2k-autoconfig.c: (e2k_validate_user): Have the full url to
+	avoid asking the password again.
+	* lib/e2k-validate.h:
+
+2007-06-15  Matthew Barnes  <mbarnes redhat com>
+
+	* storage/exchange-hierarchy-webdav.c (xfer_folder):
+	Duplicate the string returned from e_folder_get_physical_uri()
+	(#312854).
+
+2007-06-04  Veerapuram Varadhan  <vvaradhan novell com>
+
+	** Fixes #415922
+	* lib/e2k-autoconfig.c (e2k_autoconfig_get_context): Add support
+	for Microsoft ISA Server 2004 proxy URLs.
+	* lib/e2k-context.c (e2k_context_fba): Decode the URI - just incase.
+	
+2007-05-15  Ross Burton  <ross openedhand com>
+
+	* lib/e2k-user-dialog.c:
+	Remove bonobo includes, they are not used.
+
+2007-05-09  Srinivasa Ragavan  <sragavan novell com>
+
+	* lib/e2k-utils.c: Fix for build break.
+
+2007-05-07  Matthew Barnes  <mbarnes redhat com>
+
+	* lib/e2k-rule-xml.c:
+	* lib/e2k-rule.h:
+	* lib/e2k-security-descriptor.c:
+	* lib/e2k-utils.c:
+	* storage/e-folder-exchange.c:
+	Fix warnings reported by 'sparse'.  Patch from Kjartan Maraas.
+
+2007-04-05  Matthew Barnes  <mbarnes redhat com>
+
+	* lib/e2k-marshal.list:
+	Turns out, evolution-exchange still needs NONE:INT,INT.
+
+2007-04-05  Ross Burton  <ross openedhand com>
+
+	* storage/e-folder-exchange.c:
+	Use g_mkdir_with_parents (#383686).
+
+2007-04-04  Ross Burton  <ross openedhand com>
+
+	* storage/exchange-hierarchy.c:
+	* storage/e-shell-marshal.list:
+	* storage/exchange-account.c:
+	* storage/e-storage.c:
+	* storage/exchange-hierarchy-somedav.c:
+	* storage/e-folder.c:
+	* storage/Makefile.am:
+	* lib/e2k-marshal.list:
+	Remove marshallers that are in GLib already (#400970).
+
+2007-04-01  Matthew Barnes  <mbarnes redhat com>
+
+	** Various code clean-ups from Kjartan Maraas.
+
+	* storage/exchange-account.h (exchange_account_set_password):
+	Function declaration should not be subject to conditional compilation.
+
+	* storage/e-folder-type-registry.c
+	(e_folder_type_registry_get_display_name_for_type),
+	(e_folder_type_registry_get_description_for_type):
+	Return NULL, not FALSE.
+
+	* lib/e2k-rule-xml.c: Declare private arrays as static.
+
+	* lib/e2k-context.c (e2k_context_new), (e2k_debug_print_request),
+	(e2k_debug_print_response): Use NULL instead of zero (0).
+
+	* lib/e2k-context.c (do_notification):
+	g_io_channel_read_chars() returns GIOStatus, not GIOError.
+
+	* lib/e2k-autoconfig.c (e2k_autoconfig_check_exchange):
+	Fix a compiler warning.
+
+2007-03-26  Matthew Barnes  <mbarnes redhat com>
+
+	* storage/e-folder-exchange.c:
+	Don't mix declarations and code (#405495).
+	Patch from Jens Granseuer.
+
+2007-03-16  Matthew Barnes  <mbarnes redhat com>
+
+	** Fixes part of bug #360240
+
+	* storage/exchange-hierarchy-webdav.c (scan_subtree):
+	* storage/exchange-account.c (add_folder_tree):
+	* lib/e2k-uri.c (e2k_uri_new):
+	Remove unused variables.
+
+2007-01-08  Chenthill Palanisamy  <pchenthill novell com>
+	
+	* storage/exchange-account.[ch]: (exchange_account_get_hierarchy_by_email):
+	Added a new API to get the foreign user's folder hierarchy.
+	(exchange_account_get_hierarchy): Changed the name to exchange_account_get_hierarchy_by_type.
+
+2006-12-19  Jeff Cai <jeff cai sun com>
+
+	** Fix for 387397
+
+	* storage/exchange-account.c: Change macro definitions to 
+	satisfy both linux and Solaris.
+
+2006-12-18  Veerapuram Varadhan  <vvaradhan novell com>
+
+	* storage/e-folder-exchange.c: (init): Initialize rescan_tree to
+	TRUE
+	
+2006-12-18  Veerapuram Varadhan  <vvaradhan novell com>
+
+	** Missed changes
+
+	* storage/exchange-types.h: Moved ExchangeHierarchyTypes enum
+	here.
+	
+2006-12-18  Veerapuram Varadhan  <vvaradhan novell com>
+
+	** Fix for 346728, 268412
+
+	* storage/exchange-hierarchy.h: Move ExchangeHierarchyTypes to
+	exchange-types.h
+	
+	* storage/exchange-hierarchy-webdav.c: (scan_subtree):  Do not
+	rescan a tree/folder when its rescan_tree is set to FALSE.  Also,
+	just fetch DISPLAY_NAME and HAS_SUBS properties for public
+	folders.
+	
+	* storage/exchange-account.[ch]: (exchange_account_get_hierarchy):
+	Added to return hierarchy for a given type.
+	(exchange_account_get_folder_tree): Returns a tree of folders
+	for the requested URI/PATH.
+
+	* storage/e-folder-exchange.[ch]:
+	(e_folder_exchange_get_rescan_tree): Returns rescan_tree flag.
+	(e_folder_exchange_set_rescan_tree): Sets rescan_tree flag.
+	
+2006-12-13  Veerapuram Varadhan  <vvaradhan novell com>
+
+	** Fix for bnc #208395
+
+	* storage/exchange-hierarchy-webdav.c: (e_folder_webdav_new): 
+	* storage/e-folder-exchange.c: (e_folder_exchange_new):
+	Folder name can contain any characters including URI special
+	characters, encode it and use it as physical uri.
+	
+2006-11-15  Chenthill Palanisamy  <pchenthill novell com>
+
+	* storage/exchange-account.c: 
+	(exchange_account_connect): Check if the mode is unsupported
+	and reset the connecting variable.
+	Fixes #219729 (bugzilla.novell.com)
+
+2006-11-07  Chenthill Palanisamy  <pchenthill novell com>
+
+	* storage/exchange-constants.h: Define a flag to
+	indicate the foriegn folder.
+	* storage/exchange-esource.c: (add_folder_esource):
+	Disable alarms for foriegn folders.
+	* storage/exchange-hierarchy-foreign.c:
+	(exchange_hierarchy_foreign_add_folder):
+	Pass the masked value for the folder type.
+	Fixes #208318 (bugzilla.novell.com)
+
+2006-11-07  Chenthill Palanisamy  <pchenthill novell com>
+
+	Fixes the removal of properties.
+	* lib/e2k-context.c: (write_prop): Do not check for the 
+	existance of the value while removing the properties.
+	Fixes #207960 (buzilla.novell.com)
+
+2006-10-12  Srinivasa Ragavan  <sragavan novell com>
+
+	** Fix for #bug 347811
+
+	* storage/exchange-account.c: Reverting Varadhan's last commit to
+	lookup hierarchies based on offline settings, and makeing that as
+	FALSE. Reopening bug #268412
+
+2006-09-30  Veerapuram Varadhan <vvaradhan novell com>
+
+	** Fixes #347811
+	* storage/exchange-account.c (setup_account_hierarchies): While
+	creating hierarchies for the folders, set offline_supported with
+	the account level settings.
+	
+2006-07-24  Veerapuram Varadhan <vvaradhan novell com>
+
+	* lib/e2k-context.c (e2k_context_set_auth): Create SoupSessionSync
+	with a default timeout of 30 secs.
+	
+2006-07-24  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.h: Added two more error codes 
+	ExchangeAccountFolderResult.
+
+	* storage/exchange-account.c (exchange_account_discover_shared_folder):
+	Return proper error codes when GC server is NULL or invalid username
+	is selected. Fixes #234359.
+
+2006-07-24  Sushma Rai  <rsushma novell com>
+
+	* storage/Makefile.am: Added exchange-esource.h to 
+	libexchange_storageinclude_HEADERS. See #313081.
+
+2006-07-22  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.[ch]: Added a new API to read the e-mail id
+	from the given ExchangeAccount. Fixes #311322.
+	Patch submitted by "Vandana Shenoy .B <shvandana novell com>"
+
+2006-05-11  Chenthill Palanisamy  <pchenthill novell com>
+
+	Fixes #334626
+	* storage/exchange-esource.c: 
+	(add_folder_esource): set the list of calendar selections
+	in gconf for the newly created source.
+
+2006-05-10  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.c (setup_account_hierarchies): Setting
+	parameter "deep_searchable" to TRUE for favorite folders, so that we
+	scan for the sub folders if a public folder has sub folders.
+
+	* storage/exchange-hierarchy-favorites.c
+	(exchange_hierarchy_favorites_new): Similar.
+	Fixes #268412.
+
+2006-05-10  Sushma Rai  <rsushma novell com>
+
+	*  exchange/lib/e2k-autoconfig.c (e2k_validate_user): Set result to
+	E2K_AUTOCONFIG_CANCELLED on cancel. Fixes #332131.
+
+2006-04-19  Harish Krishnaswamy  <kharish novell com>
+
+	Patch suggested by Carlos Lozano <clozano at andago dot com>
+
+	* storage/exchange-account.c (is_password_expired),
+	(exchange_account_set_password): Ensure checks for expiry or
+	weakness take effect while trying for nt domain. The conditionals
+	should be AND'ed not OR'ed. 
+
+2006-04-18  Sushma Rai  <rsushma novell com>
+
+	* lib/e2k-uri.c (e2k_uri_new): Reverting the changes made for extracting
+	the user name and domain names from user name provided, which is in the
+	form of email id. Now we are doung it during account creation itself.
+	
+	* lib/e2k-validate.h: Changed the signature of e2k_validate_user().
+
+	* lib/e2k-autoconfig.c (e2k_validate_user): Authenticating with the 
+	username provided, and if it fails and username is of the form 
+	user domain, extracting the username from it and trying once more.
+	Fixes #329371.
+
+2006-04-06  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-hierarchy-webdav.c (xfer_folder): Removing the 
+	separator from the folder name.
+
+2006-03-06  Irene Huang <Irene Huang sun com>
+
+	Fixes bug #331633
+
+	* lib/e2k-global-catalog.c: (find_domain_dn): Check and see if 
+	dn_value->str[0] is nil before duplicating.
+
+2006-03-06  Sushma Rai  <rsushma novell com>
+
+	* storage/e-folder-exchange.c (e_folder_exchange_new_from_file): 
+	Freeing xml doc and xml property. See #329251.
+
+2006-03-06  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-oof.c (exchange_oof_get): Initialize variables.
+	
+	* lib/e2k-context.c (e2k_context_fba): Similar. See #329251. 
+
+2006-03-06  Sushma Rai  <rsushma novell com>
+
+	* lib/e2k-autoconfig.c (e2k_autoconfig_get_context): Freeing old value
+	of home_uri beofre storing the new value. 
+	(e2k_autoconfig_check_exchange): Freeing xml property. See #329251.
+
+	* storage/exchange-account.c (exchange_account_connect): Not 
+	duplicating password string. See #329251.
+
+2006-03-06  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-esource.c (is_offline): Freeing GconfValue,
+	See #329251.
+ 
+2006-03-06  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.c (exchange_account_connect): Skip the quota
+	limit warning display if the GC server is missing. If the quota limits 
+	are set on the account, and GC server field is missing, 
+	sending/receiving mails would fail, based on quota settings. 
+	Fixes #333163.
+
+2006-02-25  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-hierarchy-webdav.c (scan_subtree): Do not unref the
+	folder which is being used later in subtrees.
+
+2006-02-13  Chenthill Palanisamy  <pchenthill novell com>
+
+	* storage/exchange-hierarchy-webdav.c: (init),
+	(hierarchy_new_folder): Ref the folder before inserting so that it 
+	doesn't die on before removing. Fixes #326413.
+
+2006-02-10  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-esource.c (add_folder_esource): Calling 
+	e_source_sync() only when we set the property.
+
+2006-02-06  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.c (exchange_account_connect): Freeing 
+	E2kResult.
+
+	* storage/e-folder-exchange.c (e_folder_exchange_save_to_file): 
+	Returning on NULL uris or folder name, before finding the folder size. 
+	Also freeing folder size string.
+
+	* storage/exchange-hierarchy-favorites.c (get_hrefs): Initializing 
+	nresults to zero.
+
+	* storage/exchange-oof.c (exchange_oof_get):  Similar.	
+	
+	* lib/e2k-freebusy.c (e2k_freebusy_new): Similar.
+
+	* storage/exchange-hierarchy-foreign.c (check_hide_private)
+	(find_folder): Initializing nresults to zero and Freeing E2kResult.
+
+	* storage/exchange-hierarchy-somedav.c 
+	(exchange_hierarchy_somedav_add_folder): Similar.
+
+	* storage/exchange-hierarchy-webdav.c (scan_subtree): Unrefing folder.
+
+	* storage/exchange-esource.c (add_folder_esource): Freeing authtype.
+	Fixes #329251.
+
+2006-01-31  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.c (hierarchy_new_folder): Removed the debug
+	messages. Fixes #327428
+
+2006-01-27  Sushma Rai  <rsushma novell com>
+
+	* lib/e2k-uri.c (e2k_uri_new): Checking if the user name entered is 
+	e-mail id and extracting the user name and domain from it. 
+	Fixes #323637.
+
+2006-01-18  Sushma Rai  <rsushma novell com>
+
+	* lib/e2k-autoconfig.c (e2k_validate_user): Whenever the authenticate
+	button is pressed, always prompt for password. Fixes #327292. 
+
+2006-01-14  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-esource.c (add_folder_esource): Marking the GAL 
+	folder for autocompletion, while creating it. Fixes #303998.
+
+2006-01-14  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-constants.h: using API_VERSION for Exchange
+	Connector book and calendar factories. See #323115.
+
+2006-01-12  Sushma Rai  <rsushma novell com>
+
+	* lib/e2k-autoconfig.c (e2k_validate_user): Freeing password string.
+
+	* storage/exchange-account.c (display_passwd_expiry_message)
+	(change_passwd_cb): Removed these unused functions.
+	(find_passwd_exp_period): Retruning max_pwd_age_days or error instead of
+	invoking display_passwd_expiry_message().
+	(exchange_account_connect): Freeing E2KAutoconfig structure.
+	(exchange_account_check_password_expiry): Implemeted. Returns password validity
+	period. Fixes #326060.
+
+2006-01-10  Simon Zheng  <simon zheng sun com>
+
+	* lib/e2k-autoconfig.c:
+	* storage/e-folder-exchange.c:
+	* storage/e-folder.c:
+	* storage/e-storage.c:
+	As file e-util.h is renamed, replace "libedataserver/e-util.h"
+        as "libedataserver/e-data-server-util.h".
+
+2005-12-21  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-esource.c (add_folder_esource): Calling
+	e_source_list_sync() after updating the offline status of the existing 
+	source.
+	
+	* storage/exchange-account.h: Exposing exchange_account_get_authtype(),
+	which is now used from exchange-operations plugin.
+
+2005-12-19  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.[ch] (exchange_account_set_save_password):
+	Sets the flag in EAccount to TRUE if user has selected save password.
+	(exchange_account_is_save_password): Returns the save_passwd flag
+	value. Setting this value was missed in evolution 2.4
+
+2005-12-19  Sushma Rai  <rsushma novell com>
+
+	* lib/e2k-autoconfig.c (validate): Free E2kAutoconfig structure.
+	Fixes #324483.
+
+2005-12-17  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.c: Using the integer varable 
+	"account_online" to store the mode, instead of boolean. This fixes the
+	problem of comparing account_online value (TRUE/FALSE) with 
+	OFFLINE_MODE/ONLINE_MODE (1/2).
+	(exchange_account_rescan_tree)(exchange_account_open_folder)
+	(exchange_account_set_offline)(exchange_account_is_offline)
+	(setup_account_hierarchies)(exchange_account_connect): Similar. 
+
+	* storage/exchange-hierarchy.[ch] (scan_subtree)
+	(exchange_hierarchy_scan_subtree): Similar.
+	
+	* storage/exchange-hierarchy-webdav.c (xfer_folder)(rescan)
+	(scan_subtree): Similar.
+
+	* storage/exchange-hierarchy-foreign.c (scan_subtree): Similar.
+
+	* storage/exchange-hierarchy-somedav.c (scan_subtree): Similar.
+	This fixes the problem of not loading the folders after creating an
+	account, even if the user is authenticated. Fixes #322657.
+
+2005-12-17  Sushma Rai  <rsushma novell com>
+
+	* lib/e2k-autoconfig.c (e2k_validate_user): Corrected the key by
+	adding the trailing slash.
+
+	* storage/exchange-account.c (exchange_account_new): Using the proper
+	password key so that during account creation password will not be asked
+	for the second time at the end.  Fixes #322657.
+
+2005-12-15  Sushma Rai  <rsushma novell com>
+
+	* lib/e2k-autoconfig.c (e2k_validate_user): Fixes the problem
+	of validating and authenticating user only on pressing "Authenticate" 
+	button twice, even if the user gives the proper password.
+
+2005-12-11  Tor Lillqvist  <tml novell com>
+
+	* lib/Makefile.am: Drop unused CONNECTOR_LOCALEDIR.
+
+	* lib/e2k-autoconfig.c
+	* lib/e2k-context.c: Include appropriate headers on Win32. 
+
+	* lib/e2k-autoconfig.c: Drop inclusion of
+	libedataserver/e-account.h and libedataserver/e-account-list.h,
+	that API is not used here.
+	(find_global_catalog): Add Win32 implementation.
+	(find_olson_timezone): Use g_win32_getlocale() on Win32 instead of
+	the usually nonexistent LANG environment variable.
+	(read_config): Form path to connector.conf at run-time for the
+	benefit of Win32 freely chosen end-user install location. 
+
+	* lib/e2k-autoconfig.c
+	* lib/e2k-path.c
+	* storage/e-folder-exchange.c
+	* storage/exchange-hierarchy-foreign.c
+	* storage/exchange-hierarchy-webdav.c: Use gstdio wrappers.
+	
+	* lib/e2k-context.c: Define strtok_r() using strtok() on Windows,
+	where strtok() is MT-safe. Wrap socket API calls with simple
+	macros for Unix/Winsock portability. Use
+	g_io_channel_win32_new_socket() on Windows.
+
+	* lib/e2k-global-catalog-ldap.h: Include winldap.h on Windows. Add
+	some OpenLDAP macros that aren't present in winldap.h.
+
+	* lib/e2k-global-catalog.c: Remove duplicate inclusion of ldap.h,
+	it's already included by e2k-global-catalog-ldap.h
+	(finalize): g_free() works fine on NULL pointers.
+	(connect_ldap, get_ldap_connection): Rename ldap_connect() to
+	connect_ldap() to avoid clash with the ldap_connect() function in
+	the LDAP API.
+	(connect_ldap): Use WINNT authentication on Windows.
+
+	* lib/e2k-path.c (find_folders_recursive): 
+	* storage/exchange-account.c (setup_account_hierarchies): Use
+	g_dir* instead of dirent API for portability.
+
+	* storage/Makefile.am: Use NO_UNDEFINED (meaning -no-undefined on
+	Windows). Link with all needed libraries for the benefit of
+	-no-undefined.
+
+	* storage/e-folder-exchange.c (e_mkdir_hier) Remove duplicate
+	implementation, already have it as e_util_mkdir_hier() in
+	libedataserver/e-util.c.
+	(sanitize_path): Must return something in all cases, this function
+	is not void. Why did gcc let this through with just a warning?
+
+	* storage/e-folder-exchange.c
+	* storage/exchange-hierarchy-foreign.c: Use e_xml_parse_file()
+	instead of xmlParseFile(), as that doesn't take UTF-8 filenames on
+	Windows. Use e_xml_save_file() for the same reason.
+
+	* storage/e-folder-exchange.c (e_xml_get_child_by_name): Remove,
+	now in libedataserver/e-xml-utils.c.
+
+	* storage/exchange-account.c (exchange_account_set_password):
+	Do compile even if not HAVE_KRB5, but always return failure. Means
+	less ifdefs elsewhere.
+	(e_filename_make_safe): Remove this function which isn't used,
+	especially as there is an exact duplicate in evolution's
+	e-util/e-util.c. If it eventually is needed also somewhere in
+	e-d-s, move it to libedataserver instead.
+
+2005-12-12  Irene Huang <Irene Huang sun com>
+
+	* lib/e2k-autoconfig.c: (e2k_validate_user): If password
+	exists, used the remembered password to do the
+	authentification. If validation failed, forget password.
+
+2005-12-10  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.c (exchange_account_connect): set
+	account->priv->connecting flag to FALSE on NULL password.
+	Also removed unnecessary looping with try_password_again and also the
+	commented out code.
+
+2005-12-08  Tor Lillqvist  <tml novell com>
+
+	* storage/e-shell-marshal.list: Add NONE:INT.
+
+2005-12-07  Tor Lillqvist  <tml novell com>
+
+	* storage/Makefile.am: Link with libedatasererui's bootstrap
+	import library on Win32. Link also with libedataserver. Move
+	libexchange.la and libxntlm.la from LDFLAGS to LIBADD. Install
+	e-shell-marshal.h (for the benefit of evolution-exchange, which
+	used to generate its own copy, but having several copies of the
+	same file in different places is confusing).
+
+2005-11-26  Sushma Rai  <rsushma novell com>
+
+	* storage/exchange-account.c (exchange_account_remove_folder)
+	(get_password)(exchange_account_connect): Fix for compile time warnings.
+	(exchange_account_connect): Initialize the return value, info_result
+	before cheking the validity of ExchangeAccount. Fixes a crash.
+
+2005-11-25  Tor Lillqvist  <tml novell com>
+
+	* lib/e2k-uri.c (e2k_uri_new)
+	* storage/exchange-oof.c (find_str_case): Use
+	g_ascii_strncasecmp() instead of strncasecmp() for portability.
+
+2005-10-21  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/exchange-account.c (exchange_account_remove_folder) : Proceed
+	with deletion of folder only if the folder being removed is *not* a
+	standard folder. This fixes #312848.
+
+2005-10-20  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* lib : Add the test programs
+	* lib/Makefile.am : Add the entries to compile these.
+
+2005-09-30  Arunprakash  <arunp novell com>
+
+	* storage/exchange-account.c (setup_account_hierarchies) : Skips the
+	hierarchies creation if it is done.
+	** Fixes #312229.
+
+2005-08-26  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* lib/e2k-autoconfig.c (e2k_validate_user) : Fix the password key to
+	be consistent with the camel key.
+	* storage/e-folder-exchange.c (sanitize_path) : Strips the ';' in the
+	path.
+	* storage/exchange-account.c (get_hierarchy_for)
+	(setup_account_hierarchies) : Fix the physical uri to delimit the
+	folder path from the uri with a ';'
+	(exchange_account_new) : Fix the uri authority to be same as the camel
+	uri which would be later used in all components for creating the
+	password key.
+
+2005-09-05  Praveen Kumar  <kpraveen novell com>
+
+	** Fixes bug 314588.
+	
+	* lib/e2k-context.c (e2k_context_new) : Modified the constructor 
+	to return NULL if there is no host name in the SOUP URI.
+
+2005-09-14  Irene Huang  <Irene Huang sun com>
+
+	Fix for bug #316274
+
+	* storage/exchange-account.h: only declare exchange_account_set_
+	password function when the macro HAVE_KRB5 is defined.
+
+2005-08-25  Arunprakash  <arunp novell com>
+
+	* storage/exchange-account.c (init) : set the default linestatus
+	to offline.
+	(exchange_account_rescan_tree) : Use the proper linestatus value.
+	(exchange_account_set_offline) : Added lock before modifying
+	the account linestatus to complete the connection in progress.
+	(exchange_account_set_online) : Similar.
+	(exchange_account_is_offline) : Return the proper linestatus.
+
+2005-08-25  Arunprakash  <arunp novell com>
+
+	* storage/exchange-account.c (hierarchy_new_folder) : Removes
+	redundant computation.
+	
+2005-08-22  Not Zed  <NotZed Ximian com>
+
+	* storage/exchange-esource.c (is_offline): provide a proper c
+	prototype for this, () is pre-iso-c.
+
+2005-08-22  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/exchange-account.c (get_password) : Store the password
+	(exchange_account_connect) : Handle NULL password, and also move
+	the mutex to the end of connect.
+	* storage/exchange-esource.c (add_folder_esource) : Add the auth
+	properties to the esources.
+
+2005-08-17  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/exchange-account.c (exchange_account_get_folder): Lookup on
+	NULL data is wrong. Handle it earlier.
+	* storage/exchange-hierarchy-webdav.c (rescan) : Use 
+	E2K_PR_EXCHANGE_FOLDER_SIZE for getting the folder size.
+	(scan_subtree): Similar. Also, dont scan the deleted items subtree.
+
+2005-08-12  Praveen Kumar  <kpraveen novell com>
+
+	* storage/exchange-esource.c
+	(is_offline): Added new
+	(add_folder_esource): Modified to add the calendar and tasks to 
+	the selected list only if the account is online. This is a part
+	of the fix to the bug 237272.
+	
+2005-08-02  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/exchange-hierarchy-favorites.c (remove_folder) : Remove
+	the esources only after we have removed the folder from the server.
+	* storage/exchange-account.c (exchange_account_rescan_tree) : Add the
+	toplevel folder of the hierarchy in the fresh folder list manually
+	* storage/exchange-hierarchy-somedav.c (scan_subtree) : Temporary fix.
+	Allow a rescan for now.
+	* storage/exchange-esource.c (remove_folder_esource) : Handle the 
+	addressbook esource removal properly. We no longer use the absolute 
+	uri for addressbooks, except for GAL.
+
+2005-07-28  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/exchange-account.c (is_password_expired) : This should be
+	included only if kerberos is enabled in the configure options.
+
+2005-07-22  Praveen Kumar <kpraveen novell com>
+
+	* storage/exchange-esource.c (add_folder_esource) : Modified the way
+	of Exchange addressbook ESource URI handling to be the same way as
+	calendar ESource URI handling except for "gal://" protocol
+
+2005-07-21  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/Makefile.am : Add the separated esource handling code files.
+	* storage/exchange-esource.[ch] : Added new
+	* storage/e-folder-exchange.c : Reinclude the esource creation code.
+	* storage/exchange-account.c : Add a new fresh_folder list. This has
+	the latest list of folders available for the account, excluding the
+	folders that were deleted in the current session.
+	(exchange_account_rescan_tree) : Add a scan for fetching any new
+	folders apart from scanning the existing folder properties.
+	(exchange_account_get_folders): First check for the fresh_folder list
+	if available and get the list from that.
+	* storage/exchange-hierarchy-favorites.c : Reinclude the esource
+	removal code.
+	* storage/exchange-hierarchy-foreign.c : Similar
+	* storage/exchange-hierarchy-gal.c : Similar
+	* storage/exchange-hierarchy-webdav.c : Similar
+
+2005-07-15  Arunprakash <arunp novell com>
+
+	* storage/exchange-account.c (exchange_account_connect) :  Update the 
+	error value before looping. This fixes #310483.
+
+2005-07-14  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/Makefile.am : Use the version-info
+	* storage/exchange-account.c : Fix a warning
+
+2005-07-14  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/exchange-hierarchy-foreign.c (remove_folder) : The esource
+	removal should be moved to the eplugins code.
+
+2005-07-11  Sarfraaz Ahmed <asarfraaz novell com>
+
+	Initial patch submitted by Arun Prakash <arunp novell com>.
+	* storage/exchange-account.c : The private ExchangeAccount structure
+	now also stores the quota_limit which can be used by the plugins to
+	display.
+	(get_password) : Now returns the error status
+	(exchange_account_set_password) : Similar
+	(exchange_account_connect) : This now accepts a password for connecting
+	and also returns the appropriate error code so that the plugins can
+	print appropriate messages.
+	(exchange_account_get_quota_limit) : Newly added.
+	(exchange_account_check_password_expiry) : Newly added
+	* storage/exchange-account.h : Added a new ExchangeAccountResult enum
+	for returning the connection status. Also modified the appropriate
+	function declarations.
+
+2005-07-11  Shakti Sen <shprasad novell com>
+
+	* storage/Makefile.am: Included files exchange-hierarchy-foreign.c
+	and exchange-hierarchy-foreign.h
+	* storage/exchange-account.c: Added foreign hierarchy support.
+	* storage/exchange-hierarchy-foreign.[ch]: Added newly for foreign 
+	hierarchy support.
+
+2005-07-08  Praveen Kumar <kpraveen novell com>
+
+	* lib/Makefile.am : Added entry for ek-sid.h in the headers files to 
+	be installed
+	
+2005-07-08  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/Makefile.am : We should be using the CURRENT, REVISION and
+	AGE variables for the version-info of the exchange library.
+
+2005-07-07  Sarfraaz Ahmed <asarfraaz novell com>
+	
+	* storage/e-folder-exchange.c : Remove dead code
+	* storage/exchange-account.c : Make ExchangeFolderSize a member of
+	ExchangeAccount. Also removed some dead codE
+	(exchange_account_folder_size_add) (exchange_account_folder_size_remove)
+	(exchange_account_folder_size_rename)
+	(exchange_account_folder_size_get_model) : Added new
+	* storage/exchange-account.h : Similar
+	* storage/exchange-folder-size.c : Replaced E2K_MAKE_TYPE with 
+	G_DEFINE_TYPE and made necessary changes to init and class_init members
+	(format_size_func)(parent_destroyed)(exchange_folder_size_display) :
+	All moved to plugins.
+	* storage/exchange-folder-size.h : Similar
+	* storage/exchange-hierarchy-somedav.c : Fixed a warning
+	* storage/exchange-hierarchy-webdav.c : Removed ExchangeFolderSize as 
+	its member and updated the methods used to access it. We should now
+	query the ExchangeAccount object for FolderSize information updation
+	* storage/exchange-hierarchy-webdav.h : Removed dead code
+
+2005-07-01  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* lib/e2k-autoconfig.c (validate) : This should only return the
+	exception type and not print anything. The plugin will now print the
+	proper error message.
+	(e2k_validate_user) : Similar
+	* lib/e2k-autoconfig.h : Move E2kAutoconfigResult to e2k-validate.h
+	so that the plugins can use it.
+	* lib/e2k-validate.h : Moved E2kAutoconfigResult here.
+	* storage/e-folder-exchange.c (e_mkdir_hier) : Make use of 
+	g_build_filename instead of the deprecated g_concat_dir_and_file
+	* storage/e-folder-type-registry.c : Remove unwanted code.
+	* storage/e-folder.c : Similar
+	* storage/e-storage.c : Similar
+	* storage/exchange-folder-size.c : Similar
+
+2005-06-28  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/exchange-account.c (exchange_account_is_favorite_folder) :
+	Added new to check for favorites folder.
+	* storage/exchange-account.h : Similar
+	* storage/exchange-hierarchy-favorites.c 
+	(exchange_hierarchy_favorites_is_added) : The main implementation.
+	Newly added.
+	* storage/exchange-hierarchy-favorites.h : Similar
+
+2005-06-22  Sarfraaz Ahmed <asarfraaz novell com>
+	
+	* storage/Makefile.am : e-shell-marshall.list should be disted.
+	* lib/Makefile.am : mapi-properties should also be disted.
+
+2005-06-15  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/Makefile.am : Install exchange-hierarchy-webdav.h and
+	exchange-hierarchy-somedav.h
+
+2005-06-14  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/libexchange-storage.pc.in : Dont hardcode the libsoup
+	version.
+
+2005-06-12  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* lib/Makefile.am : Use ENABLE_KRB5 instead of HAVE_KRB5
+
+2005-06-12  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* lib/Makefile.am : Include e2k-global-catalog-ldap.h. Install
+	e2k-global-catalog-ldap.h and e2k-validate.h
+	* lib/e2k-global-catalog.c : Use e2k-global-catalog-ldap.h
+	* lib/e2k-global-catalog.h : Remove the use of ldap.h from here.
+	* storage/Makefile.am : Use KRB5_LDFLAGS instead of KRB5_LIBS
+	* lib/e2k-global-catalog-ldap.h : Added new
+	* lib/e2k-validate.h : Added new
+
+2005-06-12  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* lib/Makefile.am : Remove commented code and fix spacings
+	* storage/Makefile.am : Similar. Also added exchange-oof.[ch]
+	* lib/e2k-context.c : Use the proper VERSION definition name.
+
+2005-06-10  Sarfraaz Ahmed <asarfraaz novell com>
+
+	First movement of exchange server communication code into e-d-s HEAD.
+
+2005-06-07  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/Makefile.am : Removed references to config-listener and 
+	foreign-hierarchy 
+	* storage/e-folder-exchange.c : Removed references to e_source here.
+	* storage/e-storage.c : Removed from e-d-s and moved back to exchange.
+	* storage/exchange-account.c : Removed references to foreign hierarchy.
+	* storage/exchange-account.h : Added constants.h
+	* storage/exchange-constants.h : Avoided re-inclusion
+	* storage/exchange-hierarchy-favorites.c : Removed esource references.
+	* storage/exchange-hierarchy-gal.c : Removed esource references
+	* storage/exchange-hierarchy-webdav.c : Removed references to foreign 
+	hierarchy.
+	* storage/exchange-types.h : Similar
+
+2005-06-03  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* storage/e-shell-marshal.list : New file
+
+2005-06-02  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* libexchange-storage.pc.in : Moved it from exchange to 
+	exchange/storage
+	* lib/Makefile.am : Added a few more header files that had to be 
+	installed.
+	* storage/Makefile.am : Similar
+	* storage/e-folder-exchange.c : Merged the changes from HEAD.
+	* storage/e-folder.c : Added marshalling code.
+	* storage/e-storage.c : Similar
+	* storage/exchange-account.c (exchange_account_get_username): Added new
+	* storage/exchange-account.h : Similar
+	* storage/exchange-component.[ch] : Removed from Makefile.am. Should be
+	removing these files from the repository.
+	* storage/exchange-config-listener.[ch] : Merged the changed from HEAD.
+	* storage/exchange-hierarchy-favorites.c : Similar
+	* storage/exchange-hierarchy-foreign.c : Similar
+	* storage/exchange-hierarchy-gal.c : Similar
+	* storage/exchange-hierarchy-webdav.c : Similar
+	* storage/exchange-constants.h : Added a new file.
+
+2005-05-21  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* lib/Makefile.am : Install e2k-global-catalog.h and e2k-utils.h
+	Also added this new ChangeLog file
diff --git a/server/Makefile.am b/server/Makefile.am
new file mode 100644
index 0000000..0809bf2
--- /dev/null
+++ b/server/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = xntlm lib storage
+
+-include $(top_srcdir)/git.mk
diff --git a/server/docs/ChangeLog b/server/docs/ChangeLog
new file mode 100644
index 0000000..db68382
--- /dev/null
+++ b/server/docs/ChangeLog
@@ -0,0 +1,3 @@
+2005-08-19  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* openldap-ntlm.diff : Moved from evolutio-exchange
diff --git a/server/docs/openldap-ntlm.diff b/server/docs/openldap-ntlm.diff
new file mode 100644
index 0000000..1e52f99
--- /dev/null
+++ b/server/docs/openldap-ntlm.diff
@@ -0,0 +1,199 @@
+(Note that this patch is not useful on its own... it just adds some
+hooks to work with the LDAP authentication process at a lower level
+than the API otherwise allows. The code that calls these hooks and
+actually drives the NTLM authentication process is in
+lib/e2k-global-catalog.c, and the code that actually implements the
+NTLM algorithms is in xntlm/.)
+
+This is a patch against OpenLDAP 2.2.6. Apply with -p0
+
+
+--- include/ldap.h.orig	2004-01-01 13:16:28.000000000 -0500
++++ include/ldap.h	2004-07-14 11:58:49.000000000 -0400
+@@ -1753,5 +1753,26 @@
+ 	LDAPControl **cctrls ));
+ 
+ 
++/*
++ * hacks for NTLM
++ */
++#define LDAP_AUTH_NTLM_REQUEST	((ber_tag_t) 0x8aU)
++#define LDAP_AUTH_NTLM_RESPONSE	((ber_tag_t) 0x8bU)
++LDAP_F( int )
++ldap_ntlm_bind LDAP_P((
++	LDAP		*ld,
++	LDAP_CONST char	*dn,
++	ber_tag_t	tag,
++	struct berval	*cred,
++	LDAPControl	**sctrls,
++	LDAPControl	**cctrls,
++	int		*msgidp ));
++LDAP_F( int )
++ldap_parse_ntlm_bind_result LDAP_P((
++	LDAP		*ld,
++	LDAPMessage	*res,
++	struct berval	*challenge));
++
++
+ LDAP_END_DECL
+ #endif /* _LDAP_H */
+--- libraries/libldap/Makefile.in.orig	2004-01-01 13:16:29.000000000 -0500
++++ libraries/libldap/Makefile.in	2004-07-14 13:37:23.000000000 -0400
+@@ -20,7 +20,7 @@
+ SRCS	= bind.c open.c result.c error.c compare.c search.c \
+ 	controls.c messages.c references.c extended.c cyrus.c \
+ 	modify.c add.c modrdn.c delete.c abandon.c \
+-	sasl.c sbind.c kbind.c unbind.c cancel.c  \
++	sasl.c ntlm.c sbind.c kbind.c unbind.c cancel.c  \
+ 	filter.c free.c sort.c passwd.c whoami.c \
+ 	getdn.c getentry.c getattr.c getvalues.c addentry.c \
+ 	request.c os-ip.c url.c sortctrl.c vlvctrl.c \
+@@ -29,7 +29,7 @@
+ OBJS	= bind.lo open.lo result.lo error.lo compare.lo search.lo \
+ 	controls.lo messages.lo references.lo extended.lo cyrus.lo \
+ 	modify.lo add.lo modrdn.lo delete.lo abandon.lo \
+-	sasl.lo sbind.lo kbind.lo unbind.lo cancel.lo \
++	sasl.lo ntlm.lo sbind.lo kbind.lo unbind.lo cancel.lo \
+ 	filter.lo free.lo sort.lo passwd.lo whoami.lo \
+ 	getdn.lo getentry.lo getattr.lo getvalues.lo addentry.lo \
+ 	request.lo os-ip.lo url.lo sortctrl.lo vlvctrl.lo \
+--- /dev/null	2004-06-30 15:04:37.000000000 -0400
++++ libraries/libldap/ntlm.c	2004-07-14 13:44:18.000000000 -0400
+@@ -0,0 +1,137 @@
++/* $OpenLDAP: pkg/ldap/libraries/libldap/ntlm.c,v 1.1.4.10 2002/01/04 20:38:21 kurt Exp $ */
++/*
++ * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
++ * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
++ */
++
++/* Mostly copied from sasl.c */
++
++#include "portable.h"
++
++#include <stdlib.h>
++#include <stdio.h>
++
++#include <ac/socket.h>
++#include <ac/string.h>
++#include <ac/time.h>
++#include <ac/errno.h>
++
++#include "ldap-int.h"
++
++int
++ldap_ntlm_bind(
++	LDAP		*ld,
++	LDAP_CONST char	*dn,
++	ber_tag_t	tag,
++	struct berval	*cred,
++	LDAPControl	**sctrls,
++	LDAPControl	**cctrls,
++	int		*msgidp )
++{
++	BerElement	*ber;
++	int rc;
++	ber_int_t id;
++
++	Debug( LDAP_DEBUG_TRACE, "ldap_ntlm_bind\n", 0, 0, 0 );
++
++	assert( ld != NULL );
++	assert( LDAP_VALID( ld ) );
++	assert( msgidp != NULL );
++
++	if( msgidp == NULL ) {
++		ld->ld_errno = LDAP_PARAM_ERROR;
++		return ld->ld_errno;
++	}
++
++	/* create a message to send */
++	if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) {
++		ld->ld_errno = LDAP_NO_MEMORY;
++		return ld->ld_errno;
++	}
++
++	assert( LBER_VALID( ber ) );
++
++	LDAP_NEXT_MSGID( ld, id );
++	rc = ber_printf( ber, "{it{istON}" /*}*/,
++			 id, LDAP_REQ_BIND,
++			 ld->ld_version, dn, tag,
++			 cred );
++
++	/* Put Server Controls */
++	if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) {
++		ber_free( ber, 1 );
++		return ld->ld_errno;
++	}
++
++	if ( ber_printf( ber, /*{*/ "N}" ) == -1 ) {
++		ld->ld_errno = LDAP_ENCODING_ERROR;
++		ber_free( ber, 1 );
++		return ld->ld_errno;
++	}
++
++	/* send the message */
++	*msgidp = ldap_send_initial_request( ld, LDAP_REQ_BIND, dn, ber, id );
++
++	if(*msgidp < 0)
++		return ld->ld_errno;
++
++	return LDAP_SUCCESS;
++}
++
++int
++ldap_parse_ntlm_bind_result(
++	LDAP		*ld,
++	LDAPMessage	*res,
++	struct berval	*challenge)
++{
++	ber_int_t	errcode;
++	ber_tag_t	tag;
++	BerElement	*ber;
++	ber_len_t	len;
++
++	Debug( LDAP_DEBUG_TRACE, "ldap_parse_ntlm_bind_result\n", 0, 0, 0 );
++
++	assert( ld != NULL );
++	assert( LDAP_VALID( ld ) );
++	assert( res != NULL );
++
++	if ( ld == NULL || res == NULL ) {
++		return LDAP_PARAM_ERROR;
++	}
++
++	if( res->lm_msgtype != LDAP_RES_BIND ) {
++		ld->ld_errno = LDAP_PARAM_ERROR;
++		return ld->ld_errno;
++	}
++
++	if ( ld->ld_error ) {
++		LDAP_FREE( ld->ld_error );
++		ld->ld_error = NULL;
++	}
++	if ( ld->ld_matched ) {
++		LDAP_FREE( ld->ld_matched );
++		ld->ld_matched = NULL;
++	}
++
++	/* parse results */
++
++	ber = ber_dup( res->lm_ber );
++
++	if( ber == NULL ) {
++		ld->ld_errno = LDAP_NO_MEMORY;
++		return ld->ld_errno;
++	}
++
++	tag = ber_scanf( ber, "{ioa" /*}*/,
++			 &errcode, challenge, &ld->ld_error );
++	ber_free( ber, 0 );
++
++	if( tag == LBER_ERROR ) {
++		ld->ld_errno = LDAP_DECODING_ERROR;
++		return ld->ld_errno;
++	}
++
++	ld->ld_errno = errcode;
++
++	return( ld->ld_errno );
++}
diff --git a/server/lib/Makefile.am b/server/lib/Makefile.am
new file mode 100644
index 0000000..566b63d
--- /dev/null
+++ b/server/lib/Makefile.am
@@ -0,0 +1,129 @@
+noinst_LTLIBRARIES = \
+	libexchange.la
+
+PROP_GENERATED = e2k-propnames.h e2k-propnames.c e2k-proptags.h
+
+mapi_properties    = $(srcdir)/mapi-properties
+e2k_propnames_h_in = $(srcdir)/e2k-propnames.h.in 
+e2k_propnames_c_in = $(srcdir)/e2k-propnames.c.in 
+e2k_proptags_h_in  = $(srcdir)/e2k-proptags.h.in 
+
+e2k-propnames.h: $(e2k_propnames_h_in) $(mapi_properties)
+	@echo Building $@
+	@( awk '/^ AUTOGENERATE@/ {exit;} {print;}' $(e2k_propnames_h_in); \
+	  awk '/^x/ { printf "#define %-39s E2K_NS_MAPI_PROPTAG  \"%s \" \n", $$2, $$1; }' $(mapi_properties); \
+	  awk '{if (tail) { print; }} /^ AUTOGENERATE@/ {tail=1;}' $(e2k_propnames_h_in) ) \
+	> $@
+
+e2k-propnames.c: $(e2k_propnames_c_in) $(mapi_properties)
+	@echo Building $@
+	@( awk '/^ AUTOGENERATE@/ {exit;} {print;}' $(e2k_propnames_c_in); \
+	  awk '/^x/ { print " \t{  \"" $$1 " \",  \"" $$2 " \" },"; }' $(mapi_properties); \
+	  awk '{if (tail) { print; }} /^ AUTOGENERATE@/ {tail=1;}' $(e2k_propnames_c_in) ) \
+	> $@
+
+e2k-proptags.h: $(e2k_proptags_h_in) $(mapi_properties)
+	@echo Building $@
+	@( awk '/^ AUTOGENERATE@/ {exit;} {print;}' $(e2k_proptags_h_in); \
+	  awk '/^x/ { printf "#define E2K_PROPTAG_%-39s 0%s \n", $$2, $$1; }' $(mapi_properties); \
+	  awk '{if (tail) { print; }} /^ AUTOGENERATE@/ {tail=1;}' $(e2k_proptags_h_in) ) \
+	> $@
+
+BUILT_SOURCES = $(PROP_GENERATED)
+NODIST_FILES = $(PROP_GENERATED)
+CLEANFILES = $(PROP_GENERATED)
+
+MARSHAL_GENERATED = e2k-marshal.c e2k-marshal.h
+
+e2k-marshal.h: e2k-marshal.list
+	( @GLIB_GENMARSHAL@ --prefix=e2k_marshal $(srcdir)/e2k-marshal.list --header > e2k-marshal.tmp  \
+	&& mv e2k-marshal.tmp e2k-marshal.h )  \
+	|| ( rm -f e2k-marshal.tmp && exit 1 )
+
+e2k-marshal.c: e2k-marshal.h
+	( (echo '#include "e2k-marshal.h"'; @GLIB_GENMARSHAL@ --prefix=e2k_marshal $(srcdir)/e2k-marshal.list --body) > e2k-marshal.tmp  \
+	&& mv e2k-marshal.tmp e2k-marshal.c )  \
+	|| ( rm -f e2k-marshal.tmp && exit 1 )
+
+BUILT_SOURCES += $(MARSHAL_GENERATED)
+NODIST_FILES += $(MARSHAL_GENERATED)
+CLEANFILES += $(MARSHAL_GENERATED)
+
+if ENABLE_KRB5
+KERBEROS_FILES = \
+	e2k-kerberos.c \
+	e2k-kerberos.h
+else
+KERBEROS_FILES =
+endif
+
+# Fix the code to not use E_DATA_SERVER_UI_CFLAGS
+
+libexchange_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-DCONNECTOR_PREFIX=\""$(prefix)"\" \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/server/xntlm \
+	$(LDAP_CFLAGS) \
+	$(KRB5_CFLAGS) \
+	$(SOUP_CFLAGS) \
+	$(GNOME_PLATFORM_CFLAGS) \
+	$(EVOLUTION_DATA_SERVER_CFLAGS)
+
+libexchange_la_SOURCES = \
+	$(MARSHAL_GENERATED) \
+	e2k-propnames.h \
+	e2k-proptags.h \
+	e2k-action.c \
+	e2k-action.h \
+	e2k-autoconfig.c \
+	e2k-autoconfig.h \
+	e2k-context.c \
+	e2k-context.h \
+	e2k-freebusy.c \
+	e2k-freebusy.h \
+	e2k-global-catalog.c \
+	e2k-global-catalog.h \
+	e2k-global-catalog-ldap.h \
+	e2k-http-utils.c \
+	e2k-http-utils.h \
+	e2k-operation.c \
+	e2k-operation.h \
+	e2k-path.c \
+	e2k-path.h \
+	e2k-properties.c \
+	e2k-properties.h \
+	e2k-restriction.c \
+	e2k-restriction.h \
+	e2k-result.c \
+	e2k-result.h \
+	e2k-rule.c \
+	e2k-rule.h \
+	e2k-rule-xml.c \
+	e2k-rule-xml.h \
+	e2k-security-descriptor.c \
+	e2k-security-descriptor.h \
+	e2k-sid.c \
+	e2k-sid.h \
+	e2k-types.h \
+	e2k-uri.c \
+	e2k-uri.h \
+	e2k-utils.c \
+	e2k-utils.h \
+	e2k-validate.h \
+	e2k-xml-utils.c \
+	e2k-xml-utils.h \
+	$(KERBEROS_FILES) \
+	mapi.h
+
+EXTRA_DIST = \
+	e2k-marshal.list \
+	mapi-properties \
+	$(e2k_propnames_h_in) \
+	$(e2k_propnames_c_in) \
+	$(e2k_proptags_h_in)
+
+dist-hook:
+	cd $(distdir); rm -f $(NODIST_FILES)
+
+-include $(top_srcdir)/git.mk
diff --git a/server/lib/actest.c b/server/lib/actest.c
new file mode 100644
index 0000000..31d076a
--- /dev/null
+++ b/server/lib/actest.c
@@ -0,0 +1,201 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2003, 2004 Novell, Inc.
+ *
+ * This  program is free  software; you  can redistribute  it and/or
+ * modify it under the terms of version 2  of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* Autoconfig test program */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "e2k-autoconfig.h"
+#include "test-utils.h"
+
+const gchar *test_program_name = "actest";
+
+static E2kOperation op;
+
+static gpointer
+cancel (gpointer data)
+{
+	e2k_operation_cancel (&op);
+	return NULL;
+}
+
+static void
+quit (gint sig)
+{
+	static pthread_t cancel_thread;
+
+	if (!cancel_thread) {
+		pthread_create (&cancel_thread, NULL, cancel, NULL);
+	} else
+		exit (0);
+}
+
+void
+test_main (gint argc, gchar **argv)
+{
+	E2kAutoconfig *ac;
+	E2kAutoconfigResult result;
+	const gchar *username, *password, *owa_uri, *gc_server;
+
+	signal (SIGINT, quit);
+
+	if (argc < 2 || argc > 4) {
+		fprintf (stderr, "Usage: %s username [OWA URL] [Global Catalog server]\n", argv[0]);
+		exit (1);
+	}
+
+	username = argv[1];
+	password = test_get_password (username, NULL);
+
+	owa_uri = argc > 2 ? argv[2] : NULL;
+	gc_server = argc > 3 ? argv[3] : NULL;
+
+	e2k_operation_init (&op);
+	ac = e2k_autoconfig_new (owa_uri, username, password,
+				 E2K_AUTOCONFIG_USE_EITHER);
+
+	if (ac->owa_uri) {
+		if (!owa_uri)
+			printf ("[Default OWA URI: %s]\n", ac->owa_uri);
+	} else {
+		printf ("No default OWA URI available. Must specify on commandline.\n");
+		goto done;
+	}
+
+	if (ac->gc_server)
+		printf ("[Default GC: %s]\n", ac->gc_server);
+	if (ac->nt_domain)
+		printf ("[Default NT Domain: %s]\n", ac->nt_domain);
+	if (ac->w2k_domain)
+		printf ("[Default W2k Domain: %s]\n", ac->w2k_domain);
+	printf ("\n");
+
+	if (gc_server)
+		e2k_autoconfig_set_gc_server (ac, gc_server, -1, E2K_AUTOCONFIG_USE_GAL_DEFAULT);
+
+	result = e2k_autoconfig_check_exchange (ac, &op);
+	if (result != E2K_AUTOCONFIG_OK) {
+		const gchar *msg;
+		switch (result) {
+		case E2K_AUTOCONFIG_CANT_RESOLVE:
+			msg = "Could not resolve hostname";
+			break;
+		case E2K_AUTOCONFIG_CANT_CONNECT:
+			msg = "Could not connect to server";
+			break;
+		case E2K_AUTOCONFIG_REDIRECT:
+			msg = "Multiple redirection";
+			break;
+		case E2K_AUTOCONFIG_AUTH_ERROR:
+			msg = "Authentication error. Password incorrect?";
+			break;
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN:
+			msg = "Authentication error. Password incorrect, or try DOMAIN\\username?";
+			break;
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC:
+			msg = "Authentication error. Password incorrect, or try Basic auth?";
+			break;
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM:
+			msg = "Authentication error. Password incorrect, or try NTLM auth?";
+			break;
+		case E2K_AUTOCONFIG_TRY_SSL:
+			msg = "Need to use SSL";
+			break;
+		case E2K_AUTOCONFIG_EXCHANGE_5_5:
+			msg = "This is an Exchange 5.5 server";
+			break;
+		case E2K_AUTOCONFIG_NOT_EXCHANGE:
+			msg = "Server does not appear to be Exchange";
+			break;
+		case E2K_AUTOCONFIG_NO_OWA:
+			msg = "Did not find OWA at given URL";
+			break;
+		case E2K_AUTOCONFIG_NO_MAILBOX:
+			msg = "You don't seem to have a mailbox here";
+			break;
+		case E2K_AUTOCONFIG_CANT_BPROPFIND:
+			msg = "Server does not allow BPROPFIND";
+			break;
+		case E2K_AUTOCONFIG_CANCELLED:
+			msg = "Cancelled";
+			break;
+		case E2K_AUTOCONFIG_FAILED:
+		default:
+			msg = "Unknown error";
+			break;
+		}
+
+		printf ("Exchange check to %s failed:\n  %s\n",
+			ac->owa_uri, msg);
+		goto done;
+	}
+
+	result = e2k_autoconfig_check_global_catalog (ac, &op);
+	if (result != E2K_AUTOCONFIG_OK) {
+		const gchar *msg;
+		switch (result) {
+		case E2K_AUTOCONFIG_CANT_RESOLVE:
+			msg = "Could not resolve GC server";
+			break;
+		case E2K_AUTOCONFIG_NO_MAILBOX:
+			msg = "No data for user";
+			break;
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN:
+			msg = "Authentication error. Try DOMAIN\\username?";
+			break;
+		case E2K_AUTOCONFIG_CANCELLED:
+			msg = "Cancelled";
+			break;
+		case E2K_AUTOCONFIG_FAILED:
+		default:
+			msg = "Unknown error";
+			break;
+		}
+
+		printf ("\nGlobal Catalog check failed: %s\n", msg);
+		if (!ac->gc_server) {
+			if (ac->w2k_domain)
+				printf ("got domain=%s but ", ac->w2k_domain);
+			printf ("could not autodetect.\nSpecify GC on command-line.\n");
+		}
+		goto done;
+	}
+
+	printf ("%s is an Exchange Server %s\n\n", ac->exchange_server,
+		ac->version == E2K_EXCHANGE_2000 ? "2000" :
+		ac->version == E2K_EXCHANGE_2003 ? "2003" :
+		"[Unknown version]");
+
+	printf ("Name: %s\nEmail: %s\nTimezone: %s\nAccount URL: %s\n\n",
+		ac->display_name, ac->email, ac->timezone, ac->account_uri);
+
+	if (!ac->pf_server)
+		printf ("Warning: public folder server was defaulted\n\n");
+ done:
+	e2k_operation_free (&op);
+	e2k_autoconfig_free (ac);
+	test_quit ();
+}
diff --git a/server/lib/cptest.c b/server/lib/cptest.c
new file mode 100644
index 0000000..91c0349
--- /dev/null
+++ b/server/lib/cptest.c
@@ -0,0 +1,120 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2003, 2004 Novell, Inc.
+ *
+ * This  program is free  software; you  can redistribute  it and/or
+ * modify it under the terms of version 2  of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* Change password test program */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "e2k-kerberos.h"
+#include "test-utils.h"
+
+const gchar *test_program_name = "cptest";
+
+static void
+krb_error (E2kKerberosResult result, const gchar *failed)
+{
+	switch (result) {
+	case E2K_KERBEROS_USER_UNKNOWN:
+		fprintf (stderr, "Unknown user\n");
+		exit (1);
+
+	case E2K_KERBEROS_PASSWORD_INCORRECT:
+		fprintf (stderr, "Password incorrect\n");
+		exit (1);
+
+	case E2K_KERBEROS_PASSWORD_EXPIRED:
+		printf ("Note: password has expired\n");
+		break;
+
+	case E2K_KERBEROS_KDC_UNREACHABLE:
+		fprintf (stderr, "KDC unreachable (network problem or no such domain)\n");
+		exit (1);
+
+	case E2K_KERBEROS_TIME_SKEW:
+		fprintf (stderr, "Client/server time skew is too large.\n");
+		exit (1);
+
+	case E2K_KERBEROS_PASSWORD_TOO_WEAK:
+		fprintf (stderr, "Server rejected new password\n");
+		exit (1);
+
+	case E2K_KERBEROS_FAILED:
+		if (failed) {
+			fprintf (stderr, "%s\n", failed);
+			exit (1);
+		}
+		/* else fall through */
+
+	default:
+		fprintf (stderr, "Unknown error.\n");
+		exit (1);
+	}
+}
+
+void
+test_main (gint argc, gchar **argv)
+{
+	gchar *domain, *at, *prompt, *password;
+	gchar *newpass1, *newpass2;
+	const gchar *user;
+	gint res;
+
+	if (argc != 2) {
+		fprintf (stderr, "Usage: %s [user ]domain\n", argv[0]);
+		exit (1);
+	}
+
+	domain = argv[1];
+	at = strchr (domain, '@');
+	if (at) {
+		user = g_strndup (domain, at - domain);
+		domain = at + 1;
+	} else
+		user = g_get_user_name ();
+
+	prompt = g_strdup_printf ("Password for %s %s", user, domain);
+	password = test_ask_password (prompt);
+	g_free (prompt);
+
+	res = e2k_kerberos_check_password (user, domain, password);
+	if (res != E2K_KERBEROS_OK)
+		krb_error (res, NULL);
+
+	newpass1 = test_ask_password ("New password");
+	newpass2 = test_ask_password ("Confirm");
+
+	if (!newpass1 || !newpass2 || strcmp (newpass1, newpass2) != 0) {
+		fprintf (stderr, "Passwords do not match.\n");
+		exit (1);
+	}
+
+	res = e2k_kerberos_change_password (user, domain, password, newpass1);
+	if (res != E2K_KERBEROS_OK)
+		krb_error (res, "Could not change password");
+
+	printf ("Password changed\n");
+	test_quit ();
+}
diff --git a/server/lib/davcat.c b/server/lib/davcat.c
new file mode 100644
index 0000000..cb1754e
--- /dev/null
+++ b/server/lib/davcat.c
@@ -0,0 +1,184 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2003, 2004 Novell, Inc.
+ *
+ * This  program is free  software; you  can redistribute  it and/or
+ * modify it under the terms of version 2  of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* Generic WebDAV test program */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "e2k-context.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+#include "e2k-xml-utils.h"
+
+#include "test-utils.h"
+
+static void
+print_header (gpointer name, gpointer value, gpointer data)
+{
+	gboolean *isxml = data;
+
+	printf ("%s: %s\n", (gchar *)name, (gchar *)value);
+	if (!g_ascii_strcasecmp (name, "Content-Type") &&
+	    strstr (value, "/xml"))
+		*isxml = TRUE;
+}
+
+const gchar *test_program_name = "davcat";
+
+void
+test_main (gint argc, gchar **argv)
+{
+	E2kContext *ctx;
+	SoupMessage *msg;
+	GByteArray *input;
+	gchar buf[1024], *base_uri, *root_uri, *eol, *vers, *p;
+	gchar *method, *path, *uri;
+	gchar *name, *value;
+	gint nread;
+	gboolean isxml = FALSE;
+
+	if (argc != 2) {
+		fprintf (stderr, "usage: %s URI\n", argv[0]);
+		exit (1);
+	}
+	base_uri = argv[1];
+	ctx = test_get_context (base_uri);
+
+	input = g_byte_array_new ();
+	do {
+		nread = read (STDIN_FILENO, buf, sizeof (buf));
+		if (nread > 0)
+			g_byte_array_append (input, buf, nread);
+	} while (nread > 0 || (nread == -1 && errno == EINTR));
+	g_byte_array_append (input, "", 1);
+
+	method = input->data;
+	eol = strchr (method, '\n');
+	p = strchr (method, ' ');
+	if (!eol || !p || p > eol) {
+		fprintf (stderr, "Could not parse request method\n");
+		exit (1);
+	}
+	*p = '\0';
+
+	path = p + 1;
+	if (*path == '/')
+		path++;
+	p = strchr (path, ' ');
+	if (!p || p > eol)
+		p = eol;
+	*p = '\0';
+	if (p < eol)
+		vers = p + 1;
+	else
+		vers = NULL;
+
+	root_uri = g_strdup (base_uri);
+	p = strstr (root_uri, "://");
+	if (p) {
+		p = strchr (p + 3, '/');
+		if (p)
+			*p = '\0';
+	}
+	uri = e2k_uri_concat (root_uri, path);
+	g_free (root_uri);
+	msg = e2k_soup_message_new (ctx, uri, method);
+	if (!msg) {
+		fprintf (stderr, "Could not create message to %s\n", uri);
+		exit (1);
+	}
+	g_free (uri);
+
+	if (vers) {
+		if (strncmp (vers, "HTTP/1.", 7) != 0 ||
+		    (vers[7] != '0' && vers[7] != '1')) {
+			fprintf (stderr, "Could not parse HTTP version\n");
+			exit (1);
+		}
+		if (vers[7] == '0')
+			soup_message_set_http_version (msg, SOUP_HTTP_1_0);
+	}
+
+	while (1) {
+		name = eol + 1;
+		eol = strchr (name, '\n');
+		p = strchr (name, ':');
+		if (!eol || eol == name || !p || p > eol || p[1] != ' ')
+			break;
+		*p = '\0';
+		value = p + 2;
+		*eol = '\0';
+		if (eol[-1] == '\r')
+			eol[-1] = '\0';
+		soup_message_add_header (msg->request_headers, name, value);
+	}
+
+	p = name;
+	if (*p == '\r')
+		p++;
+	if (*p == '\n')
+		p++;
+
+	if (*p) {
+		msg->request.body = e2k_lf_to_crlf (p);
+		msg->request.length = strlen (msg->request.body);
+		msg->request.owner = SOUP_BUFFER_SYSTEM_OWNED;
+
+		if (!soup_message_get_header (msg->request_headers, "Content-Type")) {
+			soup_message_add_header (msg->request_headers,
+						 "Content-Type", "text/xml");
+		}
+	}
+
+	e2k_context_send_message (ctx, NULL, msg);
+
+	printf ("%d %s\n", msg->status_code, msg->reason_phrase);
+	if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code))
+		exit (1);
+
+	soup_message_foreach_header (msg->response_headers,
+				     print_header, &isxml);
+	printf ("\n");
+
+	if (isxml) {
+		xmlDoc *doc;
+
+		doc = e2k_parse_xml (msg->response.body, msg->response.length);
+		if (doc) {
+			xmlDocFormatDump (stdout, doc, 1);
+			xmlFreeDoc (doc);
+		} else
+			fwrite (msg->response.body, 1, msg->response.length, stdout);
+	} else
+		fwrite (msg->response.body, 1, msg->response.length, stdout);
+	printf ("\n");
+
+	g_object_unref (msg);
+	g_byte_array_free (input, TRUE);
+	g_object_unref (ctx);
+	test_quit ();
+}
diff --git a/server/lib/e2k-action.c b/server/lib/e2k-action.c
new file mode 100644
index 0000000..6662f34
--- /dev/null
+++ b/server/lib/e2k-action.c
@@ -0,0 +1,800 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* e2k-action.c: Exchange server-side rule actions */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "e2k-action.h"
+#include "e2k-propnames.h"
+#include "e2k-restriction.h"
+#include "e2k-rule.h"
+#include "e2k-utils.h"
+#include "mapi.h"
+
+/* The apparently-constant store entryid prefix for a move or copy action */
+#define E2K_ACTION_XFER_STORE_ENTRYID_PREFIX "\x00\x00\x00\x00\x38\xa1\xbb\x10\x05\xe5\x10\x1a\xa1\xbb\x08\x00\x2b\x2a\x56\xc2\x00\x00\x45\x4d\x53\x4d\x44\x42\x2e\x44\x4c\x4c\x00\x00\x00\x00"
+#define E2K_ACTION_XFER_STORE_ENTRYID_PREFIX_LEN (sizeof (E2K_ACTION_XFER_STORE_ENTRYID_PREFIX) - 1)
+
+static GByteArray *
+copy_bytearray (GByteArray *ba)
+{
+	GByteArray *copy;
+
+	copy = g_byte_array_sized_new (ba->len);
+	copy->len = ba->len;
+	memcpy (copy->data, ba->data, copy->len);
+
+	return copy;
+}
+
+static E2kAction *
+xfer_action (E2kActionType type, GByteArray *store_entryid,
+	     GByteArray *folder_source_key)
+{
+	E2kAction *act;
+
+	act = g_new0 (E2kAction, 1);
+	act->type = type;
+	act->act.xfer.store_entryid = copy_bytearray (store_entryid);
+	act->act.xfer.folder_source_key = copy_bytearray (folder_source_key);
+
+	return act;
+}
+
+/**
+ * e2k_action_move:
+ * @store_entryid: The PR_STORE_ENTRYID of the message store
+ * @folder_source_key: The PR_SOURCE_KEY of a folder in that store
+ *
+ * Creates a rule action to move a message into the indicated folder
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_move (GByteArray *store_entryid, GByteArray *folder_source_key)
+{
+	return xfer_action (E2K_ACTION_MOVE, store_entryid, folder_source_key);
+}
+
+/**
+ * e2k_action_copy:
+ * @store_entryid: The PR_STORE_ENTRYID of the message store
+ * @folder_source_key: The PR_SOURCE_KEY of a folder in that store
+ *
+ * Creates a rule action to copy a message into the indicated folder
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_copy (GByteArray *store_entryid, GByteArray *folder_source_key)
+{
+	return xfer_action (E2K_ACTION_COPY, store_entryid, folder_source_key);
+}
+
+static E2kAction *
+reply_action (E2kActionType type, GByteArray *template_entryid,
+	      guint8 template_guid[16])
+{
+	E2kAction *act;
+
+	act = g_new0 (E2kAction, 1);
+	act->type = type;
+	act->act.reply.entryid = copy_bytearray (template_entryid);
+	memcpy (act->act.reply.reply_template_guid, template_guid, 16);
+
+	return act;
+}
+
+/**
+ * e2k_action_reply:
+ * @template_entryid: The entryid of the reply template
+ * @template_guid: The GUID of the reply template
+ *
+ * Creates a rule action to reply to a message using the indicated
+ * template
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_reply (GByteArray *template_entryid, guint8 template_guid[16])
+{
+	return reply_action (E2K_ACTION_REPLY, template_entryid, template_guid);
+}
+
+/**
+ * e2k_action_oof_reply:
+ * @template_entryid: The entryid of the reply template
+ * @template_guid: The GUID of the reply template
+ *
+ * Creates a rule action to send an Out-of-Office reply to a message
+ * using the indicated template
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_oof_reply (GByteArray *template_entryid, guint8 template_guid[16])
+{
+	return reply_action (E2K_ACTION_OOF_REPLY, template_entryid, template_guid);
+}
+
+/**
+ * e2k_action_defer:
+ * @data: data identifying the deferred action
+ *
+ * Creates a rule action to defer processing on a message
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_defer (GByteArray *data)
+{
+	E2kAction *act;
+
+	act = g_new0 (E2kAction, 1);
+	act->type = E2K_ACTION_DEFER;
+	act->act.defer_data = copy_bytearray (data);
+
+	return act;
+}
+
+/**
+ * e2k_action_bounce:
+ * @bounce_code: a bounce code
+ *
+ * Creates a rule action to bounce a message
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_bounce (E2kActionBounceCode bounce_code)
+{
+	E2kAction *act;
+
+	act = g_new0 (E2kAction, 1);
+	act->type = E2K_ACTION_BOUNCE;
+	act->act.bounce_code = bounce_code;
+
+	return act;
+}
+
+static E2kAction *
+forward_action (E2kActionType type, E2kAddrList *list)
+{
+	E2kAction *act;
+
+	g_return_val_if_fail (type == E2K_ACTION_FORWARD || type == E2K_ACTION_DELEGATE, NULL);
+	g_return_val_if_fail (list->nentries > 0, NULL);
+
+	act = g_new0 (E2kAction, 1);
+	act->type = type;
+	act->act.addr_list = list;
+
+	return act;
+}
+
+/**
+ * e2k_action_forward:
+ * @list: a list of recipients
+ *
+ * Creates a rule action to forward a message to the indicated list of
+ * recipients
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_forward (E2kAddrList *list)
+{
+	return forward_action (E2K_ACTION_FORWARD, list);
+}
+
+/**
+ * e2k_action_delegate:
+ * @list: a list of recipients
+ *
+ * Creates a rule action to delegate a meeting request to the
+ * indicated list of recipients
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_delegate (E2kAddrList *list)
+{
+	return forward_action (E2K_ACTION_DELEGATE, list);
+}
+
+/**
+ * e2k_action_tag:
+ * @propname: a MAPI property name
+ * @type: the type of @propname
+ * @value: the value for @propname
+ *
+ * Creates a rule action to set the given property to the given value
+ * on a message.
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_tag (const gchar *propname, E2kPropType type, gpointer value)
+{
+	E2kAction *act;
+
+	act = g_new0 (E2kAction, 1);
+	act->type = E2K_ACTION_TAG;
+	e2k_rule_prop_set (&act->act.proptag.prop, propname);
+	act->act.proptag.type = type;
+	act->act.proptag.value = value; /* FIXME: copy? */
+
+	return act;
+}
+
+/**
+ * e2k_action_delete:
+ *
+ * Creates a rule action to permanently delete a message (ie, not just
+ * move it to the trash).
+ *
+ * Return value: the new rule action
+ **/
+E2kAction *
+e2k_action_delete (void)
+{
+	E2kAction *act;
+
+	act = g_new0 (E2kAction, 1);
+	act->type = E2K_ACTION_DELETE;
+
+	return act;
+}
+
+/**
+ * e2k_addr_list_new:
+ * @nentries: the number of entries
+ *
+ * Creates an address list for a forward or delegate rule, with
+ * @nentries slots
+ *
+ * Return value: the new address list
+ **/
+E2kAddrList *
+e2k_addr_list_new (gint nentries)
+{
+	E2kAddrList *list;
+
+	list = g_malloc0 (sizeof (E2kAddrList) +
+			  (nentries - 1) * sizeof (E2kAddrEntry));
+	list->nentries = nentries;
+
+	return list;
+}
+
+static void
+addr_entry_set_core (E2kPropValue *pv, GByteArray *entryid,
+		     const gchar *display_name, const gchar *email_type,
+		     const gchar *email_addr)
+{
+	e2k_rule_prop_set (&pv[0].prop, PR_ENTRYID);
+	pv[0].type = E2K_PROP_TYPE_BINARY;
+	pv[0].value = entryid;
+
+	e2k_rule_prop_set (&pv[1].prop, PR_DISPLAY_NAME);
+	pv[1].type = E2K_PROP_TYPE_STRING;
+	pv[1].value = g_strdup (display_name);
+
+	e2k_rule_prop_set (&pv[2].prop, PR_OBJECT_TYPE);
+	pv[2].type = E2K_PROP_TYPE_INT;
+	pv[2].value = GINT_TO_POINTER (MAPI_MAILUSER);
+
+	e2k_rule_prop_set (&pv[3].prop, PR_DISPLAY_TYPE);
+	pv[3].type = E2K_PROP_TYPE_INT;
+	pv[3].value = GINT_TO_POINTER (DT_MAILUSER);
+
+	e2k_rule_prop_set (&pv[4].prop, PR_TRANSMITTABLE_DISPLAY_NAME);
+	pv[4].type = E2K_PROP_TYPE_STRING;
+	pv[4].value = g_strdup (display_name);
+
+	e2k_rule_prop_set (&pv[5].prop, PR_EMAIL_ADDRESS);
+	pv[5].type = E2K_PROP_TYPE_STRING;
+	pv[5].value = g_strdup (email_addr);
+
+	e2k_rule_prop_set (&pv[6].prop, PR_ADDRTYPE);
+	pv[6].type = E2K_PROP_TYPE_STRING;
+	pv[6].value = g_strdup (email_type);
+
+	e2k_rule_prop_set (&pv[7].prop, PR_SEND_INTERNET_ENCODING);
+	pv[7].type = E2K_PROP_TYPE_INT;
+	pv[7].value = GINT_TO_POINTER (0); /* "Let transport decide" */
+
+	e2k_rule_prop_set (&pv[8].prop, PR_RECIPIENT_TYPE);
+	pv[8].type = E2K_PROP_TYPE_INT;
+	pv[8].value = GINT_TO_POINTER (MAPI_TO);
+
+	e2k_rule_prop_set (&pv[9].prop, PR_SEARCH_KEY);
+	pv[9].type = E2K_PROP_TYPE_BINARY;
+	pv[9].value = e2k_search_key_generate (email_type, email_addr);
+}
+
+/**
+ * e2k_addr_list_set_local:
+ * @list: the address list
+ * @entry_num: the list entry to set
+ * @display_name: the UTF-8 display name of the recipient
+ * @exchange_dn: the Exchange 5.5-style DN of the recipient
+ * @email: the SMTP email address of the recipient
+ *
+ * Sets entry number @entry_num of @list to refer to the indicated
+ * local Exchange user.
+ **/
+void
+e2k_addr_list_set_local (E2kAddrList *list, gint entry_num,
+			 const gchar *display_name,
+			 const gchar *exchange_dn,
+			 const gchar *email)
+{
+	E2kPropValue *pv;
+
+	list->entry[entry_num].nvalues = 12;
+	list->entry[entry_num].propval = pv = g_new0 (E2kPropValue, 12);
+
+	addr_entry_set_core (pv, e2k_entryid_generate_local (exchange_dn),
+			     display_name, "EX", exchange_dn);
+
+	e2k_rule_prop_set (&pv[10].prop, PR_EMS_AB_DISPLAY_NAME_PRINTABLE);
+	pv[10].type = E2K_PROP_TYPE_STRING;
+	pv[10].value = g_strdup ("FIXME");
+
+	e2k_rule_prop_set (&pv[11].prop, PR_SMTP_ADDRESS);
+	pv[11].type = E2K_PROP_TYPE_STRING;
+	pv[11].value = g_strdup (email);
+}
+
+/**
+ * e2k_addr_list_set_oneoff:
+ * @list: the address list
+ * @entry_num: the list entry to set
+ * @display_name: the UTF-8 display name of the recipient
+ * @email: the SMTP email address of the recipient
+ *
+ * Sets entry number @entry_num of @list to refer to the indicated
+ * "one-off" SMTP user.
+ **/
+void
+e2k_addr_list_set_oneoff (E2kAddrList *list, gint entry_num,
+			  const gchar *display_name, const gchar *email)
+{
+	E2kPropValue *pv;
+
+	list->entry[entry_num].nvalues = 12;
+	list->entry[entry_num].propval = pv = g_new0 (E2kPropValue, 12);
+
+	addr_entry_set_core (pv, e2k_entryid_generate_oneoff (display_name, email, TRUE),
+			     display_name, "SMTP", email);
+
+	e2k_rule_prop_set (&pv[10].prop, PR_SEND_RICH_INFO);
+	pv[10].type = E2K_PROP_TYPE_BOOL;
+	pv[10].value = GINT_TO_POINTER (FALSE);
+
+	e2k_rule_prop_set (&pv[11].prop, PR_RECORD_KEY);
+	pv[11].type = E2K_PROP_TYPE_BINARY;
+	pv[11].value = e2k_entryid_generate_oneoff (display_name, email, FALSE);
+}
+
+/**
+ * e2k_addr_list_free:
+ * @list: the address list
+ *
+ * Frees @list and all its entries.
+ **/
+void
+e2k_addr_list_free (E2kAddrList *list)
+{
+	gint i, j;
+	E2kAddrEntry *entry;
+
+	for (i = 0; i < list->nentries; i++) {
+		entry = &list->entry[i];
+
+		for (j = 0; j < entry->nvalues; j++)
+			e2k_rule_free_propvalue (&entry->propval[j]);
+		g_free (entry->propval);
+	}
+	g_free (list);
+}
+
+/**
+ * e2k_action_free:
+ * @act: the action
+ *
+ * Frees @act
+ **/
+void
+e2k_action_free (E2kAction *act)
+{
+	switch (act->type) {
+	case E2K_ACTION_MOVE:
+	case E2K_ACTION_COPY:
+		if (act->act.xfer.store_entryid)
+			g_byte_array_free (act->act.xfer.store_entryid, TRUE);
+		if (act->act.xfer.folder_source_key)
+			g_byte_array_free (act->act.xfer.folder_source_key, TRUE);
+		break;
+
+	case E2K_ACTION_REPLY:
+	case E2K_ACTION_OOF_REPLY:
+		if (act->act.reply.entryid)
+			g_byte_array_free (act->act.reply.entryid, TRUE);
+		break;
+
+	case E2K_ACTION_DEFER:
+		if (act->act.defer_data)
+			g_byte_array_free (act->act.defer_data, TRUE);
+		break;
+
+	case E2K_ACTION_FORWARD:
+	case E2K_ACTION_DELEGATE:
+		if (act->act.addr_list)
+			e2k_addr_list_free (act->act.addr_list);
+		break;
+
+	case E2K_ACTION_TAG:
+		e2k_rule_free_propvalue (&act->act.proptag);
+		break;
+
+	default:
+		/* Nothing to free */
+		break;
+	}
+
+	g_free (act);
+}
+
+/**
+ * e2k_actions_free:
+ * @actions: an array of #E2kAction
+ *
+ * Frees @actions and all of its elements
+ **/
+void
+e2k_actions_free (GPtrArray *actions)
+{
+	gint i;
+
+	for (i = 0; i < actions->len; i++)
+		e2k_action_free (actions->pdata[i]);
+	g_ptr_array_free (actions, TRUE);
+}
+
+static gboolean
+extract_action (guint8 **data, gint *len, E2kAction **act_ret)
+{
+	gint my_len;
+	guint8 *my_data;
+	guint16 actlen;
+	E2kAction *act;
+
+	if (!e2k_rule_extract_uint16 (data, len, &actlen))
+		return FALSE;
+
+	my_data = *data;
+	my_len = actlen;
+
+	*data += actlen;
+	*len -= actlen;
+
+	data = &my_data;
+	len = &my_len;
+
+	if (*len < 1)
+		return FALSE;
+
+	act = g_new0 (E2kAction, 1);
+	act->type = **data;
+	(*data)++;
+	(*len)--;
+
+	if (!e2k_rule_extract_uint32 (data, len, &act->flavor))
+		goto lose;
+	if (!e2k_rule_extract_uint32 (data, len, &act->flags))
+		goto lose;
+
+	switch (act->type) {
+	case E2K_ACTION_MOVE:
+	case E2K_ACTION_COPY:
+		/* FIXME: what is this? */
+		if (*len < 1 || **data != 1)
+			goto lose;
+		(*len)--;
+		(*data)++;
+
+		if (!e2k_rule_extract_binary (data, len, &act->act.xfer.store_entryid))
+			goto lose;
+		/* Remove the constant part */
+		if (act->act.xfer.store_entryid->len <= E2K_ACTION_XFER_STORE_ENTRYID_PREFIX_LEN ||
+		    memcmp (act->act.xfer.store_entryid->data,
+			    E2K_ACTION_XFER_STORE_ENTRYID_PREFIX,
+			    E2K_ACTION_XFER_STORE_ENTRYID_PREFIX_LEN) != 0)
+			goto lose;
+		act->act.xfer.store_entryid->len -=
+			E2K_ACTION_XFER_STORE_ENTRYID_PREFIX_LEN;
+		memmove (act->act.xfer.store_entryid->data,
+			 act->act.xfer.store_entryid->data +
+			 E2K_ACTION_XFER_STORE_ENTRYID_PREFIX_LEN,
+			 act->act.xfer.store_entryid->len);
+
+		if (!e2k_rule_extract_binary (data, len, &act->act.xfer.folder_source_key))
+			goto lose;
+		/* Likewise */
+		if (act->act.xfer.folder_source_key->len < 1 ||
+		    act->act.xfer.folder_source_key->data[0] != MAPI_FOLDER)
+			goto lose;
+		memmove (act->act.xfer.folder_source_key->data,
+			 act->act.xfer.folder_source_key->data + 1,
+			 act->act.xfer.folder_source_key->len);
+
+		*act_ret = act;
+		return TRUE;
+
+	case E2K_ACTION_REPLY:
+	case E2K_ACTION_OOF_REPLY:
+		/* The reply template GUID is 16 bytes, the entryid
+		 * is the rest.
+		 */
+		if (*len <= 16)
+			goto lose;
+
+		act->act.reply.entryid = g_byte_array_sized_new (*len - 16);
+		memcpy (act->act.reply.entryid->data, *data, *len - 16);
+		act->act.reply.entryid->len = *len - 16;
+		memcpy (act->act.reply.reply_template_guid, *data + *len - 16, 16);
+
+		*act_ret = act;
+		return TRUE;
+
+	case E2K_ACTION_DEFER:
+		act->act.defer_data = g_byte_array_sized_new (*len);
+		memcpy (act->act.defer_data->data, *data, *len);
+		act->act.defer_data->len = *len;
+
+		*act_ret = act;
+		return TRUE;
+
+	case E2K_ACTION_BOUNCE:
+		if (!e2k_rule_extract_uint32 (data, len, &act->act.bounce_code))
+			goto lose;
+
+		*act_ret = act;
+		return TRUE;
+
+	case E2K_ACTION_FORWARD:
+	case E2K_ACTION_DELEGATE:
+	{
+		guint16 nentries, nvalues;
+		gint i, j;
+
+		if (!e2k_rule_extract_uint16 (data, len, &nentries))
+			goto lose;
+		act->act.addr_list = e2k_addr_list_new (nentries);
+		for (i = 0; i < nentries; i++) {
+			/* FIXME: what is this? */
+			if (*len < 1 || **data != 1)
+				goto lose;
+			(*len)--;
+			(*data)++;
+
+			if (!e2k_rule_extract_uint16 (data, len, &nvalues))
+				goto lose;
+			act->act.addr_list->entry[i].nvalues = nvalues;
+			act->act.addr_list->entry[i].propval = g_new0 (E2kPropValue, nvalues);
+
+			for (j = 0; j < nvalues; j++) {
+				if (!e2k_rule_extract_propvalue (data, len, &act->act.addr_list->entry[i].propval[j]))
+					goto lose;
+			}
+		}
+
+		*act_ret = act;
+		return TRUE;
+	}
+
+	case E2K_ACTION_TAG:
+		if (!e2k_rule_extract_propvalue (data, len, &act->act.proptag))
+			goto lose;
+
+		*act_ret = act;
+		return TRUE;
+
+	case E2K_ACTION_DELETE:
+		*act_ret = act;
+		return TRUE;
+
+	case E2K_ACTION_MARK_AS_READ:
+		/* FIXME */
+		return FALSE;
+
+	default:
+		break;
+	}
+
+ lose:
+	e2k_action_free (act);
+	return FALSE;
+}
+
+/**
+ * e2k_actions_extract:
+ * @data: pointer to data pointer
+ * @len: pointer to data length
+ * @actions: pointer to array to store actions in
+ *
+ * Attempts to extract a list of actions from * data, which contains a
+ * binary-encoded list of actions from a server-side rule.
+ *
+ * On success, * actions will contain the extracted list, * data will
+ * be advanced past the end of the restriction data, and * len will be
+ * decremented accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_actions_extract (guint8 **data, gint *len, GPtrArray **actions)
+{
+	GPtrArray *acts;
+	E2kAction *act;
+	guint32 actlen;
+	guint16 nacts;
+	gint i;
+
+	if (!e2k_rule_extract_uint32 (data, len, &actlen))
+		return FALSE;
+	if (actlen > *len)
+		return FALSE;
+
+	if (!e2k_rule_extract_uint16 (data, len, &nacts))
+		return FALSE;
+
+	acts = g_ptr_array_new ();
+	for (i = 0; i < nacts; i++) {
+		if (!extract_action (data, len, &act)) {
+			e2k_actions_free (acts);
+			return FALSE;
+		} else
+			g_ptr_array_add (acts, act);
+	}
+
+	*actions = acts;
+	return TRUE;
+}
+
+static void
+append_action (GByteArray *ba, E2kAction *act)
+{
+	gint actlen_offset, actlen;
+	gchar type;
+
+	/* Save space for length */
+	actlen_offset = ba->len;
+	e2k_rule_append_uint16 (ba, 0);
+
+	e2k_rule_append_byte (ba, act->type);
+	e2k_rule_append_uint32 (ba, act->flavor);
+	e2k_rule_append_uint32 (ba, act->flags);
+
+	switch (act->type) {
+	case E2K_ACTION_MOVE:
+	case E2K_ACTION_COPY:
+		/* FIXME: what is this? */
+		e2k_rule_append_byte (ba, 1);
+
+		e2k_rule_append_uint16 (ba, act->act.xfer.store_entryid->len +
+					E2K_ACTION_XFER_STORE_ENTRYID_PREFIX_LEN);
+		g_byte_array_append (ba, (guint8 *) E2K_ACTION_XFER_STORE_ENTRYID_PREFIX,
+				     E2K_ACTION_XFER_STORE_ENTRYID_PREFIX_LEN);
+		g_byte_array_append (ba, act->act.xfer.store_entryid->data,
+				     act->act.xfer.store_entryid->len);
+
+		e2k_rule_append_uint16 (ba, 49);
+		type = MAPI_FOLDER;
+		g_byte_array_append (ba, (guint8 *) &type, 1);
+		g_byte_array_append (ba, act->act.xfer.folder_source_key->data,
+				     act->act.xfer.folder_source_key->len);
+		break;
+
+	case E2K_ACTION_REPLY:
+	case E2K_ACTION_OOF_REPLY:
+		g_byte_array_append (ba, act->act.reply.entryid->data,
+				     act->act.reply.entryid->len);
+		g_byte_array_append (ba, act->act.reply.reply_template_guid, 16);
+		break;
+
+	case E2K_ACTION_DEFER:
+		g_byte_array_append (ba, act->act.defer_data->data,
+				     act->act.defer_data->len);
+		break;
+
+	case E2K_ACTION_BOUNCE:
+		e2k_rule_append_uint32 (ba, act->act.bounce_code);
+		break;
+
+	case E2K_ACTION_FORWARD:
+	case E2K_ACTION_DELEGATE:
+	{
+		gint i, j;
+		E2kAddrList *list;
+		E2kAddrEntry *entry;
+
+		list = act->act.addr_list;
+		e2k_rule_append_uint16 (ba, list->nentries);
+		for (i = 0; i < list->nentries; i++) {
+			/* FIXME: what is this? */
+			e2k_rule_append_byte (ba, 1);
+
+			entry = &list->entry[i];
+			e2k_rule_append_uint16 (ba, entry->nvalues);
+			for (j = 0; j < entry->nvalues; j++)
+				e2k_rule_append_propvalue (ba, &entry->propval[j]);
+		}
+		break;
+	}
+
+	case E2K_ACTION_TAG:
+		e2k_rule_append_propvalue (ba, &act->act.proptag);
+		break;
+
+	case E2K_ACTION_DELETE:
+		break;
+
+	case E2K_ACTION_MARK_AS_READ:
+		/* FIXME */
+		break;
+
+	default:
+		break;
+	}
+
+	actlen = ba->len - actlen_offset - 2;
+	e2k_rule_write_uint16 (ba->data + actlen_offset, actlen);
+}
+
+/**
+ * e2k_actions_append:
+ * @ba: a buffer into which a server-side rule is being constructed
+ * @actions: the actions to append to @ba
+ *
+ * Appends @actions to @ba as part of a server-side rule.
+ **/
+void
+e2k_actions_append (GByteArray *ba, GPtrArray *actions)
+{
+	gint actlen_offset, actlen, i;
+
+	/* Save space for length */
+	actlen_offset = ba->len;
+	e2k_rule_append_uint32 (ba, 0);
+
+	e2k_rule_append_uint16 (ba, actions->len);
+	for (i = 0; i < actions->len; i++)
+		append_action (ba, actions->pdata[i]);
+
+	actlen = ba->len - actlen_offset - 4;
+	e2k_rule_write_uint32 (ba->data + actlen_offset, actlen);
+}
diff --git a/server/lib/e2k-action.h b/server/lib/e2k-action.h
new file mode 100644
index 0000000..ae7ccc2
--- /dev/null
+++ b/server/lib/e2k-action.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2002-2004 Novell, Inc. */
+
+#ifndef __E2K_ACTION_H__
+#define __E2K_ACTION_H__
+
+#include "e2k-types.h"
+#include "e2k-properties.h"
+#include "e2k-rule.h"
+
+G_BEGIN_DECLS
+
+gboolean     e2k_actions_extract       (guint8		    **data,
+					gint		     *len,
+					GPtrArray	    **actions);
+void         e2k_actions_append        (GByteArray	     *ba,
+					GPtrArray	     *actions);
+
+E2kAction   *e2k_action_move           (GByteArray	     *store_entryid,
+					GByteArray	     *folder_source_key);
+E2kAction   *e2k_action_copy           (GByteArray	     *store_entryid,
+					GByteArray	     *folder_source_key);
+E2kAction   *e2k_action_reply          (GByteArray	     *template_entryid,
+					guint8	      template_guid[16]);
+E2kAction   *e2k_action_oof_reply      (GByteArray	     *template_entryid,
+					guint8	      template_guid[16]);
+E2kAction   *e2k_action_defer          (GByteArray           *data);
+E2kAction   *e2k_action_bounce         (E2kActionBounceCode   bounce_code);
+E2kAction   *e2k_action_forward        (E2kAddrList	     *list);
+E2kAction   *e2k_action_delegate       (E2kAddrList	     *list);
+E2kAction   *e2k_action_tag            (const gchar	     *propname,
+					E2kPropType	      type,
+					gpointer	      value);
+E2kAction   *e2k_action_delete         (void);
+
+void         e2k_actions_free          (GPtrArray	     *actions);
+void         e2k_action_free           (E2kAction            *act);
+
+E2kAddrList *e2k_addr_list_new         (gint		      nentries);
+void         e2k_addr_list_set_local   (E2kAddrList	     *list,
+					gint		      entry_num,
+					const gchar	     *display_name,
+					const gchar	     *exchange_dn,
+					const gchar	     *email);
+void         e2k_addr_list_set_oneoff  (E2kAddrList	     *list,
+					gint		      entry_num,
+					const gchar	     *display_name,
+					const gchar	     *email);
+void         e2k_addr_list_free        (E2kAddrList	     *list);
+
+G_END_DECLS
+
+#endif /* __E2K_ACTION_H__ */
diff --git a/server/lib/e2k-autoconfig.c b/server/lib/e2k-autoconfig.c
new file mode 100644
index 0000000..f277e2a
--- /dev/null
+++ b/server/lib/e2k-autoconfig.c
@@ -0,0 +1,1750 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2003, 2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* e2k-autoconfig: Automatic account configuration backend code */
+
+/* Note on gtk-doc: Several functions in this file have intentionally-
+ * broken gtk-doc comments (that have only a single "*" after the
+ * opening "/") so that they can be overridden by versions in
+ * docs/reference/tmpl/e2k-autoconfig.sgml that use better markup.
+ * If you change the docs here, be sure to change them there as well.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#ifndef G_OS_WIN32
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <windns.h>
+#ifndef DNS_TYPE_SRV
+#define DNS_TYPE_SRV 33
+#endif
+#endif
+
+#include "e2k-autoconfig.h"
+#include "e2k-context.h"
+#include "e2k-global-catalog.h"
+#include "e2k-propnames.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+#include "e2k-xml-utils.h"
+#include "xntlm.h"
+
+#include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-url.h>
+#include <libedataserverui/e-passwords.h>
+#include <gconf/gconf-client.h>
+#include <libxml/tree.h>
+#include <libxml/HTMLparser.h>
+
+#include <gtk/gtk.h>
+
+#ifdef G_OS_WIN32
+#undef CONNECTOR_PREFIX
+#define CONNECTOR_PREFIX e_util_get_prefix ()
+#endif
+
+static gchar *find_olson_timezone (const gchar *windows_timezone);
+static void set_account_uri_string (E2kAutoconfig *ac);
+
+/**
+ * e2k_autoconfig_new:
+ * @owa_uri: the OWA URI, or %NULL to (try to) use a default
+ * @username: the username (or DOMAIN\username), or %NULL to use a default
+ * @password: the password, or %NULL if not yet known
+ * @auth_pref: information about what auth type to use
+ *
+ * Creates an autoconfig context, based on information stored in the
+ * config file or provided as arguments.
+ *
+ * Return value: an autoconfig context
+ **/
+E2kAutoconfig *
+e2k_autoconfig_new (const gchar *owa_uri, const gchar *username,
+		    const gchar *password, E2kAutoconfigAuthPref auth_pref)
+{
+	E2kAutoconfig *ac;
+
+	ac = g_new0 (E2kAutoconfig, 1);
+
+	if (e2k_autoconfig_lookup_option ("Disable-Plaintext")) {
+		ac->auth_pref = E2K_AUTOCONFIG_USE_NTLM;
+		ac->require_ntlm = TRUE;
+	} else
+		ac->auth_pref = auth_pref;
+
+	e2k_autoconfig_set_owa_uri (ac, owa_uri);
+	/* use same auth for gal as for the server */
+	e2k_autoconfig_set_gc_server (ac, NULL, -1, ac->auth_pref == E2K_AUTOCONFIG_USE_BASIC ? E2K_AUTOCONFIG_USE_GAL_BASIC :
+						    ac->auth_pref == E2K_AUTOCONFIG_USE_NTLM  ? E2K_AUTOCONFIG_USE_GAL_NTLM  :
+						    E2K_AUTOCONFIG_USE_GAL_DEFAULT);
+	e2k_autoconfig_set_username (ac, username);
+	e2k_autoconfig_set_password (ac, password);
+
+	return ac;
+}
+
+/**
+ * e2k_autoconfig_free:
+ * @ac: an autoconfig context
+ *
+ * Frees @ac.
+ **/
+void
+e2k_autoconfig_free (E2kAutoconfig *ac)
+{
+	g_free (ac->owa_uri);
+	g_free (ac->gc_server);
+	g_free (ac->username);
+	g_free (ac->password);
+	g_free (ac->display_name);
+	g_free (ac->email);
+	g_free (ac->account_uri);
+	g_free (ac->exchange_server);
+	g_free (ac->timezone);
+	g_free (ac->nt_domain);
+	g_free (ac->w2k_domain);
+	g_free (ac->home_uri);
+	g_free (ac->exchange_dn);
+	g_free (ac->pf_server);
+
+	g_free (ac);
+}
+
+static void
+reset_gc_derived (E2kAutoconfig *ac)
+{
+	if (ac->display_name) {
+		g_free (ac->display_name);
+		ac->display_name = NULL;
+	}
+	if (ac->email) {
+		g_free (ac->email);
+		ac->email = NULL;
+	}
+	if (ac->account_uri) {
+		g_free (ac->account_uri);
+		ac->account_uri = NULL;
+	}
+}
+
+static void
+reset_owa_derived (E2kAutoconfig *ac)
+{
+	/* Clear the information we explicitly get from OWA */
+	if (ac->timezone) {
+		g_free (ac->timezone);
+		ac->timezone = NULL;
+	}
+	if (ac->exchange_dn) {
+		g_free (ac->exchange_dn);
+		ac->exchange_dn = NULL;
+	}
+	if (ac->pf_server) {
+		g_free (ac->pf_server);
+		ac->pf_server = NULL;
+	}
+	if (ac->home_uri) {
+		g_free (ac->home_uri);
+		ac->home_uri = NULL;
+	}
+
+	/* Reset domain info we may have implicitly got */
+	ac->use_ntlm = (ac->auth_pref != E2K_AUTOCONFIG_USE_BASIC);
+	if (ac->nt_domain_defaulted) {
+		g_free (ac->nt_domain);
+		ac->nt_domain = g_strdup (e2k_autoconfig_lookup_option ("NT-Domain"));
+		ac->nt_domain_defaulted = FALSE;
+	}
+	if (ac->w2k_domain)
+		g_free (ac->w2k_domain);
+	ac->w2k_domain = g_strdup (e2k_autoconfig_lookup_option ("Domain"));
+
+	/* Reset GC-derived information since it depends on the
+	 * OWA-derived information too.
+	 */
+	reset_gc_derived (ac);
+}
+
+/**
+ * e2k_autoconfig_set_owa_uri:
+ * @ac: an autoconfig context
+ * @owa_uri: the new OWA URI, or %NULL
+ *
+ * Sets @ac's #owa_uri field to @owa_uri (or the default if @owa_uri is
+ * %NULL), and resets any fields whose values had been set based on
+ * the old value of #owa_uri.
+ **/
+void
+e2k_autoconfig_set_owa_uri (E2kAutoconfig *ac, const gchar *owa_uri)
+{
+	reset_owa_derived (ac);
+	if (ac->gc_server_autodetected)
+		e2k_autoconfig_set_gc_server (ac, NULL, -1, ac->gal_auth);
+	g_free (ac->owa_uri);
+
+	if (owa_uri) {
+		if (!strncmp (owa_uri, "http", 4))
+			ac->owa_uri = g_strdup (owa_uri);
+		else
+			ac->owa_uri = g_strdup_printf ("http://%s";, owa_uri);
+	} else
+		ac->owa_uri = g_strdup (e2k_autoconfig_lookup_option ("OWA-URL"));
+}
+
+/**
+ * e2k_autoconfig_set_gc_server:
+ * @ac: an autoconfig context
+ * @gc_server: the new GC server, or %NULL
+ * @gal_limit: GAL search size limit, or -1 for no limit
+ * @gal_auth: Preferred authentication method for gal
+ *
+ * Sets @ac's #gc_server field to @gc_server (or the default if
+ * @gc_server is %NULL) and the #gal_limit field to @gal_limit, and
+ * resets any fields whose values had been set based on the old value
+ * of #gc_server.
+ **/
+void
+e2k_autoconfig_set_gc_server (E2kAutoconfig *ac, const gchar *gc_server,
+			      gint gal_limit, E2kAutoconfigGalAuthPref gal_auth)
+{
+	const gchar *default_gal_limit;
+
+	reset_gc_derived (ac);
+	g_free (ac->gc_server);
+
+	if (gc_server)
+		ac->gc_server = g_strdup (gc_server);
+	else
+		ac->gc_server = g_strdup (e2k_autoconfig_lookup_option ("Global-Catalog"));
+	ac->gc_server_autodetected = FALSE;
+
+	if (gal_limit == -1) {
+		default_gal_limit = e2k_autoconfig_lookup_option ("GAL-Limit");
+		if (default_gal_limit)
+			gal_limit = atoi (default_gal_limit);
+	}
+	ac->gal_limit = gal_limit;
+	ac->gal_auth = gal_auth;
+}
+
+/**
+ * e2k_autoconfig_set_username:
+ * @ac: an autoconfig context
+ * @username: the new username (or DOMAIN\username), or %NULL
+ *
+ * Sets @ac's #username field to @username (or the default if
+ * @username is %NULL), and resets any fields whose values had been
+ * set based on the old value of #username.
+ **/
+void
+e2k_autoconfig_set_username (E2kAutoconfig *ac, const gchar *username)
+{
+	gint dlen;
+
+	reset_owa_derived (ac);
+	g_free (ac->username);
+
+	if (username) {
+		/* If the username includes a domain name, split it out */
+		dlen = strcspn (username, "/\\");
+		if (username[dlen]) {
+			g_free (ac->nt_domain);
+			ac->nt_domain = g_strndup (username, dlen);
+			ac->username = g_strdup (username + dlen + 1);
+			ac->nt_domain_defaulted = FALSE;
+		} else
+			ac->username = g_strdup (username);
+	} else
+		ac->username = g_strdup (g_get_user_name ());
+}
+
+/**
+ * e2k_autoconfig_set_password:
+ * @ac: an autoconfig context
+ * @password: the new password, or %NULL to clear
+ *
+ * Sets or clears @ac's #password field.
+ **/
+void
+e2k_autoconfig_set_password (E2kAutoconfig *ac, const gchar *password)
+{
+	g_free (ac->password);
+	ac->password = g_strdup (password);
+}
+
+static void
+get_ctx_auth_handler (SoupMessage *msg, gpointer user_data)
+{
+	E2kAutoconfig *ac = user_data;
+	GSList *headers;
+	const gchar *challenge_hdr;
+
+	ac->saw_ntlm = ac->saw_basic = FALSE;
+	headers = e2k_http_get_headers (msg->response_headers,
+					"WWW-Authenticate");
+	while (headers) {
+		challenge_hdr = headers->data;
+
+		if (!strcmp (challenge_hdr, "NTLM"))
+			ac->saw_ntlm = TRUE;
+		else if (!strncmp (challenge_hdr, "Basic ", 6))
+			ac->saw_basic = TRUE;
+
+		if (!strncmp (challenge_hdr, "NTLM ", 5) &&
+		    (!ac->w2k_domain || !ac->nt_domain)) {
+			guchar *challenge;
+			gsize length = 0;
+
+			challenge = g_base64_decode (challenge_hdr + 5, &length);
+			if (!ac->nt_domain)
+				ac->nt_domain_defaulted = TRUE;
+			xntlm_parse_challenge (challenge, length,
+					       NULL,
+					       ac->nt_domain ? NULL : &ac->nt_domain,
+					       ac->w2k_domain ? NULL : &ac->w2k_domain);
+			g_free (challenge);
+			ac->saw_ntlm = TRUE;
+			g_slist_free (headers);
+			return;
+		}
+
+		headers = headers->next;
+	}
+	g_slist_free (headers);
+}
+
+/*
+ * e2k_autoconfig_get_context:
+ * @ac: an autoconfig context
+ * @op: an #E2kOperation, for cancellation
+ * @result: on output, a result code
+ *
+ * Checks if @ac's URI and authentication parameters work, and if so
+ * returns an #E2kContext using them. On return, * result (which
+ * may not be %NULL) will contain a result code as follows:
+ *
+ *   %E2K_AUTOCONFIG_OK: success
+ *   %E2K_AUTOCONFIG_REDIRECT: The server issued a valid-looking
+ *     redirect. @ac->owa_uri has been updated and the caller
+ *     should try again.
+ *   %E2K_AUTOCONFIG_TRY_SSL: The server requires SSL.
+ *     @ac->owa_uri has been updated and the caller should try
+ *     again.
+ *   %E2K_AUTOCONFIG_AUTH_ERROR: Generic authentication failure.
+ *     Probably password incorrect
+ *   %E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN: Authentication failed.
+ *     Including an NT domain with the username (or using NTLM)
+ *     may fix the problem.
+ *   %E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC: Caller requested NTLM
+ *     auth, but only Basic was available.
+ *   %E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM: Caller requested Basic
+ *     auth, but only NTLM was available.
+ *   %E2K_AUTOCONFIG_EXCHANGE_5_5: Server appears to be Exchange 5.5.
+ *   %E2K_AUTOCONFIG_NOT_EXCHANGE: Server does not appear to be
+ *     any version of Exchange
+ *   %E2K_AUTOCONFIG_NO_OWA: Server may be Exchange 2000, but OWA
+ *     is not present at the given URL.
+ *   %E2K_AUTOCONFIG_NO_MAILBOX: OWA claims the user has no mailbox.
+ *   %E2K_AUTOCONFIG_CANT_RESOLVE: Could not resolve hostname.
+ *   %E2K_AUTOCONFIG_CANT_CONNECT: Could not connect to server.
+ *   %E2K_AUTOCONFIG_CANCELLED: User cancelled
+ *   %E2K_AUTOCONFIG_FAILED: Other error.
+ *
+ * Return value: the new context, or %NULL
+ *
+ * (If you change this comment, see the note at the top of this file.)
+ **/
+E2kContext *
+e2k_autoconfig_get_context (E2kAutoconfig *ac, E2kOperation *op,
+			    E2kAutoconfigResult *result)
+{
+	E2kContext *ctx;
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+	const gchar *ms_webstorage;
+	xmlDoc *doc;
+	xmlNode *node;
+	xmlChar *equiv, *content, *href;
+
+	ctx = e2k_context_new (ac->owa_uri);
+	if (!ctx) {
+		*result = E2K_AUTOCONFIG_FAILED;
+		return NULL;
+	}
+	e2k_context_set_auth (ctx, ac->username, ac->nt_domain,
+			      ac->use_ntlm ? "NTLM" : "Basic", ac->password);
+
+	msg = e2k_soup_message_new (ctx, ac->owa_uri, SOUP_METHOD_GET);
+
+	g_return_val_if_fail (msg != NULL, NULL);
+	g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+	soup_message_headers_append (msg->request_headers, "Accept-Language",
+				     e2k_http_accept_language ());
+	soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
+
+	soup_message_add_status_code_handler (msg, "got-headers",
+					      E2K_HTTP_UNAUTHORIZED,
+					      G_CALLBACK (get_ctx_auth_handler),
+					      ac);
+
+ try_again:
+	e2k_context_send_message (ctx, op, msg);
+	status = msg->status_code;
+
+	/* Check for cancellation or other transport error. */
+	if (E2K_HTTP_STATUS_IS_TRANSPORT_ERROR (status)) {
+		if (status == E2K_HTTP_CANCELLED)
+			*result = E2K_AUTOCONFIG_CANCELLED;
+		else if (status == E2K_HTTP_CANT_RESOLVE)
+			*result = E2K_AUTOCONFIG_CANT_RESOLVE;
+		else
+			*result = E2K_AUTOCONFIG_CANT_CONNECT;
+		goto done;
+	}
+
+	/* Check for an authentication failure. This could be because
+	 * the password is incorrect, or because we used Basic auth
+	 * without specifying a domain and the server doesn't have a
+	 * default domain, or because we tried to use an auth type the
+	 * server doesn't allow.
+	 */
+	if (status == E2K_HTTP_UNAUTHORIZED) {
+		if (!ac->use_ntlm && !ac->nt_domain)
+			*result = E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN;
+		else if (ac->use_ntlm && !ac->saw_ntlm)
+			*result = E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC;
+		else if (!ac->use_ntlm && !ac->saw_basic)
+			*result = E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM;
+		else
+			*result = E2K_AUTOCONFIG_AUTH_ERROR;
+		goto done;
+	}
+
+	/* A redirection to "logon.asp" means this is Exchange 5.5
+	 * OWA. A redirection to "owalogon.asp" means this is Exchange
+	 * 2003 forms-based authentication. A redirection to
+	 * "CookieAuth.dll" means that it's an Exchange 2003 server
+	 * behind an ISA Server 2004 proxy. Other redirections most
+	 * likely indicate that the user's mailbox has been moved to a
+	 * new server.
+	 */
+	if (E2K_HTTP_STATUS_IS_REDIRECTION (status)) {
+		const gchar *location;
+		gchar *new_uri;
+
+		location = soup_message_headers_get (msg->response_headers,
+						     "Location");
+		if (!location) {
+			*result = E2K_AUTOCONFIG_FAILED;
+			goto done;
+		}
+
+		if (strstr (location, "/logon.asp")) {
+			*result = E2K_AUTOCONFIG_EXCHANGE_5_5;
+			goto done;
+		} else if (strstr (location, "/owalogon.asp") ||
+			   strstr (location, "/CookieAuth.dll")) {
+			if (e2k_context_fba (ctx, msg))
+				goto try_again;
+			*result = E2K_AUTOCONFIG_AUTH_ERROR;
+			goto done;
+		}
+
+		new_uri = e2k_strdup_with_trailing_slash (location);
+		e2k_autoconfig_set_owa_uri (ac, new_uri);
+		g_free (new_uri);
+		*result = E2K_AUTOCONFIG_REDIRECT;
+		goto done;
+	}
+
+	/* If the server requires SSL, it will send back 403 Forbidden
+	 * with a body explaining that.
+	 */
+	if (status == E2K_HTTP_FORBIDDEN &&
+	    !strncmp (ac->owa_uri, "http:", 5) && msg->response_body->length > 0) {
+		if (strstr (msg->response_body->data, "SSL")) {
+			gchar *new_uri =
+				g_strconcat ("https:", ac->owa_uri + 5, NULL);
+			e2k_autoconfig_set_owa_uri (ac, new_uri);
+			g_free (new_uri);
+			*result = E2K_AUTOCONFIG_TRY_SSL;
+			goto done;
+		}
+	}
+
+	/* Figure out some stuff about the server */
+	ms_webstorage = soup_message_headers_get (msg->response_headers,
+						  "MS-WebStorage");
+	if (ms_webstorage) {
+		if (!strncmp (ms_webstorage, "6.0.", 4))
+			ac->version = E2K_EXCHANGE_2000;
+		else if (!strncmp (ms_webstorage, "6.5.", 4))
+			ac->version = E2K_EXCHANGE_2003;
+		else
+			ac->version = E2K_EXCHANGE_FUTURE;
+	} else {
+		const gchar *server = soup_message_headers_get (msg->response_headers, "Server");
+
+		/* If the server explicitly claims to be something
+		 * other than IIS, then return the "not windows"
+		 * error.
+		 */
+		if (server && !strstr (server, "IIS")) {
+			*result = E2K_AUTOCONFIG_NOT_EXCHANGE;
+			goto done;
+		}
+
+		/* It's probably Exchange 2000... older versions
+		 * didn't include the MS-WebStorage header here. But
+		 * we don't know for sure.
+		 */
+		ac->version = E2K_EXCHANGE_UNKNOWN;
+	}
+
+	/* If we're talking to OWA, then 404 Not Found means you don't
+	 * have a mailbox. Otherwise, it means you're not talking to
+	 * Exchange (even 5.5).
+	 */
+	if (status == E2K_HTTP_NOT_FOUND) {
+		if (ms_webstorage)
+			*result = E2K_AUTOCONFIG_NO_MAILBOX;
+		else
+			*result = E2K_AUTOCONFIG_NOT_EXCHANGE;
+		goto done;
+	}
+
+	/* Any other error else gets generic failure */
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		*result = E2K_AUTOCONFIG_FAILED;
+		goto done;
+	}
+
+	/* Parse the returned HTML. */
+	doc = e2k_parse_html (msg->response_body->data, msg->response_body->length);
+	if (!doc) {
+		/* Not HTML? */
+		*result = ac->version == E2K_EXCHANGE_UNKNOWN ?
+			E2K_AUTOCONFIG_NO_OWA :
+			E2K_AUTOCONFIG_FAILED;
+		goto done;
+	}
+
+	/* Make sure it's not Exchange 5.5 */
+	if (ac->version == E2K_EXCHANGE_UNKNOWN &&
+	    strstr (ac->owa_uri, "/logon.asp")) {
+		*result = E2K_AUTOCONFIG_EXCHANGE_5_5;
+		goto done;
+	}
+
+	/* Make sure it's not trying to redirect us to Exchange 5.5 */
+	for (node = doc->children; node; node = e2k_xml_find (node, "meta")) {
+		gboolean ex55 = FALSE;
+
+		equiv = xmlGetProp (node, (xmlChar *) "http-equiv");
+		content = xmlGetProp (node, (xmlChar *) "content");
+		if (equiv && content &&
+		    !g_ascii_strcasecmp ((gchar *) equiv, "REFRESH") &&
+		    xmlStrstr (content, (xmlChar *) "/logon.asp"))
+			ex55 = TRUE;
+		if (equiv)
+			xmlFree (equiv);
+		if (content)
+			xmlFree (content);
+
+		if (ex55) {
+			*result = E2K_AUTOCONFIG_EXCHANGE_5_5;
+			goto done;
+		}
+	}
+
+	/* Try to find the base URI */
+	node = e2k_xml_find (doc->children, "base");
+	if (node) {
+		/* We won */
+		*result = E2K_AUTOCONFIG_OK;
+		href = xmlGetProp (node, (xmlChar *) "href");
+		g_free (ac->home_uri);
+		ac->home_uri = g_strdup ((gchar *) href);
+		xmlFree (href);
+	} else
+		*result = E2K_AUTOCONFIG_FAILED;
+	xmlFreeDoc (doc);
+
+ done:
+	g_object_unref (msg);
+
+	if (*result != E2K_AUTOCONFIG_OK) {
+		g_object_unref (ctx);
+		ctx = NULL;
+	}
+	return ctx;
+}
+
+static const gchar *home_properties[] = {
+	PR_STORE_ENTRYID,
+	E2K_PR_EXCHANGE_TIMEZONE
+};
+static const gint n_home_properties = sizeof (home_properties) / sizeof (home_properties[0]);
+
+/*
+ * e2k_autoconfig_check_exchange:
+ * @ac: an autoconfiguration context
+ * @op: an #E2kOperation, for cancellation
+ *
+ * Tries to connect to the the Exchange server using the OWA URL,
+ * username, and password in @ac. Attempts to determine the domain
+ * name and home_uri, and then given the home_uri, looks up the
+ * user's mailbox entryid (used to find his Exchange 5.5 DN) and
+ * default timezone.
+ *
+ * The returned codes are the same as for e2k_autoconfig_get_context()
+ * with the following changes/additions/removals:
+ *
+ *   %E2K_AUTOCONFIG_REDIRECT: URL returned in first redirect returned
+ *     another redirect, which was not followed.
+ *   %E2K_AUTOCONFIG_CANT_BPROPFIND: The server does not allow
+ *     BPROPFIND due to IIS Lockdown configuration
+ *   %E2K_AUTOCONFIG_TRY_SSL: Not used; always handled internally by
+ *     e2k_autoconfig_check_exchange()
+ *
+ * Return value: an #E2kAutoconfigResult
+ *
+ * (If you change this comment, see the note at the top of this file.)
+ **/
+E2kAutoconfigResult
+e2k_autoconfig_check_exchange (E2kAutoconfig *ac, E2kOperation *op)
+{
+	xmlDoc *doc;
+	xmlNode *node;
+	E2kHTTPStatus status;
+	E2kAutoconfigResult result;
+	gchar *new_uri, *pf_uri;
+	E2kContext *ctx;
+	gboolean redirected = FALSE;
+	E2kResultIter *iter;
+	E2kResult *results;
+	GByteArray *entryid;
+	const gchar *exchange_dn, *timezone, *hrefs[] = { "" };
+	xmlChar *prop;
+	SoupBuffer *response;
+	E2kUri *euri;
+
+	g_return_val_if_fail (ac->owa_uri != NULL, E2K_AUTOCONFIG_FAILED);
+	g_return_val_if_fail (ac->username != NULL, E2K_AUTOCONFIG_FAILED);
+	g_return_val_if_fail (ac->password != NULL, E2K_AUTOCONFIG_FAILED);
+
+ try_again:
+	ctx = e2k_autoconfig_get_context (ac, op, &result);
+
+	switch (result) {
+	case E2K_AUTOCONFIG_OK:
+		break;
+
+	case E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC:
+		if (ac->use_ntlm && !ac->require_ntlm) {
+			ac->use_ntlm = FALSE;
+			goto try_again;
+		} else
+			return E2K_AUTOCONFIG_AUTH_ERROR;
+
+	case E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM:
+		return E2K_AUTOCONFIG_AUTH_ERROR;
+
+	case E2K_AUTOCONFIG_REDIRECT:
+		if (!redirected) {
+			redirected = TRUE;
+			goto try_again;
+		} else
+			return result;
+
+	case E2K_AUTOCONFIG_TRY_SSL:
+		goto try_again;
+
+	case E2K_AUTOCONFIG_NO_OWA:
+	default:
+		/* If the provided OWA URI had no path, try appending
+		 * /exchange.
+		 */
+		euri = e2k_uri_new (ac->owa_uri);
+		g_return_val_if_fail (euri != NULL, result);
+		if (!euri->path || !strcmp (euri->path, "/")) {
+			e2k_uri_free (euri);
+			new_uri = e2k_uri_concat (ac->owa_uri, "exchange/");
+			e2k_autoconfig_set_owa_uri (ac, new_uri);
+			g_free (new_uri);
+			goto try_again;
+		}
+		e2k_uri_free (euri);
+		return result;
+	}
+
+	/* Find the link to the public folders */
+	if (ac->version < E2K_EXCHANGE_2003)
+		pf_uri = g_strdup_printf ("%s/?Cmd=contents", ac->owa_uri);
+	else
+		pf_uri = g_strdup_printf ("%s/?Cmd=navbar", ac->owa_uri);
+
+	status = e2k_context_get_owa (ctx, NULL, pf_uri, FALSE, &response);
+	g_free (pf_uri);
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		doc = e2k_parse_html (response->data, response->length);
+		soup_buffer_free (response);
+	} else
+		doc = NULL;
+
+	if (doc) {
+		for (node = e2k_xml_find (doc->children, "img"); node; node = e2k_xml_find (node, "img")) {
+			prop = xmlGetProp (node, (xmlChar *) "src");
+			if (prop && xmlStrstr (prop, (xmlChar *) "public") && node->parent) {
+				node = node->parent;
+				xmlFree (prop);
+				prop = xmlGetProp (node, (xmlChar *) "href");
+				if (prop) {
+					euri = e2k_uri_new ((gchar *) prop);
+					ac->pf_server = g_strdup (euri->host);
+					e2k_uri_free (euri);
+					xmlFree (prop);
+				}
+				break;
+			}
+		}
+		xmlFreeDoc (doc);
+	} else
+		g_warning ("Could not parse pf page");
+
+	/* Now find the store entryid and default timezone. We
+	 * gratuitously use BPROPFIND in order to test if they
+	 * have the IIS Lockdown problem.
+	 */
+	iter = e2k_context_bpropfind_start (ctx, op,
+					    ac->home_uri, hrefs, 1,
+					    home_properties,
+					    n_home_properties);
+	results = e2k_result_iter_next (iter);
+	if (results) {
+		timezone = e2k_properties_get_prop (results->props,
+						    E2K_PR_EXCHANGE_TIMEZONE);
+		if (timezone)
+			ac->timezone = find_olson_timezone (timezone);
+
+		entryid = e2k_properties_get_prop (results->props,
+						   PR_STORE_ENTRYID);
+		if (entryid) {
+			exchange_dn = e2k_entryid_to_dn (entryid);
+			if (exchange_dn)
+				ac->exchange_dn = g_strdup (exchange_dn);
+		}
+	}
+	status = e2k_result_iter_free (iter);
+	g_object_unref (ctx);
+
+	if (status == E2K_HTTP_UNAUTHORIZED) {
+		if (ac->use_ntlm && !ac->require_ntlm) {
+			ac->use_ntlm = FALSE;
+			goto try_again;
+		} else
+			return E2K_AUTOCONFIG_AUTH_ERROR;
+	} else if (status == E2K_HTTP_NOT_FOUND)
+		return E2K_AUTOCONFIG_CANT_BPROPFIND;
+	else if (status == E2K_HTTP_CANCELLED)
+		return E2K_AUTOCONFIG_CANCELLED;
+	else if (status == E2K_HTTP_CANT_RESOLVE)
+		return E2K_AUTOCONFIG_CANT_RESOLVE;
+	else if (E2K_HTTP_STATUS_IS_TRANSPORT_ERROR (status))
+		return E2K_AUTOCONFIG_CANT_CONNECT;
+	else if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status))
+		return E2K_AUTOCONFIG_FAILED;
+
+	return ac->exchange_dn ? E2K_AUTOCONFIG_OK : E2K_AUTOCONFIG_FAILED;
+}
+
+/* FIXME: make this cancellable */
+static void
+find_global_catalog (E2kAutoconfig *ac)
+{
+#ifndef G_OS_WIN32
+	gint count, len;
+	guchar answer[1024], namebuf[1024], *end, *p;
+	guint16 type, qclass, rdlength, priority, weight, port;
+	guint32 ttl;
+	HEADER *header;
+
+	if (!ac->w2k_domain)
+		return;
+
+	len = res_querydomain ("_gc._tcp", ac->w2k_domain, C_IN, T_SRV,
+			       answer, sizeof (answer));
+	if (len == -1)
+		return;
+
+	header = (HEADER *)answer;
+	p = answer + sizeof (HEADER);
+	end = answer + len;
+
+	/* See RFCs 1035 and 2782 for details of the parsing */
+
+	/* Skip query */
+	count = ntohs (header->qdcount);
+	while (count-- && p < end) {
+		p += dn_expand (answer, end, p, (gchar *) namebuf, sizeof (namebuf));
+		p += 4;
+	}
+
+	/* Read answers */
+	while (count-- && p < end) {
+		p += dn_expand (answer, end, p, (gchar *) namebuf, sizeof (namebuf));
+		GETSHORT (type, p);
+		GETSHORT (qclass, p);
+		GETLONG (ttl, p);
+		GETSHORT (rdlength, p);
+
+		if (type != T_SRV || qclass != C_IN) {
+			p += rdlength;
+			continue;
+		}
+
+		GETSHORT (priority, p);
+		GETSHORT (weight, p);
+		GETSHORT (port, p);
+		p += dn_expand (answer, end, p, (gchar *) namebuf, sizeof (namebuf));
+
+		/* FIXME: obey priority and weight */
+		ac->gc_server = g_strdup ((gchar *) namebuf);
+		ac->gc_server_autodetected = TRUE;
+		return;
+	}
+
+	return;
+#else
+	gchar *name, *casefolded_name;
+	PDNS_RECORD dnsrecp, rover;
+
+	name = g_strconcat ("_gc._tcp.", ac->w2k_domain, NULL);
+	casefolded_name = g_utf8_strdown (name, -1);
+	g_free (name);
+
+	if (DnsQuery_UTF8 (casefolded_name, DNS_TYPE_SRV, DNS_QUERY_STANDARD,
+			   NULL, &dnsrecp, NULL) != ERROR_SUCCESS) {
+		g_free (casefolded_name);
+		return;
+	}
+
+	for (rover = dnsrecp; rover != NULL; rover = rover->pNext) {
+		if (rover->wType != DNS_TYPE_SRV ||
+		    strcmp (rover->pName, casefolded_name) != 0)
+			continue;
+		ac->gc_server = g_strdup (rover->Data.SRV.pNameTarget);
+		ac->gc_server_autodetected = TRUE;
+		g_free (casefolded_name);
+		DnsRecordListFree (dnsrecp, DnsFreeRecordList);
+		return;
+	}
+
+	g_free (casefolded_name);
+	DnsRecordListFree (dnsrecp, DnsFreeRecordList);
+	return;
+#endif
+}
+
+/**
+ * e2k_autoconfig_get_global_catalog
+ * @ac: an autoconfig context
+ * @op: an #E2kOperation, for cancellation
+ *
+ * Tries to connect to the global catalog associated with @ac
+ * (trying to figure it out from the domain name if the server
+ * name is not yet known).
+ *
+ * Return value: the global catalog, or %NULL if the GC server name
+ * wasn't provided and couldn't be autodetected.
+ */
+E2kGlobalCatalog *
+e2k_autoconfig_get_global_catalog (E2kAutoconfig *ac, E2kOperation *op)
+{
+	if (!ac->gc_server) {
+		find_global_catalog (ac);
+		if (!ac->gc_server)
+			return NULL;
+	}
+
+	return e2k_global_catalog_new (ac->gc_server, ac->gal_limit,
+				       ac->username, ac->nt_domain,
+				       ac->password, ac->gal_auth);
+}
+
+/*
+ * e2k_autoconfig_check_global_catalog
+ * @ac: an autoconfig context
+ * @op: an #E2kOperation, for cancellation
+ *
+ * Tries to connect to the global catalog associated with @ac
+ * (trying to figure it out from the domain name if the server
+ * name is not yet known). On success it will look up the user's
+ * full name and email address (based on his Exchange DN).
+ *
+ * Possible return values are:
+ *
+ *   %E2K_AUTOCONFIG_OK: Success
+ *   %E2K_AUTOCONFIG_CANT_RESOLVE: Could not determine GC server
+ *   %E2K_AUTOCONFIG_NO_MAILBOX: Could not find information for
+ *     the user
+ *   %E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN: Plaintext password auth
+ *     failed: need to specify NT domain
+ *   %E2K_AUTOCONFIG_CANCELLED: Operation was cancelled
+ *   %E2K_AUTOCONFIG_FAILED: Other error.
+ *
+ * Return value: an #E2kAutoconfigResult.
+ *
+ * (If you change this comment, see the note at the top of this file.)
+ */
+E2kAutoconfigResult
+e2k_autoconfig_check_global_catalog (E2kAutoconfig *ac, E2kOperation *op)
+{
+	E2kGlobalCatalog *gc;
+	E2kGlobalCatalogEntry *entry;
+	E2kGlobalCatalogStatus status;
+	E2kAutoconfigResult result;
+
+	g_return_val_if_fail (ac->exchange_dn != NULL, E2K_AUTOCONFIG_FAILED);
+
+	gc = e2k_autoconfig_get_global_catalog (ac, op);
+	if (!gc)
+		return E2K_AUTOCONFIG_CANT_RESOLVE;
+
+	set_account_uri_string (ac);
+
+	status = e2k_global_catalog_lookup (
+		gc, op, E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN,
+		ac->exchange_dn, E2K_GLOBAL_CATALOG_LOOKUP_EMAIL |
+		E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX, &entry);
+
+	if (status == E2K_GLOBAL_CATALOG_OK) {
+		ac->display_name = g_strdup (entry->display_name);
+		ac->email = g_strdup (entry->email);
+		result = E2K_AUTOCONFIG_OK;
+	} else if (status == E2K_GLOBAL_CATALOG_CANCELLED)
+		result = E2K_AUTOCONFIG_CANCELLED;
+#ifndef HAVE_LDAP_NTLM_BIND
+	else if (status == E2K_GLOBAL_CATALOG_AUTH_FAILED &&
+		 !ac->nt_domain)
+		result = E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN;
+#endif
+	else if (status == E2K_GLOBAL_CATALOG_ERROR)
+		result = E2K_AUTOCONFIG_FAILED;
+	else
+		result = E2K_AUTOCONFIG_NO_MAILBOX;
+
+	g_object_unref (gc);
+	return result;
+}
+
+static void
+set_account_uri_string (E2kAutoconfig *ac)
+{
+	E2kUri *owa_uri, *home_uri;
+	gchar *path, *mailbox;
+	GString *uri;
+
+	owa_uri = e2k_uri_new (ac->owa_uri);
+	home_uri = e2k_uri_new (ac->home_uri);
+
+	uri = g_string_new ("exchange://");
+	if (ac->nt_domain && (!ac->use_ntlm || !ac->nt_domain_defaulted)) {
+		e2k_uri_append_encoded (uri, ac->nt_domain, FALSE, "\\;:@/");
+		g_string_append_c (uri, '\\');
+	}
+	e2k_uri_append_encoded (uri, ac->username, FALSE, ";:@/");
+
+	if (!ac->use_ntlm)
+		g_string_append (uri, ";auth=Basic");
+
+	g_string_append_c (uri, '@');
+	e2k_uri_append_encoded (uri, owa_uri->host, FALSE, ":/");
+	if (owa_uri->port)
+		g_string_append_printf (uri, ":%d", owa_uri->port);
+	g_string_append_c (uri, '/');
+
+	if (!strcmp (owa_uri->protocol, "https"))
+		g_string_append (uri, ";use_ssl=always");
+	g_string_append (uri, ";ad_server=");
+	e2k_uri_append_encoded (uri, ac->gc_server, FALSE, ";?");
+	if (ac->gal_limit != -1)
+		g_string_append_printf (uri, ";ad_limit=%d", ac->gal_limit);
+	if (ac->gal_auth != E2K_AUTOCONFIG_USE_GAL_DEFAULT) {
+		const gchar *value = NULL;
+
+		switch (ac->gal_auth) {
+		case E2K_AUTOCONFIG_USE_GAL_BASIC: value = "basic"; break;
+		case E2K_AUTOCONFIG_USE_GAL_NTLM:  value = "ntlm";  break;
+		case E2K_AUTOCONFIG_USE_GAL_DEFAULT: /* should not get here */ break;
+		}
+
+		if (value)
+			g_string_append_printf (uri, ";ad_auth=%s", value);
+	}
+
+	path = g_strdup (home_uri->path + 1);
+	mailbox = strrchr (path, '/');
+	if (mailbox && !mailbox[1]) {
+		*mailbox = '\0';
+		mailbox = strrchr (path, '/');
+	}
+	if (mailbox) {
+		*mailbox++ = '\0';
+		g_string_append (uri, ";mailbox=");
+		e2k_uri_append_encoded (uri, mailbox, FALSE, ";?");
+	}
+	g_string_append (uri, ";owa_path=/");
+	e2k_uri_append_encoded (uri, path, FALSE, ";?");
+	g_free (path);
+
+	g_string_append (uri, ";pf_server=");
+	e2k_uri_append_encoded (uri, ac->pf_server ? ac->pf_server : home_uri->host, FALSE, ";?");
+
+	ac->account_uri = uri->str;
+	ac->exchange_server = g_strdup (home_uri->host);
+	g_string_free (uri, FALSE);
+	e2k_uri_free (home_uri);
+	e2k_uri_free (owa_uri);
+}
+
+/* Approximate mapping from Exchange timezones to Olson ones. Exchange
+ * is less specific, so we factor in the language/country info from
+ * the locale in our guess.
+ *
+ * We strip " Standard Time" / " Daylight Time" from the Windows
+ * timezone names. (Actually, we just strip the last two words.)
+ */
+static struct {
+	const gchar *windows_name, *lang, *country, *olson_name;
+} zonemap[] = {
+	/* (GMT-12:00) Eniwetok, Kwajalein */
+	{ "Dateline", NULL, NULL, "Pacific/Kwajalein" },
+
+	/* (GMT-11:00) Midway Island, Samoa */
+	{ "Samoa", NULL, NULL, "Pacific/Midway" },
+
+	/* (GMT-10:00) Hawaii */
+	{ "Hawaiian", NULL, NULL, "Pacific/Honolulu" },
+
+	/* (GMT-09:00) Alaska */
+	{ "Alaskan", NULL, NULL, "America/Juneau" },
+
+	/* (GMT-08:00) Pacific Time (US & Canada); Tijuana */
+	{ "Pacific", NULL, "CA", "America/Vancouver" },
+	{ "Pacific", "es", "MX", "America/Tijuana" },
+	{ "Pacific", NULL, NULL, "America/Los_Angeles" },
+
+	/* (GMT-07:00) Arizona */
+	{ "US Mountain", NULL, NULL, "America/Phoenix" },
+
+	/* (GMT-07:00) Mountain Time (US & Canada) */
+	{ "Mountain", NULL, "CA", "America/Edmonton" },
+	{ "Mountain", NULL, NULL, "America/Denver" },
+
+	/* (GMT-06:00) Central America */
+	{ "Central America", NULL, "BZ", "America/Belize" },
+	{ "Central America", NULL, "CR", "America/Costa_Rica" },
+	{ "Central America", NULL, "GT", "America/Guatemala" },
+	{ "Central America", NULL, "HN", "America/Tegucigalpa" },
+	{ "Central America", NULL, "NI", "America/Managua" },
+	{ "Central America", NULL, "SV", "America/El_Salvador" },
+
+	/* (GMT-06:00) Central Time (US & Canada) */
+	{ "Central", NULL, NULL, "America/Chicago" },
+
+	/* (GMT-06:00) Mexico City */
+	{ "Mexico", NULL, NULL, "America/Mexico_City" },
+
+	/* (GMT-06:00) Saskatchewan */
+	{ "Canada Central", NULL, NULL, "America/Regina" },
+
+	/* (GMT-05:00) Bogota, Lima, Quito */
+	{ "SA Pacific", NULL, "BO", "America/Bogota" },
+	{ "SA Pacific", NULL, "EC", "America/Guayaquil" },
+	{ "SA Pacific", NULL, "PA", "America/Panama" },
+	{ "SA Pacific", NULL, "PE", "America/Lima" },
+
+	/* (GMT-05:00) Eastern Time (US & Canada) */
+	{ "Eastern", "fr", "CA", "America/Montreal" },
+	{ "Eastern", NULL, NULL, "America/New_York" },
+
+	/* (GMT-05:00) Indiana (East) */
+	{ "US Eastern", NULL, NULL, "America/Indiana/Indianapolis" },
+
+	/* (GMT-04:00) Atlantic Time (Canada) */
+	{ "Atlantic", "es", "US", "America/Puerto_Rico" },
+	{ "Atlantic", NULL, "VI", "America/St_Thomas" },
+	{ "Atlantic", NULL, "CA", "America/Halifax" },
+
+	/* (GMT-04:00) Caracas, La Paz */
+	{ "SA Western", NULL, "BO", "America/La_Paz" },
+	{ "SA Western", NULL, "VE", "America/Caracas" },
+
+	/* (GMT-04:00) Santiago */
+	{ "Pacific SA", NULL, NULL, "America/Santiago" },
+
+	/* (GMT-03:30) Newfoundland */
+	{ "Newfoundland", NULL, NULL, "America/St_Johns" },
+
+	/* (GMT-03:00) Brasilia */
+	{ "E. South America", NULL, NULL, "America/Sao_Paulo" },
+
+	/* (GMT-03:00) Greenland */
+	{ "Greenland", NULL, NULL, "America/Godthab" },
+
+	/* (GMT-03:00) Buenos Aires, Georgetown */
+	{ "SA Eastern", NULL, NULL, "America/Buenos_Aires" },
+
+	/* (GMT-02:00) Mid-Atlantic */
+	{ "Mid-Atlantic", NULL, NULL, "America/Noronha" },
+
+	/* (GMT-01:00) Azores */
+	{ "Azores", NULL, NULL, "Atlantic/Azores" },
+
+	/* (GMT-01:00) Cape Verde Is. */
+	{ "Cape Verde", NULL, NULL, "Atlantic/Cape_Verde" },
+
+	/* (GMT) Casablanca, Monrovia */
+	{ "Greenwich", NULL, "LR", "Africa/Monrovia" },
+	{ "Greenwich", NULL, "MA", "Africa/Casablanca" },
+
+	/* (GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London */
+	{ "GMT", "ga", "IE", "Europe/Dublin" },
+	{ "GMT", "pt", "PT", "Europe/Lisbon" },
+	{ "GMT", NULL, NULL, "Europe/London" },
+
+	/* (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */
+	{ "W. Europe", "nl", "NL", "Europe/Amsterdam" },
+	{ "W. Europe", "it", "IT", "Europe/Rome" },
+	{ "W. Europe", "sv", "SE", "Europe/Stockholm" },
+	{ "W. Europe", NULL, "CH", "Europe/Zurich" },
+	{ "W. Europe", NULL, "AT", "Europe/Vienna" },
+	{ "W. Europe", "de", "DE", "Europe/Berlin" },
+
+	/* (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */
+	{ "Central Europe", "sr", "YU", "Europe/Belgrade" },
+	{ "Central Europe", "sk", "SK", "Europe/Bratislava" },
+	{ "Central Europe", "hu", "HU", "Europe/Budapest" },
+	{ "Central Europe", "sl", "SI", "Europe/Ljubljana" },
+	{ "Central Europe", "cz", "CZ", "Europe/Prague" },
+
+	/* (GMT+01:00) Brussels, Copenhagen, Madrid, Paris */
+	{ "Romance", NULL, "BE", "Europe/Brussels" },
+	{ "Romance", "da", "DK", "Europe/Copenhagen" },
+	{ "Romance", "es", "ES", "Europe/Madrid" },
+	{ "Romance", "fr", "FR", "Europe/Paris" },
+
+	/* (GMT+01:00) Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb */
+	{ "Central European", "bs", "BA", "Europe/Sarajevo" },
+	{ "Central European", "mk", "MK", "Europe/Skopje" },
+	{ "Central European", "bg", "BG", "Europe/Sofia" },
+	{ "Central European", "lt", "LT", "Europe/Vilnius" },
+	{ "Central European", "pl", "PL", "Europe/Warsaw" },
+	{ "Central European", "hr", "HR", "Europe/Zagreb" },
+
+	/* (GMT+01:00) West Central Africa */
+	{ "W. Central Africa", NULL, NULL, "Africa/Kinshasa" },
+
+	/* (GMT+02:00) Athens, Istanbul, Minsk */
+	{ "GTB", "el", "GR", "Europe/Athens" },
+	{ "GTB", "tr", "TR", "Europe/Istanbul" },
+	{ "GTB", "be", "BY", "Europe/Minsk" },
+
+	/* (GMT+02:00) Bucharest */
+	{ "E. Europe", NULL, NULL, "Europe/Bucharest" },
+
+	/* (GMT+02:00) Cairo */
+	{ "Egypt", NULL, NULL, "Africa/Cairo" },
+
+	/* (GMT+02:00) Harare, Pretoria */
+	{ "South Africa", NULL, NULL, "Africa/Johannesburg" },
+
+	/* (GMT+02:00) Helsinki, Riga, Tallinn */
+	{ "FLE", "lv", "LV", "Europe/Riga" },
+	{ "FLE", "et", "EE", "Europe/Tallinn" },
+	{ "FLE", "fi", "FI", "Europe/Helsinki" },
+
+	/* (GMT+02:00) Jerusalem */
+	{ "Israel", NULL, NULL, "Asia/Jerusalem" },
+
+	/* (GMT+03:00) Baghdad */
+	{ "Arabic", NULL, NULL, "Asia/Baghdad" },
+
+	/* (GMT+03:00) Kuwait, Riyadh */
+	{ "Arab", NULL, "KW", "Asia/Kuwait" },
+	{ "Arab", NULL, "SA", "Asia/Riyadh" },
+
+	/* (GMT+03:00) Moscow, St. Petersburg, Volgograd */
+	{ "Russian", NULL, NULL, "Europe/Moscow" },
+
+	/* (GMT+03:00) Nairobi */
+	{ "E. Africa", NULL, NULL, "Africa/Nairobi" },
+
+	/* (GMT+03:30) Tehran */
+	{ "Iran", NULL, NULL, "Asia/Tehran" },
+
+	/* (GMT+04:00) Abu Dhabi, Muscat */
+	{ "Arabian", NULL, NULL, "Asia/Muscat" },
+
+	/* (GMT+04:00) Baku, Tbilisi, Yerevan */
+	{ "Caucasus", NULL, NULL, "Asia/Baku" },
+
+	/* (GMT+04:30) Kabul */
+	{ "Afghanistan", NULL, NULL, "Asia/Kabul" },
+
+	/* (GMT+05:00) Ekaterinburg */
+	{ "Ekaterinburg", NULL, NULL, "Asia/Yekaterinburg" },
+
+	/* (GMT+05:00) Islamabad, Karachi, Tashkent */
+	{ "West Asia", NULL, NULL, "Asia/Karachi" },
+
+	/* (GMT+05:30) Kolkata, Chennai, Mumbai, New Delhi */
+	{ "India", NULL, NULL, "Asia/Calcutta" },
+
+	/* (GMT+05:45) Kathmandu */
+	{ "Nepal", NULL, NULL, "Asia/Katmandu" },
+
+	/* (GMT+06:00) Almaty, Novosibirsk */
+	{ "N. Central Asia", NULL, NULL, "Asia/Almaty" },
+
+	/* (GMT+06:00) Astana, Dhaka */
+	{ "Central Asia", NULL, NULL, "Asia/Dhaka" },
+
+	/* (GMT+06:00) Sri Jayawardenepura */
+	{ "Sri Lanka", NULL, NULL, "Asia/Colombo" },
+
+	/* (GMT+06:30) Rangoon */
+	{ "Myanmar", NULL, NULL, "Asia/Rangoon" },
+
+	/* (GMT+07:00) Bangkok, Hanoi, Jakarta */
+	{ "SE Asia", "th", "TH", "Asia/Bangkok" },
+	{ "SE Asia", "vi", "VN", "Asia/Saigon" },
+	{ "SE Asia", "id", "ID", "Asia/Jakarta" },
+
+	/* (GMT+07:00) Krasnoyarsk */
+	{ "North Asia", NULL, NULL, "Asia/Krasnoyarsk" },
+
+	/* (GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi */
+	{ "China", NULL, "HK", "Asia/Hong_Kong" },
+	{ "China", NULL, NULL, "Asia/Shanghai" },
+
+	/* (GMT+08:00) Irkutsk, Ulaan Bataar */
+	{ "North Asia East", NULL, NULL, "Asia/Irkutsk" },
+
+	/* (GMT+08:00) Perth */
+	{ "W. Australia", NULL, NULL, "Australia/Perth" },
+
+	/* (GMT+08:00) Kuala Lumpur, Singapore */
+	{ "Singapore", NULL, NULL, "Asia/Kuala_Lumpur" },
+
+	/* (GMT+08:00) Taipei */
+	{ "Taipei", NULL, NULL, "Asia/Taipei" },
+
+	/* (GMT+09:00) Osaka, Sapporo, Tokyo */
+	{ "Tokyo", NULL, NULL, "Asia/Tokyo" },
+
+	/* (GMT+09:00) Seoul */
+	{ "Korea", NULL, "KP", "Asia/Pyongyang" },
+	{ "Korea", NULL, "KR", "Asia/Seoul" },
+
+	/* (GMT+09:00) Yakutsk */
+	{ "Yakutsk", NULL, NULL, "Asia/Yakutsk" },
+
+	/* (GMT+09:30) Adelaide */
+	{ "Cen. Australia", NULL, NULL, "Australia/Adelaide" },
+
+	/* (GMT+09:30) Darwin */
+	{ "AUS Central", NULL, NULL, "Australia/Darwin" },
+
+	/* (GMT+10:00) Brisbane */
+	{ "E. Australia", NULL, NULL, "Australia/Brisbane" },
+
+	/* (GMT+10:00) Canberra, Melbourne, Sydney */
+	{ "AUS Eastern", NULL, NULL, "Australia/Sydney" },
+
+	/* (GMT+10:00) Guam, Port Moresby */
+	{ "West Pacific", NULL, NULL, "Pacific/Guam" },
+
+	/* (GMT+10:00) Hobart */
+	{ "Tasmania", NULL, NULL, "Australia/Hobart" },
+
+	/* (GMT+10:00) Vladivostok */
+	{ "Vladivostok", NULL, NULL, "Asia/Vladivostok" },
+
+	/* (GMT+11:00) Magadan, Solomon Is., New Caledonia */
+	{ "Central Pacific", NULL, NULL, "Pacific/Midway" },
+
+	/* (GMT+12:00) Auckland, Wellington */
+	{ "New Zealand", NULL, NULL, "Pacific/Auckland" },
+
+	/* (GMT+12:00) Fiji, Kamchatka, Marshall Is. */
+	{ "Fiji", "ru", "RU", "Asia/Kamchatka" },
+	{ "Fiji", NULL, NULL, "Pacific/Fiji" },
+
+	/* (GMT+13:00) Nuku'alofa */
+	{ "Tonga", NULL, NULL, "Pacific/Tongatapu" }
+};
+static const gint n_zone_mappings = sizeof (zonemap) / sizeof (zonemap[0]);
+
+static gchar *
+find_olson_timezone (const gchar *windows_timezone)
+{
+	gint i, tzlen;
+	const gchar *locale, *p;
+	gchar lang[3] = { 0 }, country[3] = { 0 };
+
+	/* Strip " Standard Time" / " Daylight Time" from name */
+	p = windows_timezone + strlen (windows_timezone) - 1;
+	while (p > windows_timezone && *p-- != ' ')
+		;
+	while (p > windows_timezone && *p-- != ' ')
+		;
+	tzlen = p - windows_timezone + 1;
+
+	/* Find the first entry in zonemap with a matching name */
+	for (i = 0; i < n_zone_mappings; i++) {
+		if (!g_ascii_strncasecmp (windows_timezone,
+					  zonemap[i].windows_name,
+					  tzlen))
+			break;
+	}
+	if (i == n_zone_mappings)
+		return NULL; /* Shouldn't happen... */
+
+	/* If there's only one choice, go with it */
+	if (!zonemap[i].lang && !zonemap[i].country)
+		return g_strdup (zonemap[i].olson_name);
+
+	/* Find our language/country (hopefully). */
+#ifndef G_OS_WIN32
+	locale = getenv ("LANG");
+#else
+	locale = g_win32_getlocale ();
+#endif
+	if (locale) {
+		strncpy (lang, locale, 2);
+		locale = strchr (locale, '_');
+		if (locale++)
+			strncpy (country, locale, 2);
+	}
+#ifdef G_OS_WIN32
+	g_free ((gchar *) locale);
+#endif
+
+	/* Look for an entry where either the country or the
+	 * language matches.
+	 */
+	do {
+		if ((zonemap[i].lang && !strcmp (zonemap[i].lang, lang)) ||
+		    (zonemap[i].country && !strcmp (zonemap[i].country, country)))
+			return g_strdup (zonemap[i].olson_name);
+	} while (++i < n_zone_mappings &&
+		 !g_ascii_strncasecmp (windows_timezone,
+				       zonemap[i].windows_name,
+				       tzlen));
+
+	/* None of the hints matched, so (semi-arbitrarily) return the
+	 * last of the entries with the right Windows timezone name.
+	 */
+	return g_strdup (zonemap[i - 1].olson_name);
+}
+
+/* Config file handling */
+
+static GHashTable *config_options;
+
+static void
+read_config (void)
+{
+	struct stat st;
+	gchar *p, *name, *value;
+	gchar *config_data;
+	gint fd = -1;
+
+	config_options = g_hash_table_new (e2k_ascii_strcase_hash,
+					    e2k_ascii_strcase_equal);
+
+#ifndef G_OS_WIN32
+	fd = g_open ("/etc/ximian/connector.conf", O_RDONLY, 0);
+#endif
+	if (fd == -1) {
+		gchar *filename = g_build_filename (CONNECTOR_PREFIX,
+						    "etc/connector.conf",
+						    NULL);
+
+		fd = g_open (filename, O_RDONLY, 0);
+		g_free (filename);
+	}
+	if (fd == -1)
+		return;
+	if (fstat (fd, &st) == -1) {
+		g_warning ("Could not stat connector.conf: %s",
+			   g_strerror (errno));
+		close (fd);
+		return;
+	}
+
+	config_data = g_malloc (st.st_size + 1);
+	if (read (fd, config_data, st.st_size) != st.st_size) {
+		g_warning ("Could not read connector.conf: %s",
+			   g_strerror (errno));
+		close (fd);
+		g_free (config_data);
+		return;
+	}
+	close (fd);
+	config_data[st.st_size] = '\0';
+
+	/* Read config data */
+	p = config_data;
+
+	while (1) {
+		for (name = p; isspace ((guchar)*name); name++)
+			;
+
+		p = strchr (name, ':');
+		if (!p || !p[1])
+			break;
+		*p = '\0';
+		value = p + 2;
+		p = strchr (value, '\n');
+		if (!p)
+			break;
+		if (*(p - 1) == '\r')
+			*(p - 1) = '\0';
+		*p = '\0';
+		p++;
+
+		if (g_ascii_strcasecmp (value, "false") &&
+		    g_ascii_strcasecmp (value, "no"))
+			g_hash_table_insert (config_options, name, value);
+	};
+
+	g_free (config_data);
+}
+
+/**
+ * e2k_autoconfig_lookup_option:
+ * @option: option name to look up
+ *
+ * Looks up an autoconfiguration hint in the config file (if present)
+ *
+ * Return value: the string value of the option, or %NULL if it is unset.
+ **/
+const gchar *
+e2k_autoconfig_lookup_option (const gchar *option)
+{
+	if (!config_options)
+		read_config ();
+	return g_hash_table_lookup (config_options, option);
+}
+
+static gboolean
+validate (const gchar *owa_url, gchar *user, gchar *password, ExchangeParams *exchange_params, E2kAutoconfigResult *result)
+{
+	E2kAutoconfig *ac;
+	E2kOperation op;        /* FIXME */
+	E2kUri *euri;
+	gboolean valid = FALSE;
+	const gchar *old, *new;
+	gchar *path, *mailbox;
+
+	ac = e2k_autoconfig_new (owa_url, user, password,
+				 E2K_AUTOCONFIG_USE_EITHER);
+
+	e2k_operation_init (&op);
+	/* e2k_autoconfig_set_gc_server (ac, ad_server, gal_limit) FIXME */
+	/* e2k_autoconfig_set_gc_server (ac, NULL, -1); */
+	*result = e2k_autoconfig_check_exchange (ac, &op);
+
+	if (*result == E2K_AUTOCONFIG_OK) {
+		/*
+		 * On error code 403 and SSL seen in server response
+		 * e2k_autoconfig_get_context() tries to
+		 * connect using https if owa url has http and vice versa.
+		 * And also re-sets the owa_uri in E2kAutoconfig.
+		 * So even if the uri is incorrect,
+		 * e2k_autoconfig_check_exchange() will return success.
+		 * In this case of account set up, owa_url paramter will still
+		 * have wrong url entered, and we throw the error, instead of
+		 * going ahead with account setup and failing later.
+		 */
+		if (g_str_has_prefix (ac->owa_uri, "http:")) {
+		    if (!g_str_has_prefix (owa_url, "http:"))
+			*result = E2K_AUTOCONFIG_CANT_CONNECT;
+		}
+		else if (!g_str_has_prefix (owa_url, "https:"))
+			*result = E2K_AUTOCONFIG_CANT_CONNECT;
+	}
+
+	if (*result == E2K_AUTOCONFIG_OK) {
+		gint len;
+
+		*result = e2k_autoconfig_check_global_catalog (ac, &op);
+		e2k_operation_free (&op);
+
+		/* find mailbox and owa_path values */
+		euri = e2k_uri_new (ac->home_uri);
+		path = g_strdup (euri->path + 1);
+		e2k_uri_free (euri);
+
+		/* no slash at the end of path */
+		len = strlen (path);
+		while (len && path [len - 1] == '/') {
+			path [len - 1] = '\0';
+			len--;
+		}
+
+		/* change a mailbox only if not set by the caller */
+		if (!exchange_params->mailbox || !*exchange_params->mailbox) {
+			mailbox = strrchr (path, '/');
+			if (mailbox && !mailbox[1]) {
+				*mailbox = '\0';
+				mailbox = strrchr (path, '/');
+			}
+			if (mailbox)
+				*mailbox++ = '\0';
+
+			g_free (exchange_params->mailbox);
+			exchange_params->mailbox  = g_strdup (mailbox);
+		} else {
+			/* always strip the mailbox part from the path */
+			gchar *slash = strrchr (path, '/');
+
+			if (slash)
+				*slash = '\0';
+		}
+
+		exchange_params->owa_path = g_strdup_printf ("%s%s", "/", path);
+		g_free (path);
+		exchange_params->host = g_strdup (ac->pf_server);
+		if (ac->gc_server)
+			exchange_params->ad_server = g_strdup (ac->gc_server);
+		exchange_params->is_ntlm = ac->saw_ntlm;
+
+		valid = TRUE;
+	}
+	else {
+		switch (*result) {
+
+		case E2K_AUTOCONFIG_CANT_CONNECT:
+			if (!strncmp (ac->owa_uri, "http:", 5)) {
+				old = "http";
+				new = "https";
+			} else {
+				old = "https";
+				new = "http";
+			}
+
+			/* SURF : e_notice (NULL, GTK_MESSAGE_ERROR,
+				  _("Could not connect to the Exchange "
+				    "server.\nMake sure the URL is correct "
+				    "(try \"%s\" instead of \"%s\"?) "
+				    "and try again."), new, old);
+			*/
+			valid = FALSE;
+			break;
+
+		case E2K_AUTOCONFIG_CANT_RESOLVE:
+		/* SURF :	e_notice (NULL, GTK_MESSAGE_ERROR,
+				_("Could not locate Exchange server.\n"
+				  "Make sure the server name is spelled correctly "
+				  "and try again."));
+		*/
+			valid = FALSE;
+			break;
+
+		case E2K_AUTOCONFIG_AUTH_ERROR:
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM:
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC:
+		/* SURF :	e_notice (NULL, GTK_MESSAGE_ERROR,
+				_("Could not authenticate to the Exchange "
+				  "server.\nMake sure the username and "
+				  "password are correct and try again."));
+		*/
+			valid = FALSE;
+			break;
+
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN:
+		/* SURF :		e_notice (NULL, GTK_MESSAGE_ERROR,
+				_("Could not authenticate to the Exchange "
+				  "server.\nMake sure the username and "
+				  "password are correct and try again.\n\n"
+				  "You may need to specify the Windows "
+				  "domain name as part of your username "
+				  "(eg, \"MY-DOMAIN\\%s\")."),
+				  ac->username);
+		*/
+			valid = FALSE;
+			break;
+
+		case E2K_AUTOCONFIG_NO_OWA:
+		case E2K_AUTOCONFIG_NOT_EXCHANGE:
+		/* SURF :	e_notice (NULL, GTK_MESSAGE_ERROR,
+				_("Could not find OWA data at the indicated URL.\n"
+				  "Make sure the URL is correct and try again."));
+		*/
+			valid = FALSE;
+			break;
+
+		case E2K_AUTOCONFIG_CANT_BPROPFIND:
+		/* SURF :	e_notice (
+				NULL, GTK_MESSAGE_ERROR,
+				_("Ximian Connector requires access to certain "
+				"functionality on the Exchange Server that appears "
+				"to be disabled or blocked.  (This is usually "
+				"unintentional.)  Your Exchange Administrator will "
+				"need to enable this functionality in order for "
+				"you to be able to use Ximian Connector.\n\n"
+				"For information to provide to your Exchange "
+				"administrator, please follow the link below:\n"
+				"http://support.novell.com/cgi-bin/search/searchtid.cgi?/ximian/ximian328.html "));
+		*/
+			valid = FALSE;
+			break;
+
+		case E2K_AUTOCONFIG_EXCHANGE_5_5:
+		/* SURF :	e_notice (
+				NULL, GTK_MESSAGE_ERROR,
+				_("The Exchange server URL you provided is for an "
+				"Exchange 5.5 Server. Ximian Connector supports "
+				"Microsoft Exchange 2000 and 2003 only."));
+		*/
+			valid = FALSE;
+			break;
+
+		default:
+		/* SURF :	e_notice (NULL, GTK_MESSAGE_ERROR,
+				_("Could not configure Exchange account because "
+				  "an unknown error occurred. Check the URL, "
+				  "username, and password, and try again."));
+		*/
+			valid = FALSE; /* FIXME return valid */
+			break;
+		}
+	}
+
+	e2k_autoconfig_free (ac);
+	return valid;
+}
+
+gboolean
+e2k_validate_user (const gchar *owa_url, gchar *pkey, gchar **user,
+		   ExchangeParams *exchange_params, gboolean *remember_password,
+		   E2kAutoconfigResult *result, GtkWindow *parent)
+{
+	gboolean valid = FALSE, remember=FALSE;
+	gchar *key, *password, *prompt;
+	gchar *username;
+	gchar **usernames;
+	gint try = 0;
+	EUri *uri;
+
+	uri = e_uri_new (owa_url);
+	key = g_strdup_printf ("%s%s/", pkey, uri->host); /* FIXME */
+	e_uri_free (uri);
+
+try_auth_again:
+	username = g_strdup (*user);
+
+	password = e_passwords_get_password ("Exchange", key);
+	if (password) {
+		/* This can be the case, where user presses authenticate button and
+		 * later cancels the account setup or removal of account fails for
+		 * some reason. We need to prompt for the password always when
+		 * authenticate button is pressed */
+		e_passwords_forget_password ("Exchange", key);
+	}
+
+	prompt = g_strdup_printf (_("Enter password for %s"), username);
+	password = e_passwords_ask_password (_("Enter password"),
+				"Exchange", key, prompt,
+				E_PASSWORDS_REMEMBER_FOREVER|E_PASSWORDS_SECRET,
+				&remember, parent);
+	g_free (prompt);
+	if (!password) {
+		g_free (key);
+		g_free (username);
+		*result = E2K_AUTOCONFIG_CANCELLED;
+		return valid;
+	}
+
+	valid = validate (owa_url, username, password, exchange_params, result);
+	if (valid) {
+		/* generate the proper key once the host name
+		 * is read and remember password temporarily,
+		 * so that at the end of * account creation,
+		 * user will not be prompted, for password will
+		 * not be asked again.
+		 */
+		*remember_password = remember;
+		g_free (key);
+		if (exchange_params->is_ntlm)
+			key = g_strdup_printf ("exchange://%s;auth=NTLM %s/",
+						       username, exchange_params->host);
+		else
+			key = g_strdup_printf ("exchange://%s %s/", username, exchange_params->host);
+		e_passwords_add_password (key, password);
+		e_passwords_remember_password ("Exchange", key);
+	}
+	else {
+		if (try == 0) {
+			/* Check for name as e-mail id and try once again
+			 * extracing username from e-mail id.
+			 */
+			usernames = g_strsplit (*user, "@", 2);
+			if (usernames && usernames[0] && usernames[1]) {
+				username = g_strdup (usernames[0]);
+				g_strfreev (usernames);
+				try ++;
+				memset(*user, 0, strlen(*user));
+				g_free (*user);
+				*user = g_strdup (username);
+				g_free (username);
+				goto try_auth_again;
+			}
+		}
+		/* if validation failed*/
+		e_passwords_forget_password ("Exchange", key);
+	}
+
+	g_free (key);
+	g_free (password);
+	g_free (username);
+	return valid;
+}
diff --git a/server/lib/e2k-autoconfig.h b/server/lib/e2k-autoconfig.h
new file mode 100644
index 0000000..bf9d5b9
--- /dev/null
+++ b/server/lib/e2k-autoconfig.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2003, 2004 Novell, Inc. */
+
+#ifndef __E2K_AUTOCONFIG_H__
+#define __E2K_AUTOCONFIG_H__
+
+#include "e2k-types.h"
+#include "e2k-operation.h"
+#include "e2k-validate.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E2K_EXCHANGE_UNKNOWN,
+	E2K_EXCHANGE_2000,
+	E2K_EXCHANGE_2003,
+
+	E2K_EXCHANGE_FUTURE
+} E2kExchangeVersion;
+
+typedef enum {
+	E2K_AUTOCONFIG_USE_BASIC,
+	E2K_AUTOCONFIG_USE_NTLM,
+	E2K_AUTOCONFIG_USE_EITHER
+} E2kAutoconfigAuthPref;
+
+typedef struct {
+	/* Input data. (gc_server is optional) */
+	gchar *owa_uri, *gc_server;
+	gchar *username, *password;
+	gint gal_limit;
+	E2kAutoconfigGalAuthPref gal_auth;
+
+	/* Output data */
+	E2kExchangeVersion version;
+	gchar *display_name, *email;
+	gchar *account_uri, *exchange_server;
+	gchar *timezone;
+
+	/* Private-ish members */
+	gchar *nt_domain, *w2k_domain;
+	gchar *home_uri, *exchange_dn;
+	gchar *pf_server;
+	E2kAutoconfigAuthPref auth_pref;
+	gboolean require_ntlm, use_ntlm;
+	gboolean saw_basic, saw_ntlm;
+	gboolean nt_domain_defaulted, gc_server_autodetected;
+} E2kAutoconfig;
+
+E2kAutoconfig       *e2k_autoconfig_new                  (const gchar *owa_uri,
+							  const gchar *username,
+							  const gchar *password,
+							  E2kAutoconfigAuthPref auth_pref);
+void                 e2k_autoconfig_free                 (E2kAutoconfig *ac);
+
+void                 e2k_autoconfig_set_owa_uri          (E2kAutoconfig *ac,
+							  const gchar *owa_uri);
+void                 e2k_autoconfig_set_gc_server        (E2kAutoconfig *ac,
+							  const gchar *gc_server,
+							  gint gal_limit,
+							  E2kAutoconfigGalAuthPref gal_auth);
+void                 e2k_autoconfig_set_username         (E2kAutoconfig *ac,
+							  const gchar *username);
+void                 e2k_autoconfig_set_password         (E2kAutoconfig *ac,
+							  const gchar *password);
+
+E2kContext          *e2k_autoconfig_get_context          (E2kAutoconfig *ac,
+							  E2kOperation *op,
+							  E2kAutoconfigResult *result);
+E2kAutoconfigResult  e2k_autoconfig_check_exchange       (E2kAutoconfig *ac,
+							  E2kOperation *op);
+E2kGlobalCatalog    *e2k_autoconfig_get_global_catalog   (E2kAutoconfig *ac,
+							  E2kOperation *op);
+E2kAutoconfigResult  e2k_autoconfig_check_global_catalog (E2kAutoconfig *ac,
+							  E2kOperation *op);
+
+const gchar          *e2k_autoconfig_lookup_option        (const gchar *option);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_AUTOCONFIG_H__ */
diff --git a/server/lib/e2k-context.c b/server/lib/e2k-context.c
new file mode 100644
index 0000000..6e5bc68
--- /dev/null
+++ b/server/lib/e2k-context.c
@@ -0,0 +1,2764 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <glib.h>
+
+#ifndef G_OS_WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <windns.h>
+#endif
+
+#include "e2k-context.h"
+#include "e2k-marshal.h"
+#include "e2k-propnames.h"
+#include "e2k-restriction.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+#include "e2k-xml-utils.h"
+
+#include <libedataserver/e-proxy.h>
+#include <libsoup/soup.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+
+#ifdef G_OS_WIN32
+/* The strtok() in Microsoft's C library is MT-safe (not stateless,
+ * but that is not needed here).
+ */
+#define strtok_r(s,sep,lasts ) (*(lasts) = strtok((s),(sep)))
+#endif
+
+#ifdef G_OS_WIN32
+#define CLOSE_SOCKET(socket) closesocket (socket)
+#define STATUS_IS_SOCKET_ERROR(status) ((status) == SOCKET_ERROR)
+#define SOCKET_IS_INVALID(socket) ((socket) == INVALID_SOCKET)
+#define BIND_STATUS_IS_ADDRINUSE() (WSAGetLastError () == WSAEADDRINUSE)
+#else
+#define CLOSE_SOCKET(socket) close (socket)
+#define STATUS_IS_SOCKET_ERROR(status) ((status) == -1)
+#define SOCKET_IS_INVALID(socket) ((socket) < 0)
+#define BIND_STATUS_IS_ADDRINUSE() (errno == EADDRINUSE)
+#endif
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class;
+
+enum {
+	REDIRECT,
+	LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+struct _E2kContextPrivate {
+	SoupSession *session, *async_session;
+	gchar *owa_uri, *username, *password;
+	time_t last_timestamp;
+
+	/* Notification listener */
+	SoupSocket *get_local_address_sock;
+	GIOChannel *listener_channel;
+	gint listener_watch_id;
+
+	gchar *notification_uri;
+	GHashTable *subscriptions_by_id, *subscriptions_by_uri;
+
+	/* Forms-based authentication */
+	gchar *cookie;
+	gboolean cookie_verified;
+	EProxy* proxy;
+};
+
+/* For operations with progress */
+#define E2K_CONTEXT_MIN_BATCH_SIZE 25
+#define E2K_CONTEXT_MAX_BATCH_SIZE 100
+
+/* For soup sync session timeout */
+#define E2K_SOUP_SESSION_TIMEOUT 30
+
+/* Soup session proxy-uri property */
+#define SOUP_SESSION_PROXY_URI "proxy-uri"
+
+#ifdef E2K_DEBUG
+gchar *e2k_debug;
+gint e2k_debug_level;
+
+static SoupLoggerLogLevel e2k_debug_request_filter  (SoupLogger  *logger,
+						     SoupMessage *msg,
+						     gpointer     user_data);
+static SoupLoggerLogLevel e2k_debug_response_filter (SoupLogger  *logger,
+						     SoupMessage *msg,
+						     gpointer     user_data);
+#endif
+
+static gboolean renew_subscription (gpointer user_data);
+static void unsubscribe_internal (E2kContext *ctx, const gchar *uri, GList *sub_list, gboolean destrying);
+static gboolean do_notification (GIOChannel *source, GIOCondition condition, gpointer data);
+
+static void setup_message (SoupSession *session, SoupMessage *msg, SoupSocket *socket, gpointer user_data);
+static void proxy_settings_changed (EProxy *proxy, gpointer user_data);
+
+static void
+proxy_settings_changed (EProxy *proxy, gpointer user_data)
+{
+	SoupURI *proxy_uri = NULL;
+	E2kContext* ctx = (E2kContext *)user_data;
+	if (!ctx || !ctx->priv ||
+	    (!ctx->priv->session && !ctx->priv->async_session) ||
+	    (!ctx->priv->owa_uri))
+		return;
+
+	if (!e_proxy_require_proxy_for_uri (proxy, ctx->priv->owa_uri))
+		proxy_uri = NULL;
+	else
+		proxy_uri = e_proxy_peek_uri_for (proxy, ctx->priv->owa_uri);
+
+	if (ctx->priv->session)
+		g_object_set (ctx->priv->session, SOUP_SESSION_PROXY_URI,
+			      proxy_uri, NULL);
+	if (ctx->priv->async_session)
+		g_object_set (ctx->priv->async_session, SOUP_SESSION_PROXY_URI,
+			      proxy_uri, NULL);
+}
+
+static void
+init (GObject *object)
+{
+	E2kContext *ctx = E2K_CONTEXT (object);
+
+	ctx->priv = g_new0 (E2kContextPrivate, 1);
+	ctx->priv->subscriptions_by_id =
+		g_hash_table_new (g_str_hash, g_str_equal);
+	ctx->priv->subscriptions_by_uri =
+		g_hash_table_new (g_str_hash, g_str_equal);
+	ctx->priv->proxy = e_proxy_new ();
+	e_proxy_setup_proxy (ctx->priv->proxy);
+	g_signal_connect (ctx->priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), ctx);
+}
+
+static void
+destroy_sub_list (gpointer uri, gpointer sub_list, gpointer ctx)
+{
+	unsubscribe_internal (ctx, uri, sub_list, TRUE);
+	g_list_free (sub_list);
+}
+
+static void
+dispose (GObject *object)
+{
+	E2kContext *ctx = E2K_CONTEXT (object);
+
+	if (ctx->priv) {
+		if (ctx->priv->owa_uri)
+			g_free (ctx->priv->owa_uri);
+		if (ctx->priv->username)
+			g_free (ctx->priv->username);
+		if (ctx->priv->password)
+			g_free (ctx->priv->password);
+
+		if (ctx->priv->get_local_address_sock)
+			g_object_unref (ctx->priv->get_local_address_sock);
+
+		g_hash_table_foreach (ctx->priv->subscriptions_by_uri,
+				      destroy_sub_list, ctx);
+		g_hash_table_destroy (ctx->priv->subscriptions_by_uri);
+
+		g_hash_table_destroy (ctx->priv->subscriptions_by_id);
+
+		if (ctx->priv->listener_watch_id)
+			g_source_remove (ctx->priv->listener_watch_id);
+		if (ctx->priv->listener_channel) {
+			g_io_channel_shutdown (ctx->priv->listener_channel,
+					       FALSE, NULL);
+			g_io_channel_unref (ctx->priv->listener_channel);
+		}
+
+		if (ctx->priv->session)
+			g_object_unref (ctx->priv->session);
+		if (ctx->priv->async_session)
+			g_object_unref (ctx->priv->async_session);
+
+		g_free (ctx->priv->cookie);
+		g_free (ctx->priv->notification_uri);
+
+		if (ctx->priv->proxy) {
+			g_object_unref (ctx->priv->proxy);
+			ctx->priv->proxy = NULL;
+		}
+		g_free (ctx->priv);
+		ctx->priv = NULL;
+
+	}
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+class_init (GObjectClass *object_class)
+{
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* virtual method override */
+	object_class->dispose = dispose;
+
+	signals[REDIRECT] =
+		g_signal_new ("redirect",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (E2kContextClass, redirect),
+			      NULL, NULL,
+			      e2k_marshal_NONE__INT_STRING_STRING,
+			      G_TYPE_NONE, 3,
+			      G_TYPE_INT,
+			      G_TYPE_STRING,
+			      G_TYPE_STRING);
+
+#ifdef E2K_DEBUG
+	e2k_debug = getenv ("E2K_DEBUG");
+	if (e2k_debug)
+		e2k_debug_level = atoi (e2k_debug);
+#endif
+}
+
+E2K_MAKE_TYPE (e2k_context, E2kContext, class_init, init, PARENT_TYPE)
+
+static void
+renew_sub_list (gpointer key, gpointer value, gpointer data)
+{
+	GList *sub_list;
+
+	for (sub_list = value; sub_list; sub_list = sub_list->next)
+		renew_subscription (sub_list->data);
+}
+
+static void
+got_connection (SoupSocket *sock, guint status, gpointer user_data)
+{
+	E2kContext *ctx = user_data;
+	SoupAddress *addr;
+	struct sockaddr_in sin;
+	const gchar *local_ipaddr;
+	unsigned short port;
+	gint s, ret;
+
+	ctx->priv->get_local_address_sock = NULL;
+
+	if (status != SOUP_STATUS_OK)
+		goto done;
+
+	addr = soup_socket_get_local_address (sock);
+	local_ipaddr = soup_address_get_physical (addr);
+
+	s = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (SOCKET_IS_INVALID (s))
+		goto done;
+
+	memset (&sin, 0, sizeof (sin));
+	sin.sin_family = AF_INET;
+
+	port = (short)getpid ();
+	do {
+		port++;
+		if (port < 1024)
+			port += 1024;
+		sin.sin_port = htons (port);
+		ret = bind (s, (struct sockaddr *)&sin, sizeof (sin));
+	} while (STATUS_IS_SOCKET_ERROR (ret) && BIND_STATUS_IS_ADDRINUSE ());
+
+	if (ret == -1) {
+		CLOSE_SOCKET (s);
+		goto done;
+	}
+
+#ifndef G_OS_WIN32
+	ctx->priv->listener_channel = g_io_channel_unix_new (s);
+#else
+	ctx->priv->listener_channel = g_io_channel_win32_new_socket (s);
+#endif
+	g_io_channel_set_encoding (ctx->priv->listener_channel, NULL, NULL);
+	g_io_channel_set_buffered (ctx->priv->listener_channel, FALSE);
+
+	ctx->priv->listener_watch_id =
+		g_io_add_watch (ctx->priv->listener_channel,
+				G_IO_IN, do_notification, ctx);
+
+	ctx->priv->notification_uri = g_strdup_printf ("httpu://%s:%u/",
+							local_ipaddr,
+							port);
+
+	g_hash_table_foreach (ctx->priv->subscriptions_by_uri,
+			      renew_sub_list, ctx);
+
+ done:
+	if (sock)
+		g_object_unref (sock);
+	g_object_unref (ctx);
+}
+
+/**
+ * e2k_context_new:
+ * @uri: OWA uri to connect to
+ *
+ * Creates a new #E2kContext based at @uri
+ *
+ * Return value: the new context
+ **/
+E2kContext *
+e2k_context_new (const gchar *uri)
+{
+	E2kContext *ctx;
+	SoupURI *suri;
+	SoupAddress *addr;
+
+	suri = soup_uri_new (uri);
+	if (!suri)
+		return NULL;
+
+	if (!suri->host) {
+		soup_uri_free (suri);
+		return NULL;
+	}
+
+	addr = soup_address_new (suri->host, suri->port);
+	soup_uri_free (suri);
+
+	ctx = g_object_new (E2K_TYPE_CONTEXT, NULL);
+	ctx->priv->owa_uri = g_strdup (uri);
+
+	ctx->priv->get_local_address_sock =
+		soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, addr,
+				 NULL);
+	soup_socket_connect_async (ctx->priv->get_local_address_sock, NULL,
+				   got_connection, g_object_ref (ctx));
+	g_object_unref (addr);
+
+	return ctx;
+}
+
+static void
+session_authenticate (SoupSession *session, SoupMessage *msg,
+		      SoupAuth *auth, gboolean retrying, gpointer user_data)
+{
+	E2kContext *ctx = user_data;
+
+	if (!retrying) {
+		soup_auth_authenticate (auth, ctx->priv->username,
+					ctx->priv->password);
+	}
+}
+
+/**
+ * e2k_context_set_auth:
+ * @ctx: the context
+ * @username: the Windows username (not including domain) of the user
+ * @domain: the NT domain, or %NULL to use the default (if using NTLM)
+ * @authmech: the HTTP Authorization type to use; either "Basic" or "NTLM"
+ * @password: the user's password
+ *
+ * Sets the authentication information on @ctx. This will have the
+ * side effect of cancelling any pending requests on @ctx.
+ **/
+void
+e2k_context_set_auth (E2kContext *ctx, const gchar *username,
+		      const gchar *domain, const gchar *authmech,
+		      const gchar *password)
+{
+	guint timeout = E2K_SOUP_SESSION_TIMEOUT;
+	SoupURI* uri = NULL;
+#ifdef E2K_DEBUG
+	SoupLogger *logger;
+	SoupLoggerLogLevel level;
+#endif
+
+	g_return_if_fail (E2K_IS_CONTEXT (ctx));
+
+	if (username) {
+		g_free (ctx->priv->username);
+		if (domain) {
+			ctx->priv->username =
+				g_strdup_printf ("%s\\%s", domain,
+						 username);
+		} else
+			ctx->priv->username = g_strdup (username);
+	}
+
+	if (password) {
+		g_free (ctx->priv->password);
+		ctx->priv->password = g_strdup (password);
+	}
+
+	/* Destroy the old sessions so we don't reuse old auths */
+	if (ctx->priv->session)
+		g_object_unref (ctx->priv->session);
+	if (ctx->priv->async_session)
+		g_object_unref (ctx->priv->async_session);
+
+	/* Set a default timeout value of 30 seconds.
+	   FIXME: Make timeout configurable
+	*/
+	if (g_getenv ("SOUP_SESSION_TIMEOUT"))
+		timeout = atoi (g_getenv ("SOUP_SESSION_TIMEOUT"));
+
+	/* Check do we need a proxy to contact the server? */
+        if (e_proxy_require_proxy_for_uri (ctx->priv->proxy, ctx->priv->owa_uri))
+                uri = e_proxy_peek_uri_for (ctx->priv->proxy, ctx->priv->owa_uri);
+
+	ctx->priv->session = soup_session_sync_new_with_options (
+		SOUP_SESSION_USE_NTLM, !authmech || !strcmp (authmech, "NTLM"),
+		SOUP_SESSION_TIMEOUT, timeout,
+		SOUP_SESSION_PROXY_URI, uri,
+		NULL);
+	g_signal_connect (ctx->priv->session, "authenticate",
+			  G_CALLBACK (session_authenticate), ctx);
+	g_signal_connect (ctx->priv->session, "request_started",
+			  G_CALLBACK (setup_message), ctx);
+
+	ctx->priv->async_session = soup_session_async_new_with_options (
+		SOUP_SESSION_USE_NTLM, !authmech || !strcmp (authmech, "NTLM"),
+		SOUP_SESSION_PROXY_URI, uri, NULL);
+	g_signal_connect (ctx->priv->async_session, "authenticate",
+			  G_CALLBACK (session_authenticate), ctx);
+	g_signal_connect (ctx->priv->async_session, "request_started",
+			  G_CALLBACK (setup_message), ctx);
+
+#ifdef E2K_DEBUG
+	if (e2k_debug_level < 4)
+		level = (SoupLoggerLogLevel)e2k_debug_level;
+	else
+		level = SOUP_LOGGER_LOG_BODY;
+	logger = soup_logger_new (level, -1);
+	if (level == SOUP_LOGGER_LOG_BODY && e2k_debug_level < 5) {
+		soup_logger_set_request_filter (logger, e2k_debug_request_filter, NULL, NULL);
+		soup_logger_set_response_filter (logger, e2k_debug_response_filter, NULL, NULL);
+	}
+	soup_session_add_feature (
+		ctx->priv->session, SOUP_SESSION_FEATURE (logger));
+	soup_session_add_feature (
+		ctx->priv->async_session, SOUP_SESSION_FEATURE (logger));
+#endif
+}
+
+/**
+ * e2k_context_get_last_timestamp:
+ * @ctx: the context
+ *
+ * Returns a %time_t corresponding to the last "Date" header
+ * received from the server.
+ *
+ * Return value: the timestamp
+ **/
+time_t
+e2k_context_get_last_timestamp (E2kContext *ctx)
+{
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), -1);
+
+	return ctx->priv->last_timestamp;
+}
+
+#ifdef E2K_DEBUG
+/* Debug levels:
+ * 0 - None
+ * 1 - Basic request and response (SOUP_LOGGER_LOG_MINIMAL)
+ * 2 - 1 plus all headers (SOUP_LOGGER_LOG_HEADERS)
+ * 3 - 2 plus most bodies (SOUP_LOGGER_LOG_BODY with filters)
+ * 4 - 3 plus Global Catalog debug too
+ * 5 - 4 plus all bodies (SOUP_LOGGER_LOG_BODY)
+ */
+
+/* The filters are only used when e2k_debug_level is 3 or 4,
+ * meaning we want to show most, but not all, bodies.
+ */
+static SoupLoggerLogLevel
+e2k_debug_request_filter (SoupLogger *logger, SoupMessage *msg,
+			  gpointer user_data)
+{
+	if (msg->method == SOUP_METHOD_POST)
+		return SOUP_LOGGER_LOG_HEADERS;
+	else
+		return SOUP_LOGGER_LOG_BODY;
+}
+
+static SoupLoggerLogLevel
+e2k_debug_response_filter (SoupLogger *logger, SoupMessage *msg,
+			   gpointer user_data)
+{
+	const gchar *content_type;
+
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (msg->status_code))
+		return SOUP_LOGGER_LOG_HEADERS;
+
+	content_type = soup_message_headers_get (msg->response_headers,
+						 "Content-Type");
+	if (!content_type || g_ascii_strncasecmp (content_type, "text/html", 9))
+		return SOUP_LOGGER_LOG_BODY;
+	else
+		return SOUP_LOGGER_LOG_HEADERS;
+}
+#endif
+
+#define E2K_FBA_FLAG_FORCE_DOWNLEVEL "1"
+#define E2K_FBA_FLAG_TRUSTED         "4"
+
+/**
+ * e2k_context_fba:
+ * @ctx: the context
+ * @failed_msg: a message that received a 440 status code
+ *
+ * Attempts to synchronously perform Exchange 2003 forms-based
+ * authentication.
+ *
+ * Return value: %FALSE if authentication failed, %TRUE if it
+ * succeeded, in which case @failed_msg can be requeued.
+ **/
+gboolean
+e2k_context_fba (E2kContext *ctx, SoupMessage *failed_msg)
+{
+	static gboolean in_fba_auth = FALSE;
+	gint status, len;
+	SoupBuffer *response = NULL;
+	gchar *action;
+	xmlChar *method, *name, *value;
+	xmlDoc *doc = NULL;
+	xmlNode *node;
+	SoupMessage *post_msg;
+	GHashTable *form_data;
+	gchar *form_body;
+	GString *cookie_str;
+	GSList *cookies, *c;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), FALSE);
+
+	if (in_fba_auth)
+		return FALSE;
+
+	if (ctx->priv->cookie) {
+		g_free (ctx->priv->cookie);
+		ctx->priv->cookie = NULL;
+		if (!ctx->priv->cookie_verified) {
+			/* New cookie failed on the first try. Must
+			 * be a bad password.
+			 */
+			return FALSE;
+		}
+		/* Otherwise, it's just expired. */
+	}
+
+	if (!ctx->priv->username || !ctx->priv->password)
+		return FALSE;
+
+	in_fba_auth = TRUE;
+
+	status = e2k_context_get_owa (ctx, NULL, ctx->priv->owa_uri,
+				      FALSE, &response);
+	if (!SOUP_STATUS_IS_SUCCESSFUL (status) || response->length == 0) {
+		if (response)
+			soup_buffer_free (response);
+		goto failed;
+	}
+
+	doc = e2k_parse_html (response->data, response->length);
+	soup_buffer_free (response);
+
+	node = e2k_xml_find (doc->children, "form");
+	if (!node)
+		goto failed;
+
+	method = xmlGetProp (node, (xmlChar *) "method");
+	if (!method || g_ascii_strcasecmp ((gchar *) method, "post") != 0) {
+		if (method)
+			xmlFree (method);
+		goto failed;
+	}
+	xmlFree (method);
+
+	value = xmlGetProp (node, (xmlChar *) "action");
+	if (!value || !*value)
+		goto failed;
+	if (*value == '/') {
+		SoupURI *suri;
+
+		suri = soup_uri_new (ctx->priv->owa_uri);
+		g_free (suri->path);
+		suri->path = g_strdup ((gchar *) value);
+		action = soup_uri_to_string (suri, FALSE);
+		soup_uri_free (suri);
+	} else if (xmlStrncmp (value, (xmlChar *) "http", 4) != 0) {
+		SoupURI *suri;
+		gchar *path_end;
+		const gchar *location;
+
+		location = soup_message_headers_get (failed_msg->response_headers,
+						     "Location");
+		if (location != NULL) {/*Make sure we can get absolute path*/
+			suri = soup_uri_new (location);
+			if (suri != NULL) {/*Valid URI*/
+				if (!suri->path || strchr (suri->path, '/') == NULL)
+					goto failed;
+
+				path_end = strrchr (suri->path, '/') + 1;
+				*path_end = '\0';
+				suri->path = g_realloc (suri->path,
+					path_end - suri->path + xmlStrlen (value) + 1);
+				strcat (suri->path, (gchar *) value);
+				g_free (suri->query);
+				suri->query = NULL;
+				action = soup_uri_to_string (suri, FALSE);
+				soup_uri_free (suri);
+			}
+		}
+	} else
+		action = g_strdup ((gchar *) value);
+	xmlFree (value);
+
+	form_data = g_hash_table_new_full (g_str_hash, g_str_equal,
+					   NULL, g_free);
+	while ((node = e2k_xml_find (node, "input"))) {
+		name = xmlGetProp (node, (xmlChar *) "name");
+		if (!name)
+			continue;
+		value = xmlGetProp (node, (xmlChar *) "value");
+
+		if (!g_ascii_strcasecmp ((gchar *) name, "destination") && value) {
+			g_hash_table_insert (
+				form_data,
+				(gpointer) "destination",
+				g_strdup ((gchar *) value));
+		} else if (!g_ascii_strcasecmp ((gchar *) name, "flags")) {
+			g_hash_table_insert (
+				form_data,
+				(gpointer) "flags",
+				g_strdup (E2K_FBA_FLAG_TRUSTED));
+		} else if (!g_ascii_strcasecmp ((gchar *) name, "username")) {
+			g_hash_table_insert (
+				form_data,
+				(gpointer) "username",
+				g_strdup (ctx->priv->username));
+		} else if (!g_ascii_strcasecmp ((gchar *) name, "password")) {
+			g_hash_table_insert (
+				form_data,
+				(gpointer) "password",
+				g_strdup (ctx->priv->password));
+		}
+
+		if (value)
+			xmlFree (value);
+		xmlFree (name);
+	}
+	g_hash_table_insert (
+		form_data, (gpointer) "trusted",
+		g_strdup (E2K_FBA_FLAG_TRUSTED));
+	xmlFreeDoc (doc);
+	doc = NULL;
+
+	form_body = soup_form_encode_hash (form_data);
+	g_hash_table_destroy (form_data);
+
+	post_msg = e2k_soup_message_new_full (
+		ctx, action, "POST",
+		"application/x-www-form-urlencoded",
+		SOUP_MEMORY_TAKE,
+		form_body, strlen (form_body));
+	if (!post_msg)
+		goto failed;
+
+	soup_message_set_flags (post_msg, SOUP_MESSAGE_NO_REDIRECT);
+	e2k_context_send_message (ctx, NULL /* FIXME? */, post_msg);
+	g_free (action);
+
+	if (!SOUP_STATUS_IS_SUCCESSFUL (post_msg->status_code) &&
+	    !SOUP_STATUS_IS_REDIRECTION (post_msg->status_code)) {
+		g_object_unref (post_msg);
+		goto failed;
+	}
+
+	/* Extract the cookies */
+	cookies = e2k_http_get_headers (post_msg->response_headers, "Set-Cookie");
+	cookie_str = g_string_new (NULL);
+
+	for (c = cookies; c; c = c->next) {
+		gchar *string = c->data;
+		len = strcspn (string, ";");
+
+		if (cookie_str->len)
+			g_string_append (cookie_str, "; ");
+		g_string_append_len (cookie_str, string, len);
+	}
+	ctx->priv->cookie = cookie_str->str;
+	ctx->priv->cookie_verified = FALSE;
+	g_string_free (cookie_str, FALSE);
+	g_slist_free (cookies);
+	g_object_unref (post_msg);
+
+	in_fba_auth = FALSE;
+
+	/* Set up the failed message to be requeued */
+	soup_message_headers_remove (failed_msg->request_headers, "Cookie");
+	soup_message_headers_append (failed_msg->request_headers,
+				     "Cookie", ctx->priv->cookie);
+	return TRUE;
+
+ failed:
+	in_fba_auth = FALSE;
+	if (doc)
+		xmlFreeDoc (doc);
+	return FALSE;
+}
+
+static void
+fba_timeout_handler (SoupMessage *msg, gpointer user_data)
+{
+	E2kContext *ctx = user_data;
+
+	if (e2k_context_fba (ctx, msg))
+		soup_session_requeue_message (ctx->priv->session, msg);
+	else
+		soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
+}
+
+static void
+timestamp_handler (SoupMessage *msg, gpointer user_data)
+{
+	E2kContext *ctx = user_data;
+	const gchar *date;
+
+	date = soup_message_headers_get (msg->response_headers, "Date");
+	if (date)
+		ctx->priv->last_timestamp = e2k_http_parse_date (date);
+}
+
+static void
+redirect_handler (SoupMessage *msg, gpointer user_data)
+{
+	E2kContext *ctx = user_data;
+	const gchar *new_uri;
+	SoupURI *soup_uri;
+	gchar *old_uri;
+
+	if (!SOUP_STATUS_IS_REDIRECTION (msg->status_code) ||
+	    (soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT))
+		return;
+
+	new_uri = soup_message_headers_get (msg->response_headers, "Location");
+	soup_uri = soup_uri_copy (soup_message_get_uri (msg));
+	old_uri = soup_uri_to_string (soup_uri, FALSE);
+
+	g_signal_emit (ctx, signals[REDIRECT], 0,
+		       msg->status_code, old_uri, new_uri);
+	soup_uri_free (soup_uri);
+	g_free (old_uri);
+}
+
+static void
+setup_message (SoupSession *session, SoupMessage *msg,
+	       SoupSocket *socket, gpointer user_data)
+{
+	E2kContext *ctx = user_data;
+
+	if (ctx->priv->cookie) {
+		soup_message_headers_replace (msg->request_headers,
+					      "Cookie", ctx->priv->cookie);
+	}
+
+	/* Only do this the first time through */
+	if (!soup_message_headers_get (msg->request_headers, "User-Agent")) {
+		g_signal_connect (msg, "got-headers",
+				  G_CALLBACK (timestamp_handler), ctx);
+		soup_message_add_header_handler (msg, "got-headers",
+						 "Location",
+						 G_CALLBACK (redirect_handler),
+						 ctx);
+		soup_message_add_status_code_handler (msg, "got-headers",
+						      E2K_HTTP_TIMEOUT,
+						      G_CALLBACK (fba_timeout_handler),
+						      ctx);
+		soup_message_headers_append (msg->request_headers, "User-Agent",
+					     "Evolution/" VERSION);
+
+	}
+}
+
+/**
+ * e2k_soup_message_new:
+ * @ctx: the context
+ * @uri: the URI
+ * @method: the HTTP method
+ *
+ * Creates a new %SoupMessage for @ctx.
+ *
+ * Return value: a new %SoupMessage, set up for connector use
+ **/
+SoupMessage *
+e2k_soup_message_new (E2kContext *ctx, const gchar *uri, const gchar *method)
+{
+	SoupMessage *msg;
+
+	if (method[0] == 'B') {
+		gchar *slash_uri = e2k_strdup_with_trailing_slash (uri);
+		msg = soup_message_new (method, slash_uri);
+		if (!msg)
+			g_warning ("Invalid uri '%s'", slash_uri ? slash_uri : "[null]");
+		g_free (slash_uri);
+	} else {
+		msg = soup_message_new (method, uri);
+		if (!msg)
+			g_warning ("Invalid uri '%s'", uri ? uri : "[null]");
+	}
+
+	return msg;
+}
+
+/**
+ * e2k_soup_message_new_full:
+ * @ctx: the context
+ * @uri: the URI
+ * @method: the HTTP method
+ * @content_type: MIME Content-Type of @body
+ * @use: use policy of @body
+ * @body: request body
+ * @length: length of @body
+ *
+ * Creates a new %SoupMessage with the given body.
+ *
+ * Return value: a new %SoupMessage with a request body, set up for
+ * connector use
+ **/
+SoupMessage *
+e2k_soup_message_new_full (E2kContext *ctx, const gchar *uri,
+			   const gchar *method, const gchar *content_type,
+			   SoupMemoryUse use, const gchar *body,
+			   gsize length)
+{
+	SoupMessage *msg;
+
+	msg = e2k_soup_message_new (ctx, uri, method);
+	g_return_val_if_fail (msg != NULL, NULL);
+	soup_message_set_request (msg, content_type, use, body, length);
+
+	return msg;
+}
+
+/**
+ * e2k_context_queue_message:
+ * @ctx: the context
+ * @msg: the message to queue
+ * @callback: callback to invoke when @msg is done
+ * @user_data: data for @callback
+ *
+ * Asynchronously queues @msg in @ctx's session.
+ **/
+void
+e2k_context_queue_message (E2kContext *ctx, SoupMessage *msg,
+			   SoupSessionCallback callback,
+			   gpointer user_data)
+{
+	g_return_if_fail (E2K_IS_CONTEXT (ctx));
+
+	soup_session_queue_message (ctx->priv->async_session, msg,
+				    callback, user_data);
+}
+
+static void
+context_canceller (E2kOperation *op, gpointer owner, gpointer data)
+{
+	E2kContext *ctx = owner;
+	SoupMessage *msg = data;
+
+	soup_session_cancel_message (ctx->priv->session, msg,
+				     SOUP_STATUS_CANCELLED);
+}
+
+/**
+ * e2k_context_send_message:
+ * @ctx: the context
+ * @op: an #E2kOperation to use for cancellation
+ * @msg: the message to send
+ *
+ * Synchronously sends @msg in @ctx's session.
+ *
+ * Return value: the HTTP status of the message
+ **/
+E2kHTTPStatus
+e2k_context_send_message (E2kContext *ctx, E2kOperation *op, SoupMessage *msg)
+{
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+
+	if (e2k_operation_is_cancelled (op)) {
+		soup_message_set_status (msg, E2K_HTTP_CANCELLED);
+		return E2K_HTTP_CANCELLED;
+	}
+
+	e2k_operation_start (op, context_canceller, ctx, msg);
+	status = soup_session_send_message (ctx->priv->session, msg);
+	e2k_operation_finish (op);
+
+	return status;
+}
+
+static void
+update_unique_uri (E2kContext *ctx, SoupMessage *msg,
+		   const gchar *folder_uri, const gchar *encoded_name, gint *count,
+		   E2kContextTestCallback test_callback, gpointer user_data)
+{
+	SoupURI *suri;
+	gchar *uri = NULL;
+
+	do {
+		g_free (uri);
+		if (*count == 1) {
+			uri = g_strdup_printf ("%s%s.EML", folder_uri,
+					       encoded_name);
+		} else {
+			uri = g_strdup_printf ("%s%s-%d.EML", folder_uri,
+					       encoded_name, *count);
+		}
+		(*count)++;
+	} while (test_callback && !test_callback (ctx, uri, user_data));
+
+	suri = soup_uri_new (uri);
+	soup_message_set_uri (msg, suri);
+	soup_uri_free (suri);
+	g_free (uri);
+}
+
+/* GET */
+
+static SoupMessage *
+get_msg (E2kContext *ctx, const gchar *uri, gboolean owa, gboolean claim_ie)
+{
+	SoupMessage *msg;
+
+	msg = e2k_soup_message_new (ctx, uri, "GET");
+	if (!owa)
+		soup_message_headers_append (msg->request_headers, "Translate", "F");
+	if (claim_ie) {
+		soup_message_headers_replace (msg->request_headers, "User-Agent",
+					      "MSIE 6.0b (Windows NT 5.0; compatible; "
+					      "Evolution/" VERSION ")");
+	}
+
+	return msg;
+}
+
+/**
+ * e2k_context_get:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: URI of the object to GET
+ * @content_type: if not %NULL, will contain the Content-Type of the
+ * response on return.
+ * @response: if not %NULL, will contain the response on return
+ *
+ * Performs a GET on @ctx for @uri. If successful (2xx status code),
+ * the Content-Type, and response body will be returned. The body is not
+ * terminated by a '\0'. If the GET is not successful, @content_type and
+ * @response will be untouched (even if the error response
+ * included a body).
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_get (E2kContext *ctx, E2kOperation *op, const gchar *uri,
+		 gchar **content_type, SoupBuffer **response)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (uri != NULL, E2K_HTTP_MALFORMED);
+
+	msg = get_msg (ctx, uri, FALSE, FALSE);
+	status = e2k_context_send_message (ctx, op, msg);
+
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		if (content_type) {
+			const gchar *header;
+			header = soup_message_headers_get (msg->response_headers,
+							   "Content-Type");
+			*content_type = g_strdup (header);
+		}
+		if (response)
+			*response = soup_message_body_flatten (msg->response_body);
+	}
+
+	g_object_unref (msg);
+	return status;
+}
+
+/**
+ * e2k_context_get_owa:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: URI of the object to GET
+ * @claim_ie: whether or not to claim to be IE
+ * @response: if not %NULL, will contain the response on return
+ *
+ * As with e2k_context_get(), but used when you need the HTML or XML
+ * data that would be returned to OWA rather than the raw object data.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_get_owa (E2kContext *ctx, E2kOperation *op,
+		     const gchar *uri, gboolean claim_ie,
+		     SoupBuffer **response)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (uri != NULL, E2K_HTTP_MALFORMED);
+
+	msg = get_msg (ctx, uri, TRUE, claim_ie);
+	status = e2k_context_send_message (ctx, op, msg);
+
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		if (response)
+			*response = soup_message_body_flatten (msg->response_body);
+	}
+
+	g_object_unref (msg);
+	return status;
+}
+
+/* PUT / POST */
+
+static SoupMessage *
+put_msg (E2kContext *ctx, const gchar *uri, const gchar *content_type,
+	 SoupMemoryUse buffer_type, const gchar *body, gint length)
+{
+	SoupMessage *msg;
+
+	msg = e2k_soup_message_new_full (ctx, uri, "PUT", content_type,
+					 buffer_type, body, length);
+	soup_message_headers_append (msg->request_headers, "Translate", "f");
+
+	return msg;
+}
+
+static SoupMessage *
+post_msg (E2kContext *ctx, const gchar *uri, const gchar *content_type,
+	  SoupMemoryUse buffer_type, const gchar *body, gint length)
+{
+	SoupMessage *msg;
+
+	msg = e2k_soup_message_new_full (ctx, uri, "POST", content_type,
+					 buffer_type, body, length);
+	soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
+
+	return msg;
+}
+
+static void
+extract_put_results (SoupMessage *msg, gchar **location, gchar **repl_uid)
+{
+	const gchar *header;
+
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (msg->status_code))
+		return;
+
+	if (repl_uid) {
+		header = soup_message_headers_get (msg->response_headers,
+						   "Repl-UID");
+		*repl_uid = g_strdup (header);
+	}
+	if (location) {
+		header = soup_message_headers_get (msg->response_headers,
+						   "Location");
+		*location = g_strdup (header);
+	}
+}
+
+/**
+ * e2k_context_put:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: the URI to PUT to
+ * @content_type: MIME Content-Type of the data
+ * @body: data to PUT
+ * @length: length of @body
+ * @repl_uid: if not %NULL, will contain the Repl-UID of the PUT
+ * object on return
+ *
+ * Performs a PUT operation on @ctx for @uri.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_put (E2kContext *ctx, E2kOperation *op, const gchar *uri,
+		 const gchar *content_type, const gchar *body, gint length,
+		 gchar **repl_uid)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (uri != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (content_type != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (body != NULL, E2K_HTTP_MALFORMED);
+
+	msg = put_msg (ctx, uri, content_type,
+		       SOUP_MEMORY_COPY, body, length);
+	status = e2k_context_send_message (ctx, op, msg);
+	extract_put_results (msg, NULL, repl_uid);
+
+	g_object_unref (msg);
+	return status;
+}
+
+/**
+ * e2k_context_put_new:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @folder_uri: the URI of the folder to PUT into
+ * @object_name: base name of the new object (not URI-encoded)
+ * @test_callback: callback to use to test possible object URIs
+ * @user_data: data for @test_callback
+ * @content_type: MIME Content-Type of the data
+ * @body: data to PUT
+ * @length: length of @body
+ * @location: if not %NULL, will contain the Location of the PUT
+ * object on return
+ * @repl_uid: if not %NULL, will contain the Repl-UID of the PUT
+ * object on return
+ *
+ * PUTs data into @folder_uri on @ctx with a new name based on
+ * @object_name. If @test_callback is non-%NULL, it will be called
+ * with each URI that is considered for the object so that the caller
+ * can check its summary data to see if that URI is in use
+ * (potentially saving one or more round-trips to the server).
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_put_new (E2kContext *ctx, E2kOperation *op,
+		     const gchar *folder_uri, const gchar *object_name,
+		     E2kContextTestCallback test_callback, gpointer user_data,
+		     const gchar *content_type, const gchar *body, gint length,
+		     gchar **location, gchar **repl_uid)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+	gchar *slash_uri, *encoded_name;
+	gint count;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (folder_uri != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (object_name != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (content_type != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (body != NULL, E2K_HTTP_MALFORMED);
+
+	slash_uri = e2k_strdup_with_trailing_slash (folder_uri);
+	encoded_name = e2k_uri_encode (object_name, TRUE, NULL);
+
+	/* folder_uri is a dummy here */
+	msg = put_msg (ctx, folder_uri, content_type,
+		       SOUP_MEMORY_COPY, body, length);
+	soup_message_headers_append (msg->request_headers, "If-None-Match", "*");
+
+	count = 1;
+	do {
+		update_unique_uri (ctx, msg, slash_uri, encoded_name, &count,
+				   test_callback, user_data);
+		status = e2k_context_send_message (ctx, op, msg);
+	} while (status == E2K_HTTP_PRECONDITION_FAILED);
+
+	extract_put_results (msg, location, repl_uid);
+
+	g_object_unref (msg);
+	g_free (slash_uri);
+	g_free (encoded_name);
+	return status;
+}
+
+/**
+ * e2k_context_post:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: the URI to POST to
+ * @content_type: MIME Content-Type of the data
+ * @body: data to PUT
+ * @length: length of @body
+ * @location: if not %NULL, will contain the Location of the POSTed
+ * object on return
+ * @repl_uid: if not %NULL, will contain the Repl-UID of the POSTed
+ * object on return
+ *
+ * Performs a POST operation on @ctx for @uri.
+ *
+ * Note that POSTed objects will be irrevocably(?) marked as "unsent",
+ * If you open a POSTed message in Outlook, it will open in the
+ * composer rather than in the message viewer.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_post (E2kContext *ctx, E2kOperation *op, const gchar *uri,
+		  const gchar *content_type, const gchar *body, gint length,
+		  gchar **location, gchar **repl_uid)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (uri != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (content_type != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (body != NULL, E2K_HTTP_MALFORMED);
+
+	msg = post_msg (ctx, uri, content_type,
+			SOUP_MEMORY_COPY, body, length);
+
+	status = e2k_context_send_message (ctx, op, msg);
+	extract_put_results (msg, location, repl_uid);
+
+	g_object_unref (msg);
+	return status;
+}
+
+/* PROPPATCH */
+
+static void
+add_namespaces (const gchar *namespace, gchar abbrev, gpointer user_data)
+{
+	GString *propxml = user_data;
+
+	g_string_append_printf (propxml, " xmlns:%c=\"%s\"", abbrev, namespace);
+}
+
+static void
+write_prop (GString *xml, const gchar *propertyname,
+	    E2kPropType type, gpointer value, gboolean set)
+{
+	const gchar *namespace, *name, *typestr;
+	gchar *encoded, abbrev;
+	gboolean b64enc, need_type;
+	GByteArray *data;
+	GPtrArray *array;
+	gint i;
+
+	if (set && (value == NULL))
+		return;
+
+	namespace = e2k_prop_namespace_name (propertyname);
+	abbrev = e2k_prop_namespace_abbrev (propertyname);
+	name = e2k_prop_property_name (propertyname);
+
+	g_string_append_printf (xml, "<%c:%s", abbrev, name);
+	if (!set) {
+		/* This means we are removing the property, so just return
+		   with ending tag */
+		g_string_append (xml, "/>");
+		return;
+	}
+
+	need_type = (strstr (namespace, "/mapi/id/") != NULL);
+	if (!need_type)
+		g_string_append_c (xml, '>');
+
+	switch (type) {
+	case E2K_PROP_TYPE_BINARY:
+		if (need_type)
+			g_string_append (xml, " T:dt=\"bin.base64\">");
+		data = value;
+		encoded = g_base64_encode (data->data, data->len);
+		g_string_append (xml, encoded);
+		g_free (encoded);
+		break;
+
+	case E2K_PROP_TYPE_STRING_ARRAY:
+		typestr = " T:dt=\"mv.string\">";
+		b64enc = FALSE;
+		goto array_common;
+
+	case E2K_PROP_TYPE_INT_ARRAY:
+		typestr = " T:dt=\"mv.int\">";
+		b64enc = FALSE;
+		goto array_common;
+
+	case E2K_PROP_TYPE_BINARY_ARRAY:
+		typestr = " T:dt=\"mv.bin.base64\">";
+		b64enc = TRUE;
+
+	array_common:
+		if (need_type)
+			g_string_append (xml, typestr);
+		array = value;
+		for (i = 0; i < array->len; i++) {
+			g_string_append (xml, "<X:v>");
+
+			if (b64enc) {
+				data = array->pdata[i];
+				encoded = g_base64_encode (data->data,
+							     data->len);
+				g_string_append (xml, encoded);
+				g_free (encoded);
+			} else
+				e2k_g_string_append_xml_escaped (xml, array->pdata[i]);
+
+			g_string_append (xml, "</X:v>");
+		}
+		break;
+
+	case E2K_PROP_TYPE_XML:
+		g_assert_not_reached ();
+		break;
+
+	case E2K_PROP_TYPE_STRING:
+	default:
+		if (need_type) {
+			switch (type) {
+			case E2K_PROP_TYPE_INT:
+				typestr = " T:dt=\"int\">";
+				break;
+			case E2K_PROP_TYPE_BOOL:
+				typestr = " T:dt=\"boolean\">";
+				break;
+			case E2K_PROP_TYPE_FLOAT:
+				typestr = " T:dt=\"float\">";
+				break;
+			case E2K_PROP_TYPE_DATE:
+				typestr = " T:dt=\"dateTime.tz\">";
+				break;
+			default:
+				typestr = ">";
+				break;
+			}
+			g_string_append (xml, typestr);
+		}
+		e2k_g_string_append_xml_escaped (xml, value);
+		break;
+
+	}
+
+	g_string_append_printf (xml, "</%c:%s>", abbrev, name);
+}
+
+static void
+add_set_props (const gchar *propertyname, E2kPropType type,
+	       gpointer value, gpointer user_data)
+{
+	GString **props = user_data;
+
+	if (!*props)
+		*props = g_string_new (NULL);
+
+	write_prop (*props, propertyname, type, value, TRUE);
+}
+
+static void
+add_remove_props (const gchar *propertyname, E2kPropType type,
+		  gpointer value, gpointer user_data)
+{
+	GString **props = user_data;
+
+	if (!*props)
+		*props = g_string_new (NULL);
+
+	write_prop (*props, propertyname, type, value, FALSE);
+}
+
+static SoupMessage *
+patch_msg (E2kContext *ctx, const gchar *uri, const gchar *method,
+	   const gchar **hrefs, gint nhrefs, E2kProperties *props,
+	   gboolean create)
+{
+	SoupMessage *msg;
+	GString *propxml, *subxml;
+	gint i;
+
+	propxml = g_string_new (E2K_XML_HEADER);
+	g_string_append (propxml, "<D:propertyupdate xmlns:D=\"DAV:\"");
+
+	/* Iterate over the properties, noting each namespace once,
+	 * then add them all to the header.
+	 */
+	e2k_properties_foreach_namespace (props, add_namespaces, propxml);
+	g_string_append (propxml, ">\r\n");
+
+	/* If this is a BPROPPATCH, add the <target> section. */
+	if (hrefs) {
+		g_string_append (propxml, "<D:target>\r\n");
+		for (i = 0; i < nhrefs; i++) {
+			g_string_append_printf (propxml, "<D:href>%s</D:href>",
+						hrefs[i]);
+		}
+		g_string_append (propxml, "\r\n</D:target>\r\n");
+	}
+
+	/* Add <set> properties. */
+	subxml = NULL;
+	e2k_properties_foreach (props, add_set_props, &subxml);
+	if (subxml) {
+		g_string_append (propxml, "<D:set><D:prop>\r\n");
+		g_string_append (propxml, subxml->str);
+		g_string_append (propxml, "\r\n</D:prop></D:set>");
+		g_string_free (subxml, TRUE);
+	}
+
+	/* Add <remove> properties. */
+	subxml = NULL;
+	e2k_properties_foreach_removed (props, add_remove_props, &subxml);
+	if (subxml) {
+		g_string_append (propxml, "<D:remove><D:prop>\r\n");
+		g_string_append (propxml, subxml->str);
+		g_string_append (propxml, "\r\n</D:prop></D:remove>");
+		g_string_free (subxml, TRUE);
+	}
+
+	/* Finish it up */
+	g_string_append (propxml, "\r\n</D:propertyupdate>");
+
+	/* And build the message. */
+	msg = e2k_soup_message_new_full (ctx, uri, method,
+					 "text/xml", SOUP_MEMORY_TAKE,
+					 propxml->str, propxml->len);
+	g_string_free (propxml, FALSE);
+	soup_message_headers_append (msg->request_headers, "Brief", "t");
+	if (!create)
+		soup_message_headers_append (msg->request_headers, "If-Match", "*");
+
+	return msg;
+}
+
+/**
+ * e2k_context_proppatch:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: the URI to PROPPATCH
+ * @props: the properties to set/remove
+ * @create: whether or not to create @uri if it does not exist
+ * @repl_uid: if not %NULL, will contain the Repl-UID of the
+ * PROPPATCHed object on return
+ *
+ * Performs a PROPPATCH operation on @ctx for @uri.
+ *
+ * If @create is %FALSE and @uri does not already exist, the response
+ * code will be %E2K_HTTP_PRECONDITION_FAILED.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_proppatch (E2kContext *ctx, E2kOperation *op,
+		       const gchar *uri, E2kProperties *props,
+		       gboolean create, gchar **repl_uid)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (uri != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (props != NULL, E2K_HTTP_MALFORMED);
+
+	msg = patch_msg (ctx, uri, "PROPPATCH", NULL, 0, props, create);
+	status = e2k_context_send_message (ctx, op, msg);
+	extract_put_results (msg, NULL, repl_uid);
+
+	g_object_unref (msg);
+	return status;
+}
+
+/**
+ * e2k_context_proppatch_new:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @folder_uri: the URI of the folder to PROPPATCH a new object in
+ * @object_name: base name of the new object (not URI-encoded)
+ * @test_callback: callback to use to test possible object URIs
+ * @user_data: data for @test_callback
+ * @props: the properties to set/remove
+ * @location: if not %NULL, will contain the Location of the
+ * PROPPATCHed object on return
+ * @repl_uid: if not %NULL, will contain the Repl-UID of the
+ * PROPPATCHed object on return
+ *
+ * PROPPATCHes data into @folder_uri on @ctx with a new name based on
+ * @object_name. If @test_callback is non-%NULL, it will be called
+ * with each URI that is considered for the object so that the caller
+ * can check its summary data to see if that URI is in use
+ * (potentially saving one or more round-trips to the server).
+
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_proppatch_new (E2kContext *ctx, E2kOperation *op,
+			   const gchar *folder_uri, const gchar *object_name,
+			   E2kContextTestCallback test_callback,
+			   gpointer user_data,
+			   E2kProperties *props,
+			   gchar **location, gchar **repl_uid)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+	gchar *slash_uri, *encoded_name;
+	gint count;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (folder_uri != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (object_name != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (props != NULL, E2K_HTTP_MALFORMED);
+
+	slash_uri = e2k_strdup_with_trailing_slash (folder_uri);
+	encoded_name = e2k_uri_encode (object_name, TRUE, NULL);
+
+	/* folder_uri is a dummy here */
+	msg = patch_msg (ctx, folder_uri, "PROPPATCH", NULL, 0, props, TRUE);
+	soup_message_headers_append (msg->request_headers, "If-None-Match", "*");
+
+	count = 1;
+	do {
+		update_unique_uri (ctx, msg, slash_uri, encoded_name, &count,
+				   test_callback, user_data);
+		status = e2k_context_send_message (ctx, op, msg);
+	} while (status == E2K_HTTP_PRECONDITION_FAILED);
+
+	if (location)
+		*location = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
+	extract_put_results (msg, NULL, repl_uid);
+
+	g_object_unref (msg);
+	g_free (slash_uri);
+	g_free (encoded_name);
+	return status;
+}
+
+static E2kHTTPStatus
+bproppatch_fetch (E2kResultIter *iter,
+		  E2kContext *ctx, E2kOperation *op,
+		  E2kResult **results, gint *nresults,
+		  gint *first, gint *total,
+		  gpointer user_data)
+{
+	SoupMessage *msg = user_data;
+	E2kHTTPStatus status;
+
+	/* We only want to send the BPROPPATCH once. So check if we've
+	 * already done that.
+	 */
+	if (msg->status_code != 0)
+		return E2K_HTTP_OK;
+
+	status = e2k_context_send_message (ctx, op, msg);
+	if (status == E2K_HTTP_MULTI_STATUS) {
+		e2k_results_from_multistatus (msg, results, nresults);
+		*total = *nresults;
+	}
+	return status;
+}
+
+static void
+bproppatch_free (E2kResultIter *iter, gpointer msg)
+{
+	g_object_unref (msg);
+}
+
+/**
+ * e2k_context_bproppatch_start:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: the base URI
+ * @hrefs: array of URIs, possibly relative to @uri
+ * @nhrefs: length of @hrefs
+ * @props: the properties to set/remove
+ * @create: whether or not to create @uri if it does not exist
+ *
+ * Begins a BPROPPATCH (bulk PROPPATCH) of @hrefs based at @uri.
+ *
+ * Return value: an iterator for getting the results of the BPROPPATCH
+ **/
+E2kResultIter *
+e2k_context_bproppatch_start (E2kContext *ctx, E2kOperation *op,
+			      const gchar *uri, const gchar **hrefs, gint nhrefs,
+			      E2kProperties *props, gboolean create)
+{
+	SoupMessage *msg;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), NULL);
+	g_return_val_if_fail (uri != NULL, NULL);
+	g_return_val_if_fail (props != NULL, NULL);
+
+	msg = patch_msg (ctx, uri, "BPROPPATCH", hrefs, nhrefs, props, create);
+	return e2k_result_iter_new (ctx, op, TRUE, -1,
+				    bproppatch_fetch, bproppatch_free,
+				    msg);
+}
+
+/* PROPFIND */
+
+static SoupMessage *
+propfind_msg (E2kContext *ctx, const gchar *base_uri,
+	      const gchar **props, gint nprops, const gchar **hrefs, gint nhrefs)
+{
+	SoupMessage *msg;
+	GString *propxml;
+	GData *set_namespaces;
+	const gchar *name;
+	gchar abbrev;
+	gint i;
+
+	propxml = g_string_new (E2K_XML_HEADER);
+	g_string_append (propxml, "<D:propfind xmlns:D=\"DAV:\"");
+
+	set_namespaces = NULL;
+	for (i = 0; i < nprops; i++) {
+		name = e2k_prop_namespace_name (props[i]);
+		abbrev = e2k_prop_namespace_abbrev (props[i]);
+
+		if (!g_datalist_get_data (&set_namespaces, name)) {
+			g_datalist_set_data (&set_namespaces, name,
+					     GINT_TO_POINTER (1));
+			g_string_append_printf (propxml, " xmlns:%c=\"%s\"",
+						abbrev, name);
+		}
+	}
+	g_datalist_clear (&set_namespaces);
+	g_string_append (propxml, ">\r\n");
+
+	if (hrefs) {
+		g_string_append (propxml, "<D:target>\r\n");
+		for (i = 0; i < nhrefs; i++) {
+			g_string_append_printf (propxml, "<D:href>%s</D:href>",
+						hrefs[i]);
+		}
+		g_string_append (propxml, "\r\n</D:target>\r\n");
+	}
+
+	g_string_append (propxml, "<D:prop>\r\n");
+	for (i = 0; i < nprops; i++) {
+		abbrev = e2k_prop_namespace_abbrev (props[i]);
+		name = e2k_prop_property_name (props[i]);
+		g_string_append_printf (propxml, "<%c:%s/>", abbrev, name);
+	}
+	g_string_append (propxml, "\r\n</D:prop>\r\n</D:propfind>");
+
+	msg = e2k_soup_message_new_full (ctx, base_uri,
+					 hrefs ? "BPROPFIND" : "PROPFIND",
+					 "text/xml", SOUP_MEMORY_TAKE,
+					 propxml->str, propxml->len);
+	g_string_free (propxml, FALSE);
+	soup_message_headers_append (msg->request_headers, "Brief", "t");
+	soup_message_headers_append (msg->request_headers, "Depth", "0");
+
+	return msg;
+}
+
+/**
+ * e2k_context_propfind:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: the URI to PROPFIND on
+ * @props: array of properties to find
+ * @nprops: length of @props
+ * @results: on return, the results
+ * @nresults: length of @results
+ *
+ * Performs a PROPFIND operation on @ctx for @uri. If successful, the
+ * results are returned as an array of #E2kResult (which you must free
+ * with e2k_results_free()), but the array will always have either 0
+ * or 1 members.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_propfind (E2kContext *ctx, E2kOperation *op,
+		      const gchar *uri, const gchar **props, gint nprops,
+		      E2kResult **results, gint *nresults)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (uri != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (props != NULL, E2K_HTTP_MALFORMED);
+
+	msg = propfind_msg (ctx, uri, props, nprops, NULL, 0);
+	status = e2k_context_send_message (ctx, op, msg);
+
+	if (msg->status_code == E2K_HTTP_MULTI_STATUS)
+		e2k_results_from_multistatus (msg, results, nresults);
+	g_object_unref (msg);
+	return status;
+}
+
+static E2kHTTPStatus
+bpropfind_fetch (E2kResultIter *iter,
+		 E2kContext *ctx, E2kOperation *op,
+		 E2kResult **results, gint *nresults,
+		 gint *first, gint *total,
+		 gpointer user_data)
+{
+	GSList **msgs = user_data;
+	E2kHTTPStatus status;
+	SoupMessage *msg;
+
+	if (!*msgs)
+		return E2K_HTTP_OK;
+
+	msg = (*msgs)->data;
+	*msgs = g_slist_remove (*msgs, msg);
+
+	status = e2k_context_send_message (ctx, op, msg);
+	if (status == E2K_HTTP_MULTI_STATUS)
+		e2k_results_from_multistatus (msg, results, nresults);
+	g_object_unref (msg);
+
+	return status;
+}
+
+static void
+bpropfind_free (E2kResultIter *iter, gpointer user_data)
+{
+	GSList **msgs = user_data, *m;
+
+	for (m = *msgs; m; m = m->next)
+		g_object_unref (m->data);
+	g_slist_free (*msgs);
+	g_free (msgs);
+}
+
+/**
+ * e2k_context_bpropfind_start:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: the base URI
+ * @hrefs: array of URIs, possibly relative to @uri
+ * @nhrefs: length of @hrefs
+ * @props: array of properties to find
+ * @nprops: length of @props
+ *
+ * Begins a BPROPFIND (bulk PROPFIND) operation on @ctx for @hrefs.
+ *
+ * Return value: an iterator for getting the results
+ **/
+E2kResultIter *
+e2k_context_bpropfind_start (E2kContext *ctx, E2kOperation *op,
+			     const gchar *uri, const gchar **hrefs, gint nhrefs,
+			     const gchar **props, gint nprops)
+{
+	SoupMessage *msg;
+	GSList **msgs;
+	gint i;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), NULL);
+	g_return_val_if_fail (uri != NULL, NULL);
+	g_return_val_if_fail (props != NULL, NULL);
+	g_return_val_if_fail (hrefs != NULL, NULL);
+
+	msgs = g_new0 (GSList *, 1);
+	for (i = 0; i < nhrefs; i += E2K_CONTEXT_MAX_BATCH_SIZE) {
+		msg = propfind_msg (ctx, uri, props, nprops,
+				    hrefs + i, MIN (E2K_CONTEXT_MAX_BATCH_SIZE, nhrefs - i));
+		*msgs = g_slist_append (*msgs, msg);
+	}
+
+	return e2k_result_iter_new (ctx, op, TRUE, nhrefs,
+				    bpropfind_fetch, bpropfind_free,
+				    msgs);
+}
+
+/* SEARCH */
+
+static SoupMessage *
+search_msg (E2kContext *ctx, const gchar *uri,
+	    SoupMemoryUse buffer_type, const gchar *searchxml,
+	    gint size, gboolean ascending, gint offset)
+{
+	SoupMessage *msg;
+
+	msg = e2k_soup_message_new_full (ctx, uri, "SEARCH", "text/xml",
+					 buffer_type, searchxml,
+					 strlen (searchxml));
+	soup_message_headers_append (msg->request_headers, "Brief", "t");
+
+	if (size) {
+		gchar *range;
+
+		if (offset == INT_MAX) {
+			range = g_strdup_printf ("rows=-%u", size);
+		} else {
+			range = g_strdup_printf ("rows=%u-%u",
+						 offset, offset + size - 1);
+		}
+		soup_message_headers_append (msg->request_headers, "Range", range);
+		g_free (range);
+	}
+
+	return msg;
+}
+
+static gchar *
+search_xml (const gchar **props, gint nprops,
+	    E2kRestriction *rn, const gchar *orderby)
+{
+	GString *xml;
+	gchar *ret, *where;
+	gint i;
+
+	xml = g_string_new (E2K_XML_HEADER);
+	g_string_append (xml, "<searchrequest xmlns=\"DAV:\"><sql>\r\n");
+	g_string_append (xml, "SELECT ");
+
+	for (i = 0; i < nprops; i++) {
+		if (i > 0)
+			g_string_append (xml, ", ");
+		g_string_append_c (xml, '"');
+		g_string_append   (xml, props[i]);
+		g_string_append_c (xml, '"');
+	}
+
+	if (e2k_restriction_folders_only (rn))
+		g_string_append_printf (xml, "\r\nFROM SCOPE('hierarchical traversal of \"\"')\r\n");
+	else
+		g_string_append (xml, "\r\nFROM \"\"\r\n");
+
+	if (rn) {
+		where = e2k_restriction_to_sql (rn);
+		if (where) {
+			e2k_g_string_append_xml_escaped (xml, where);
+			g_string_append (xml, "\r\n");
+			g_free (where);
+		}
+	}
+
+	if (orderby)
+		g_string_append_printf (xml, "ORDER BY \"%s\"\r\n", orderby);
+
+	g_string_append (xml, "</sql></searchrequest>");
+
+	ret = xml->str;
+	g_string_free (xml, FALSE);
+
+	return ret;
+}
+
+static gboolean
+search_result_get_range (SoupMessage *msg, gint *first, gint *total)
+{
+	const gchar *range, *p;
+
+	range = soup_message_headers_get (msg->response_headers,
+					  "Content-Range");
+	if (!range)
+		return FALSE;
+	p = strstr (range, "rows ");
+	if (!p)
+		return FALSE;
+
+	if (first)
+		*first = atoi (p + 5);
+
+	if (total) {
+		p = strstr (range, "total=");
+		if (p)
+			*total = atoi (p + 6);
+		else
+			*total = -1;
+	}
+
+	return TRUE;
+}
+
+typedef struct {
+	gchar *uri, *xml;
+	gboolean ascending;
+	gint batch_size, next;
+} E2kSearchData;
+
+static E2kHTTPStatus
+search_fetch (E2kResultIter *iter,
+	      E2kContext *ctx, E2kOperation *op,
+	      E2kResult **results, gint *nresults,
+	      gint *first, gint *total,
+	      gpointer user_data)
+{
+	E2kSearchData *search_data = user_data;
+	E2kHTTPStatus status;
+	SoupMessage *msg;
+
+	if (search_data->batch_size == 0)
+		return E2K_HTTP_OK;
+
+	msg = search_msg (ctx, search_data->uri,
+			  SOUP_MEMORY_COPY, search_data->xml,
+			  search_data->batch_size,
+			  search_data->ascending, search_data->next);
+	status = e2k_context_send_message (ctx, op, msg);
+	if (msg->status_code == E2K_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE)
+		status = E2K_HTTP_OK;
+	else if (status == E2K_HTTP_MULTI_STATUS) {
+		search_result_get_range (msg, first, total);
+		if (*total == 0)
+			goto cleanup;
+
+		e2k_results_from_multistatus (msg, results, nresults);
+		if (*total == -1)
+			*total = *first + *nresults;
+
+		if (search_data->ascending && *first + *nresults < *total)
+			search_data->next = *first + *nresults;
+		else if (!search_data->ascending && *first > 0) {
+			if (*first >= search_data->batch_size)
+				search_data->next = *first - search_data->batch_size;
+			else {
+				search_data->batch_size = *first;
+				search_data->next = 0;
+			}
+		} else
+			search_data->batch_size = 0;
+	}
+
+ cleanup:
+	g_object_unref (msg);
+	return status;
+}
+
+static void
+search_free (E2kResultIter *iter, gpointer user_data)
+{
+	E2kSearchData *search_data = user_data;
+
+	g_free (search_data->uri);
+	g_free (search_data->xml);
+	g_free (search_data);
+}
+
+/**
+ * e2k_context_search_start:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: the folder to search
+ * @props: the properties to search for
+ * @nprops: size of @props array
+ * @rn: the search restriction
+ * @orderby: if non-%NULL, the field to sort the search results by
+ * @ascending: %TRUE for an ascending search, %FALSE for descending.
+ *
+ * Begins a SEARCH on @ctx at @uri.
+ *
+ * Return value: an iterator for returning the search results
+ **/
+E2kResultIter *
+e2k_context_search_start (E2kContext *ctx, E2kOperation *op, const gchar *uri,
+			  const gchar **props, gint nprops, E2kRestriction *rn,
+			  const gchar *orderby, gboolean ascending)
+{
+	E2kSearchData *search_data;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), NULL);
+	g_return_val_if_fail (uri != NULL, NULL);
+	g_return_val_if_fail (props != NULL, NULL);
+
+	search_data = g_new0 (E2kSearchData, 1);
+	search_data->uri = g_strdup (uri);
+	search_data->xml = search_xml (props, nprops, rn, orderby);
+	search_data->ascending = ascending;
+	search_data->batch_size = E2K_CONTEXT_MAX_BATCH_SIZE;
+	search_data->next = ascending ? 0 : INT_MAX;
+
+	return e2k_result_iter_new (ctx, op, ascending, -1,
+				    search_fetch, search_free,
+				    search_data);
+}
+
+/* DELETE */
+
+static SoupMessage *
+delete_msg (E2kContext *ctx, const gchar *uri)
+{
+	return e2k_soup_message_new (ctx, uri, "DELETE");
+}
+
+/**
+ * e2k_context_delete:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: URI to DELETE
+ *
+ * Attempts to DELETE @uri on @ctx.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_delete (E2kContext *ctx, E2kOperation *op, const gchar *uri)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (uri != NULL, E2K_HTTP_MALFORMED);
+
+	msg = delete_msg (ctx, uri);
+	status = e2k_context_send_message (ctx, op, msg);
+
+	g_object_unref (msg);
+	return status;
+}
+
+/* BDELETE */
+
+static SoupMessage *
+bdelete_msg (E2kContext *ctx, const gchar *uri, const gchar **hrefs, gint nhrefs)
+{
+	SoupMessage *msg;
+	GString *xml;
+	gint i;
+
+	xml = g_string_new (E2K_XML_HEADER "<delete xmlns=\"DAV:\"><target>");
+
+	for (i = 0; i < nhrefs; i++) {
+		g_string_append (xml, "<href>");
+		e2k_g_string_append_xml_escaped (xml, hrefs[i]);
+		g_string_append (xml, "</href>");
+	}
+
+	g_string_append (xml, "</target></delete>");
+
+	msg = e2k_soup_message_new_full (ctx, uri, "BDELETE", "text/xml",
+					 SOUP_MEMORY_TAKE,
+					 xml->str, xml->len);
+	g_string_free (xml, FALSE);
+
+	return msg;
+}
+
+static E2kHTTPStatus
+bdelete_fetch (E2kResultIter *iter,
+	       E2kContext *ctx, E2kOperation *op,
+	       E2kResult **results, gint *nresults,
+	       gint *first, gint *total,
+	       gpointer user_data)
+{
+	GSList **msgs = user_data;
+	E2kHTTPStatus status;
+	SoupMessage *msg;
+
+	if (!*msgs)
+		return E2K_HTTP_OK;
+
+	msg = (*msgs)->data;
+	*msgs = g_slist_remove (*msgs, msg);
+
+	status = e2k_context_send_message (ctx, op, msg);
+	if (status == E2K_HTTP_MULTI_STATUS)
+		e2k_results_from_multistatus (msg, results, nresults);
+	g_object_unref (msg);
+
+	return status;
+}
+
+static void
+bdelete_free (E2kResultIter *iter, gpointer user_data)
+{
+	GSList **msgs = user_data, *m;
+
+	for (m = (*msgs); m; m = m->next)
+		g_object_unref (m->data);
+	g_slist_free (*msgs);
+	g_free (msgs);
+}
+
+/**
+ * e2k_context_bdelete_start:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: the base URI
+ * @hrefs: array of URIs, possibly relative to @uri, to delete
+ * @nhrefs: length of @hrefs
+ *
+ * Begins a BDELETE (bulk DELETE) operation on @ctx for @hrefs.
+ *
+ * Return value: an iterator for returning the results
+ **/
+E2kResultIter *
+e2k_context_bdelete_start (E2kContext *ctx, E2kOperation *op,
+			   const gchar *uri, const gchar **hrefs, gint nhrefs)
+{
+	GSList **msgs;
+	gint i, batchsize;
+	SoupMessage *msg;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), NULL);
+	g_return_val_if_fail (uri != NULL, NULL);
+	g_return_val_if_fail (hrefs != NULL, NULL);
+
+	batchsize = (nhrefs + 9) / 10;
+	if (batchsize < E2K_CONTEXT_MIN_BATCH_SIZE)
+		batchsize = E2K_CONTEXT_MIN_BATCH_SIZE;
+	else if (batchsize > E2K_CONTEXT_MAX_BATCH_SIZE)
+		batchsize = E2K_CONTEXT_MAX_BATCH_SIZE;
+
+	msgs = g_new0 (GSList *, 1);
+	for (i = 0; i < nhrefs; i += batchsize) {
+		batchsize = MIN (batchsize, nhrefs - i);
+		msg = bdelete_msg (ctx, uri, hrefs + i, batchsize);
+		*msgs = g_slist_prepend (*msgs, msg);
+	}
+
+	return e2k_result_iter_new (ctx, op, TRUE, nhrefs,
+				    bdelete_fetch, bdelete_free,
+				    msgs);
+}
+
+/* MKCOL */
+
+/**
+ * e2k_context_mkcol:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @uri: URI of the new folder
+ * @props: properties to set on the new folder, or %NULL
+ * @permanent_url: if not %NULL, will contain the permanent URL of the
+ * new folder on return
+ *
+ * Performs a MKCOL operation on @ctx to create @uri, with optional
+ * additional properties.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_mkcol (E2kContext *ctx, E2kOperation *op,
+		   const gchar *uri, E2kProperties *props,
+		   gchar **permanent_url)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (uri != NULL, E2K_HTTP_MALFORMED);
+
+	if (!props)
+		msg = e2k_soup_message_new (ctx, uri, "MKCOL");
+	else
+		msg = patch_msg (ctx, uri, "MKCOL", NULL, 0, props, TRUE);
+
+	status = e2k_context_send_message (ctx, op, msg);
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status) && permanent_url) {
+		const gchar *header;
+
+		header = soup_message_headers_get (msg->response_headers,
+						   "MS-Exchange-Permanent-URL");
+		*permanent_url = g_strdup (header);
+	}
+
+	g_object_unref (msg);
+	return status;
+}
+
+/* BMOVE / BCOPY */
+
+static SoupMessage *
+transfer_msg (E2kContext *ctx,
+	      const gchar *source_uri, const gchar *dest_uri,
+	      const gchar **source_hrefs, gint nhrefs,
+	      gboolean delete_originals)
+{
+	SoupMessage *msg;
+	GString *xml;
+	gint i;
+
+	xml = g_string_new (E2K_XML_HEADER);
+	g_string_append (xml, delete_originals ? "<move" : "<copy");
+	g_string_append (xml, " xmlns=\"DAV:\"><target>");
+	for (i = 0; i < nhrefs; i++) {
+		g_string_append (xml, "<href>");
+		e2k_g_string_append_xml_escaped (xml, source_hrefs[i]);
+		g_string_append (xml, "</href>");
+	}
+	g_string_append (xml, "</target></");
+	g_string_append (xml, delete_originals ? "move>" : "copy>");
+
+	msg = e2k_soup_message_new_full (ctx, source_uri,
+					 delete_originals ? "BMOVE" : "BCOPY",
+					 "text/xml",
+					 SOUP_MEMORY_TAKE,
+					 xml->str, xml->len);
+	soup_message_headers_append (msg->request_headers, "Overwrite", "f");
+	soup_message_headers_append (msg->request_headers, "Allow-Rename", "t");
+	soup_message_headers_append (msg->request_headers, "Destination", dest_uri);
+	g_string_free (xml, FALSE);
+
+	return msg;
+}
+
+static E2kHTTPStatus
+transfer_next (E2kResultIter *iter,
+	       E2kContext *ctx, E2kOperation *op,
+	       E2kResult **results, gint *nresults,
+	       gint *first, gint *total,
+	       gpointer user_data)
+{
+	GSList **msgs = user_data;
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	if (!*msgs)
+		return E2K_HTTP_OK;
+
+	msg = (*msgs)->data;
+	*msgs = g_slist_remove (*msgs, msg);
+
+	status = e2k_context_send_message (ctx, op, msg);
+	if (status == E2K_HTTP_MULTI_STATUS)
+		e2k_results_from_multistatus (msg, results, nresults);
+
+	g_object_unref (msg);
+	return status;
+}
+
+static void
+transfer_free (E2kResultIter *iter, gpointer user_data)
+{
+	GSList **msgs = user_data, *m;
+
+	for (m = *msgs; m; m = m->next)
+		g_object_unref (m->data);
+	g_slist_free (*msgs);
+	g_free (msgs);
+}
+
+/**
+ * e2k_context_transfer_start:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @source_folder: URI of the source folder
+ * @dest_folder: URI of the destination folder
+ * @source_hrefs: an array of hrefs to move, relative to @source_folder
+ * @delete_originals: whether or not to delete the original objects
+ *
+ * Starts a BMOVE or BCOPY (depending on @delete_originals) operation
+ * on @ctx for @source_folder. The objects in @source_folder described
+ * by @source_hrefs will be moved or copied to @dest_folder.
+ * e2k_result_iter_next() can be used to check the success or failure
+ * of each move/copy. (The #E2K_PR_DAV_LOCATION property for each
+ * result will show the new location of the object.)
+ *
+ * NB: may not work correctly if @source_hrefs contains folders
+ *
+ * Return value: the iterator for the results
+ **/
+E2kResultIter *
+e2k_context_transfer_start (E2kContext *ctx, E2kOperation *op,
+			    const gchar *source_folder, const gchar *dest_folder,
+			    GPtrArray *source_hrefs, gboolean delete_originals)
+{
+	GSList **msgs;
+	SoupMessage *msg;
+	gchar *dest_uri;
+	const gchar **hrefs;
+	gint i;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), NULL);
+	g_return_val_if_fail (source_folder != NULL, NULL);
+	g_return_val_if_fail (dest_folder != NULL, NULL);
+	g_return_val_if_fail (source_hrefs != NULL, NULL);
+
+	dest_uri = e2k_strdup_with_trailing_slash (dest_folder);
+	if (!dest_uri)
+		return NULL;
+	hrefs = (const gchar **)source_hrefs->pdata;
+
+	msgs = g_new0 (GSList *, 1);
+	for (i = 0; i < source_hrefs->len; i += E2K_CONTEXT_MAX_BATCH_SIZE) {
+		msg = transfer_msg (ctx, source_folder, dest_uri,
+				    hrefs + i, MIN (E2K_CONTEXT_MAX_BATCH_SIZE, source_hrefs->len - i),
+				    delete_originals);
+		*msgs = g_slist_append (*msgs, msg);
+	}
+	g_free (dest_uri);
+
+	return e2k_result_iter_new (ctx, op, TRUE, source_hrefs->len,
+				    transfer_next, transfer_free,
+				    msgs);
+}
+
+/**
+ * e2k_context_transfer_dir:
+ * @ctx: the context
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @source_href: URI of the source folder
+ * @dest_href: URI of the destination folder
+ * @delete_original: whether or not to delete the original folder
+ * @permanent_url: if not %NULL, will contain the permanent URL of the
+ * new folder on return
+ *
+ * Performs a MOVE or COPY (depending on @delete_original) operation
+ * on @ctx for @source_href. The folder itself will be moved, renamed,
+ * or copied to @dest_href (which is the name of the new folder
+ * itself, not its parent).
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e2k_context_transfer_dir (E2kContext *ctx, E2kOperation *op,
+			  const gchar *source_href, const gchar *dest_href,
+			  gboolean delete_original,
+			  gchar **permanent_url)
+{
+	SoupMessage *msg;
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (E2K_IS_CONTEXT (ctx), E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (source_href != NULL, E2K_HTTP_MALFORMED);
+	g_return_val_if_fail (dest_href != NULL, E2K_HTTP_MALFORMED);
+
+	msg = e2k_soup_message_new (ctx, source_href, delete_original ? "MOVE" : "COPY");
+	soup_message_headers_append (msg->request_headers, "Overwrite", "f");
+	soup_message_headers_append (msg->request_headers, "Destination", dest_href);
+
+	status = e2k_context_send_message (ctx, op, msg);
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status) && permanent_url) {
+		const gchar *header;
+
+		header = soup_message_headers_get (msg->response_headers,
+						   "MS-Exchange-Permanent-URL");
+		*permanent_url = g_strdup (header);
+	}
+
+	g_object_unref (msg);
+	return status;
+}
+
+/* Subscriptions */
+
+typedef struct {
+	E2kContext *ctx;
+	gchar *uri, *id;
+	E2kContextChangeType type;
+	gint lifetime, min_interval;
+	time_t last_notification;
+
+	E2kContextChangeCallback callback;
+	gpointer user_data;
+
+	guint renew_timeout;
+	SoupMessage *renew_msg;
+	guint poll_timeout;
+	SoupMessage *poll_msg;
+	guint notification_timeout;
+} E2kSubscription;
+
+static gboolean
+belated_notification (gpointer user_data)
+{
+	E2kSubscription *sub = user_data;
+
+	sub->notification_timeout = 0;
+	sub->callback (sub->ctx, sub->uri, sub->type, sub->user_data);
+	return FALSE;
+}
+
+static void
+maybe_notification (E2kSubscription *sub)
+{
+	time_t now = time (NULL);
+	gint delay = sub->last_notification + sub->min_interval - now;
+
+	if (delay > 0) {
+		if (sub->notification_timeout)
+			g_source_remove (sub->notification_timeout);
+		sub->notification_timeout = g_timeout_add (delay * 1000,
+							   belated_notification,
+							   sub);
+		return;
+	}
+	sub->last_notification = now;
+
+	sub->callback (sub->ctx, sub->uri, sub->type, sub->user_data);
+}
+
+static void
+polled (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+	E2kSubscription *sub = user_data;
+	E2kContext *ctx = sub->ctx;
+	E2kResult *results;
+	gint nresults, i;
+	xmlNode *ids;
+	xmlChar *id;
+
+	sub->poll_msg = NULL;
+	if (msg->status_code != E2K_HTTP_MULTI_STATUS) {
+		g_warning ("Unexpected error %d %s from POLL",
+			   msg->status_code, msg->reason_phrase);
+		return;
+	}
+
+	e2k_results_from_multistatus (msg, &results, &nresults);
+	for (i = 0; i < nresults; i++) {
+		if (results[i].status != E2K_HTTP_OK)
+			continue;
+
+		ids = e2k_properties_get_prop (results[i].props, E2K_PR_SUBSCRIPTION_ID);
+		if (!ids)
+			continue;
+		for (ids = ids->xmlChildrenNode; ids; ids = ids->next) {
+			if (xmlStrcmp (ids->name, (xmlChar *) "li") != 0 ||
+			    !ids->xmlChildrenNode ||
+			    !ids->xmlChildrenNode->content)
+				continue;
+			id = ids->xmlChildrenNode->content;
+			sub = g_hash_table_lookup (ctx->priv->subscriptions_by_id, id);
+			if (sub)
+				maybe_notification (sub);
+		}
+	}
+	e2k_results_free (results, nresults);
+}
+
+static gboolean
+timeout_notification (gpointer user_data)
+{
+	E2kSubscription *sub = user_data, *sub2;
+	E2kContext *ctx = sub->ctx;
+	GList *sub_list;
+	GString *subscription_ids;
+
+	sub->poll_timeout = 0;
+	subscription_ids = g_string_new (sub->id);
+
+	/* Find all subscriptions at this URI that are awaiting a
+	 * POLL so we can POLL them all at once.
+	 */
+	sub_list = g_hash_table_lookup (ctx->priv->subscriptions_by_uri,
+					sub->uri);
+	for (; sub_list; sub_list = sub_list->next) {
+		sub2 = sub_list->data;
+		if (sub2 == sub)
+			continue;
+		if (!sub2->poll_timeout)
+			continue;
+		g_source_remove (sub2->poll_timeout);
+		sub2->poll_timeout = 0;
+		g_string_append_printf (subscription_ids, ",%s", sub2->id);
+	}
+
+	sub->poll_msg = e2k_soup_message_new (ctx, sub->uri, "POLL");
+	soup_message_headers_append (sub->poll_msg->request_headers,
+				     "Subscription-id", subscription_ids->str);
+	e2k_context_queue_message (ctx, sub->poll_msg, polled, sub);
+
+	g_string_free (subscription_ids, TRUE);
+	return FALSE;
+}
+
+static gboolean
+do_notification (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+	E2kContext *ctx = data;
+	E2kSubscription *sub;
+	gchar buffer[1024], *id, *lasts;
+	gsize len;
+	GIOStatus status;
+
+	status = g_io_channel_read_chars (source, buffer, sizeof (buffer) - 1, &len, NULL);
+	if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN) {
+		g_warning ("do_notification I/O error: %d (%s)", status,
+			   g_strerror (errno));
+		return FALSE;
+	}
+	buffer[len] = '\0';
+
+#ifdef E2K_DEBUG
+	if (e2k_debug_level) {
+		if (e2k_debug_level == 1) {
+			fwrite (buffer, 1, strcspn (buffer, "\r\n"), stdout);
+			fputs ("\n\n", stdout);
+		} else
+			fputs (buffer, stdout);
+	}
+#endif
+
+	if (g_ascii_strncasecmp (buffer, "NOTIFY ", 7) != 0)
+		return TRUE;
+
+	id = buffer;
+	while (1) {
+		id = strchr (id, '\n');
+		if (!id++)
+			return TRUE;
+		if (g_ascii_strncasecmp (id, "Subscription-id: ", 17) == 0)
+			break;
+	}
+	id += 17;
+
+	for (id = strtok_r (id, ",\r", &lasts); id; id = strtok_r (NULL, ",\r", &lasts)) {
+		sub = g_hash_table_lookup (ctx->priv->subscriptions_by_id, id);
+		if (!sub)
+			continue;
+
+		/* We don't want to POLL right away in case there are
+		 * several changes in a row. So we just bump up the
+		 * timeout to be one second from now. (Using an idle
+		 * handler here doesn't actually work to prevent
+		 * multiple POLLs.)
+		 */
+		if (sub->poll_timeout)
+			g_source_remove (sub->poll_timeout);
+		sub->poll_timeout =
+			g_timeout_add (1000, timeout_notification, sub);
+	}
+
+	return TRUE;
+}
+
+static void
+renew_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+	E2kSubscription *sub = user_data;
+
+	sub->renew_msg = NULL;
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+		g_warning ("renew_subscription: %d %s", msg->status_code,
+			   msg->reason_phrase);
+		return;
+	}
+
+	if (sub->id) {
+		g_hash_table_remove (sub->ctx->priv->subscriptions_by_id, sub->id);
+		g_free (sub->id);
+	}
+	sub->id = g_strdup (soup_message_headers_get (msg->response_headers,
+						      "Subscription-id"));
+	g_return_if_fail (sub->id != NULL);
+	g_hash_table_insert (sub->ctx->priv->subscriptions_by_id,
+			     sub->id, sub);
+}
+
+#define E2K_SUBSCRIPTION_INITIAL_LIFETIME  3600 /*  1 hour  */
+#define E2K_SUBSCRIPTION_MAX_LIFETIME     57600 /* 16 hours */
+
+/* This must be kept in sync with E2kSubscriptionType */
+static const gchar *subscription_type[] = {
+	"update",		/* E2K_SUBSCRIPTION_OBJECT_CHANGED */
+	"update/newmember",	/* E2K_SUBSCRIPTION_OBJECT_ADDED */
+	"delete",		/* E2K_SUBSCRIPTION_OBJECT_REMOVED */
+	"move"			/* E2K_SUBSCRIPTION_OBJECT_MOVED */
+};
+
+static gboolean
+renew_subscription (gpointer user_data)
+{
+	E2kSubscription *sub = user_data;
+	E2kContext *ctx = sub->ctx;
+	gchar ltbuf[80];
+
+	if (!ctx->priv->notification_uri)
+		return FALSE;
+
+	if (sub->lifetime < E2K_SUBSCRIPTION_MAX_LIFETIME)
+		sub->lifetime *= 2;
+
+	sub->renew_msg = e2k_soup_message_new (ctx, sub->uri, "SUBSCRIBE");
+	sprintf (ltbuf, "%d", sub->lifetime);
+	soup_message_headers_append (sub->renew_msg->request_headers,
+				     "Subscription-lifetime", ltbuf);
+	soup_message_headers_append (sub->renew_msg->request_headers,
+				     "Notification-type",
+				     subscription_type[sub->type]);
+	if (sub->min_interval > 1) {
+		sprintf (ltbuf, "%d", sub->min_interval);
+		soup_message_headers_append (sub->renew_msg->request_headers,
+					     "Notification-delay", ltbuf);
+	}
+	soup_message_headers_append (sub->renew_msg->request_headers,
+				     "Call-back", ctx->priv->notification_uri);
+
+	e2k_context_queue_message (ctx, sub->renew_msg, renew_cb, sub);
+	sub->renew_timeout = g_timeout_add ((sub->lifetime - 60) * 1000,
+					    renew_subscription, sub);
+	return FALSE;
+}
+
+/**
+ * e2k_context_subscribe:
+ * @ctx: the context
+ * @uri: the folder URI to subscribe to notifications on
+ * @type: the type of notification to subscribe to
+ * @min_interval: the minimum interval (in seconds) between
+ * notifications.
+ * @callback: the callback to call when a notification has been
+ * received
+ * @user_data: data to pass to @callback.
+ *
+ * This subscribes to change notifications of the given @type on @uri.
+ * @callback will (eventually) be invoked any time the folder changes
+ * in the given way: whenever an object is added to it for
+ * %E2K_CONTEXT_OBJECT_ADDED, whenever an object is deleted (but
+ * not moved) from it (or the folder itself is deleted) for
+ * %E2K_CONTEXT_OBJECT_REMOVED, whenever an object is moved in or
+ * out of the folder for %E2K_CONTEXT_OBJECT_MOVED, and whenever
+ * any of the above happens, or the folder or one of its items is
+ * modified, for %E2K_CONTEXT_OBJECT_CHANGED. (This means that if
+ * you subscribe to both CHANGED and some other notification on the
+ * same folder that multiple callbacks may be invoked every time an
+ * object is added/removed/moved/etc.)
+ *
+ * Notifications can be used *only* to discover changes made by other
+ * clients! The code cannot assume that it will receive a notification
+ * for every change that it makes to the server, for two reasons:
+ *
+ * First, if multiple notifications occur within @min_interval seconds
+ * of each other, the later ones will be suppressed, to avoid
+ * excessive traffic between the client and the server as the client
+ * tries to sync. Second, if there is a firewall between the client
+ * and the server, it is possible that all notifications will be lost.
+ **/
+void
+e2k_context_subscribe (E2kContext *ctx, const gchar *uri,
+		       E2kContextChangeType type, gint min_interval,
+		       E2kContextChangeCallback callback,
+		       gpointer user_data)
+{
+	E2kSubscription *sub;
+	GList *sub_list;
+	gpointer key, value;
+
+	g_return_if_fail (E2K_IS_CONTEXT (ctx));
+
+	sub = g_new0 (E2kSubscription, 1);
+	sub->ctx = ctx;
+	sub->uri = g_strdup (uri);
+	sub->type = type;
+	sub->lifetime = E2K_SUBSCRIPTION_INITIAL_LIFETIME / 2;
+	sub->min_interval = min_interval;
+	sub->callback = callback;
+	sub->user_data = user_data;
+
+	if (g_hash_table_lookup_extended (ctx->priv->subscriptions_by_uri,
+					  uri, &key, &value)) {
+		sub_list = value;
+		sub_list = g_list_prepend (sub_list, sub);
+		g_hash_table_insert (ctx->priv->subscriptions_by_uri,
+				     key, sub_list);
+	} else {
+		g_hash_table_insert (ctx->priv->subscriptions_by_uri,
+				     sub->uri, g_list_prepend (NULL, sub));
+	}
+
+	renew_subscription (sub);
+}
+
+static void
+free_subscription (E2kSubscription *sub)
+{
+	SoupSession *session = sub->ctx->priv->session;
+
+	if (sub->renew_timeout)
+		g_source_remove (sub->renew_timeout);
+	if (sub->renew_msg) {
+		soup_session_cancel_message (session, sub->renew_msg,
+					     SOUP_STATUS_CANCELLED);
+	}
+	if (sub->poll_timeout)
+		g_source_remove (sub->poll_timeout);
+	if (sub->notification_timeout)
+		g_source_remove (sub->notification_timeout);
+	if (sub->poll_msg) {
+		soup_session_cancel_message (session, sub->poll_msg,
+					     SOUP_STATUS_CANCELLED);
+	}
+	g_free (sub->uri);
+	g_free (sub->id);
+	g_free (sub);
+}
+
+static void
+unsubscribed (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+	;
+}
+
+static void
+unsubscribe_internal (E2kContext *ctx, const gchar *puri, GList *sub_list, gboolean destrying)
+{
+	GList *l;
+	E2kSubscription *sub;
+	SoupMessage *msg;
+	GString *subscription_ids = NULL;
+	gchar *uri = g_strdup (puri);
+	/* puri comes from sub->uri, but we are using it after sub is freed, thus making copy here */
+
+	for (l = sub_list; l; l = l->next) {
+		sub = l->data;
+		if (sub->id) {
+			/* do not send messages when destroying, because they are server on idle,
+			   when the context itself already gone */
+			if (!destrying) {
+				if (!subscription_ids)
+					subscription_ids = g_string_new (sub->id);
+				else {
+					g_string_append_printf (subscription_ids,
+							",%s", sub->id);
+				}
+			}
+			g_hash_table_remove (ctx->priv->subscriptions_by_id, sub->id);
+		}
+		free_subscription (sub);
+	}
+
+	if (subscription_ids) {
+		msg = e2k_soup_message_new (ctx, uri, "UNSUBSCRIBE");
+		if (msg) {
+			soup_message_headers_append (msg->request_headers,
+						     "Subscription-id",
+						     subscription_ids->str);
+			e2k_context_queue_message (ctx, msg, unsubscribed, NULL);
+		}
+		g_string_free (subscription_ids, TRUE);
+	}
+
+	g_free (uri);
+}
+
+/**
+ * e2k_context_unsubscribe:
+ * @ctx: the context
+ * @uri: the URI to unsubscribe from
+ *
+ * Unsubscribes to all notifications on @ctx for @uri.
+ **/
+void
+e2k_context_unsubscribe (E2kContext *ctx, const gchar *uri)
+{
+	GList *sub_list;
+
+	g_return_if_fail (E2K_IS_CONTEXT (ctx));
+
+	sub_list = g_hash_table_lookup (ctx->priv->subscriptions_by_uri, uri);
+	g_hash_table_remove (ctx->priv->subscriptions_by_uri, uri);
+	unsubscribe_internal (ctx, uri, sub_list, FALSE);
+	g_list_free (sub_list);
+}
diff --git a/server/lib/e2k-context.h b/server/lib/e2k-context.h
new file mode 100644
index 0000000..e722d58
--- /dev/null
+++ b/server/lib/e2k-context.h
@@ -0,0 +1,213 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_CONTEXT_H__
+#define __E2K_CONTEXT_H__
+
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-session.h>
+#include <sys/time.h>
+
+#include <glib-object.h>
+
+#include "e2k-types.h"
+#include "e2k-operation.h"
+#include "e2k-http-utils.h"
+#include "e2k-result.h"
+
+G_BEGIN_DECLS
+
+#define E2K_TYPE_CONTEXT            (e2k_context_get_type ())
+#define E2K_CONTEXT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E2K_TYPE_CONTEXT, E2kContext))
+#define E2K_CONTEXT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E2K_TYPE_CONTEXT, E2kContextClass))
+#define E2K_IS_CONTEXT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E2K_TYPE_CONTEXT))
+#define E2K_IS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E2K_TYPE_CONTEXT))
+
+struct _E2kContext {
+	GObject parent;
+
+	E2kContextPrivate *priv;
+};
+
+struct _E2kContextClass {
+	GObjectClass parent_class;
+
+	/* signals */
+	void (*redirect) (E2kContext *ctx, E2kHTTPStatus status,
+			  const gchar *old_uri, const gchar *new_uri);
+};
+
+GType          e2k_context_get_type         (void);
+
+E2kContext    *e2k_context_new              (const gchar  *uri);
+void           e2k_context_set_auth         (E2kContext  *ctx,
+					     const gchar  *username,
+					     const gchar  *domain,
+					     const gchar  *authmech,
+					     const gchar  *password);
+gboolean       e2k_context_fba              (E2kContext  *ctx,
+					     SoupMessage *failed_msg);
+
+time_t        e2k_context_get_last_timestamp    (E2kContext *ctx);
+
+typedef gboolean (*E2kContextTestCallback)     (E2kContext *ctx,
+						const gchar *test_name,
+						gpointer user_data);
+
+E2kHTTPStatus  e2k_context_get               (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      gchar **content_type,
+					      SoupBuffer **response);
+E2kHTTPStatus  e2k_context_get_owa           (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      gboolean claim_ie,
+					      SoupBuffer **response);
+
+E2kHTTPStatus  e2k_context_put               (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      const gchar *content_type,
+					      const gchar *body, gint length,
+					      gchar **repl_uid);
+E2kHTTPStatus  e2k_context_put_new           (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *folder_uri,
+					      const gchar *object_name,
+					      E2kContextTestCallback test_callback,
+					      gpointer user_data,
+					      const gchar *content_type,
+					      const gchar *body, gint length,
+					      gchar **location,
+					      gchar **repl_uid);
+E2kHTTPStatus  e2k_context_post              (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      const gchar *content_type,
+					      const gchar *body, gint length,
+					      gchar **location,
+					      gchar **repl_uid);
+
+E2kHTTPStatus  e2k_context_proppatch         (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      E2kProperties *props,
+					      gboolean create,
+					      gchar **repl_uid);
+E2kHTTPStatus  e2k_context_proppatch_new     (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *folder_uri,
+					      const gchar *object_name,
+					      E2kContextTestCallback test_callback,
+					      gpointer user_data,
+					      E2kProperties *props,
+					      gchar **location,
+					      gchar **repl_uid);
+E2kResultIter *e2k_context_bproppatch_start  (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      const gchar **hrefs,
+					      gint nhrefs,
+					      E2kProperties *props,
+					      gboolean create);
+
+E2kHTTPStatus  e2k_context_propfind          (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      const gchar **props,
+					      gint nprops,
+					      E2kResult **results,
+					      gint *nresults);
+E2kResultIter *e2k_context_bpropfind_start   (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      const gchar **hrefs,
+					      gint nhrefs,
+					      const gchar **props,
+					      gint nprops);
+
+E2kResultIter *e2k_context_search_start      (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      const gchar **props,
+					      gint nprops,
+					      E2kRestriction *rn,
+					      const gchar *orderby,
+					      gboolean ascending);
+
+E2kHTTPStatus  e2k_context_delete            (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri);
+
+E2kResultIter *e2k_context_bdelete_start     (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      const gchar **hrefs,
+					      gint nhrefs);
+
+E2kHTTPStatus  e2k_context_mkcol             (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *uri,
+					      E2kProperties *props,
+					      gchar **permanent_url);
+
+E2kResultIter *e2k_context_transfer_start    (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *source_folder,
+					      const gchar *dest_folder,
+					      GPtrArray *source_hrefs,
+					      gboolean delete_originals);
+E2kHTTPStatus  e2k_context_transfer_dir      (E2kContext *ctx,
+					      E2kOperation *op,
+					      const gchar *source_href,
+					      const gchar *dest_href,
+					      gboolean delete_original,
+					      gchar **permanent_url);
+
+/* Subscriptions */
+typedef enum {
+	E2K_CONTEXT_OBJECT_CHANGED,
+	E2K_CONTEXT_OBJECT_ADDED,
+	E2K_CONTEXT_OBJECT_REMOVED,
+	E2K_CONTEXT_OBJECT_MOVED
+} E2kContextChangeType;
+
+typedef void (*E2kContextChangeCallback)     (E2kContext *ctx,
+					      const gchar *uri,
+					      E2kContextChangeType type,
+					      gpointer user_data);
+
+void          e2k_context_subscribe          (E2kContext *ctx,
+					      const gchar *uri,
+					      E2kContextChangeType type,
+					      gint min_interval,
+					      E2kContextChangeCallback callback,
+					      gpointer user_data);
+void          e2k_context_unsubscribe        (E2kContext *ctx,
+					      const gchar *uri);
+
+/*
+ * Utility functions
+ */
+SoupMessage   *e2k_soup_message_new      (E2kContext *ctx,
+					  const gchar *uri,
+					  const gchar *method);
+SoupMessage   *e2k_soup_message_new_full (E2kContext *ctx,
+					  const gchar *uri,
+					  const gchar *method,
+					  const gchar *content_type,
+					  SoupMemoryUse use,
+					  const gchar *body,
+					  gsize length);
+void           e2k_context_queue_message (E2kContext *ctx,
+					  SoupMessage *msg,
+					  SoupSessionCallback callback,
+					  gpointer user_data);
+E2kHTTPStatus  e2k_context_send_message  (E2kContext *ctx,
+					  E2kOperation *op,
+					  SoupMessage *msg);
+
+G_END_DECLS
+
+#endif /* __E2K_CONTEXT_H__ */
diff --git a/server/lib/e2k-freebusy.c b/server/lib/e2k-freebusy.c
new file mode 100644
index 0000000..6ed069d
--- /dev/null
+++ b/server/lib/e2k-freebusy.c
@@ -0,0 +1,555 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* e2k-freebusy.c: routines for manipulating Exchange free/busy data */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e2k-freebusy.h"
+#include "e2k-propnames.h"
+#include "e2k-restriction.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <libedataserver/e-time-utils.h>
+
+/**
+ * e2k_freebusy_destroy:
+ * @fb: the #E2kFreebusy
+ *
+ * Frees @fb and all associated data.
+ **/
+void
+e2k_freebusy_destroy (E2kFreebusy *fb)
+{
+	gint i;
+
+	g_object_unref (fb->ctx);
+	for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
+		g_array_free (fb->events[i], TRUE);
+	g_free (fb->uri);
+	g_free (fb->dn);
+	g_free (fb);
+}
+
+static gchar *
+fb_uri_for_dn (const gchar *public_uri, const gchar *dn)
+{
+	gchar *uri, *div, *org;
+	GString *str;
+
+	for (div = strchr (dn, '/'); div; div = strchr (div + 1, '/')) {
+		if (!g_ascii_strncasecmp (div, "/cn=", 4))
+			break;
+	}
+	g_return_val_if_fail (div, NULL);
+
+	org = g_strndup (dn, div - dn);
+
+	str = g_string_new (public_uri);
+	g_string_append (str, "/NON_IPM_SUBTREE/SCHEDULE%2B%20FREE%20BUSY/EX:");
+	e2k_uri_append_encoded (str, org, TRUE, NULL);
+	g_string_append (str, "/USER-");
+	e2k_uri_append_encoded (str, div, TRUE, NULL);
+	g_string_append (str, ".EML");
+
+	uri = str->str;
+	g_string_free (str, FALSE);
+	g_free (org);
+
+	return uri;
+}
+
+static void
+merge_events (GArray *events)
+{
+	E2kFreebusyEvent evt, evt2;
+	gint i;
+
+	if (events->len < 2)
+		return;
+
+	evt = g_array_index (events, E2kFreebusyEvent, 0);
+	for (i = 1; i < events->len; i++) {
+		evt2 = g_array_index (events, E2kFreebusyEvent, i);
+		if (evt.end >= evt2.start) {
+			if (evt2.end > evt.end)
+				evt.end = evt2.end;
+			g_array_remove_index (events, i--);
+		} else
+			evt = evt2;
+	}
+}
+
+static void
+add_data_for_status (E2kFreebusy *fb, GPtrArray *monthyears, GPtrArray *fbdatas, GArray *events)
+{
+	E2kFreebusyEvent evt;
+	gint i, monthyear;
+	GByteArray *fbdata;
+	guchar *p;
+	struct tm tm;
+
+	if (!monthyears || !fbdatas)
+		return;
+
+	memset (&tm, 0, sizeof (tm));
+	for (i = 0; i < monthyears->len && i < fbdatas->len; i++) {
+		monthyear = atoi (monthyears->pdata[i]);
+		fbdata = fbdatas->pdata[i];
+
+		tm.tm_year = (monthyear >> 4) - 1900;
+		tm.tm_mon = (monthyear & 0xF) - 1;
+
+		for (p = fbdata->data; p + 3 < fbdata->data + fbdata->len; p += 4) {
+			tm.tm_mday = 1;
+			tm.tm_hour = 0;
+			tm.tm_min = p[0] + p[1] * 256;
+			evt.start = e_mktime_utc (&tm);
+
+			tm.tm_mday = 1;
+			tm.tm_hour = 0;
+			tm.tm_min = p[2] + p[3] * 256;
+			evt.end = e_mktime_utc (&tm);
+
+			g_array_append_val (events, evt);
+		}
+	}
+	merge_events (events);
+}
+
+static const gchar *public_freebusy_props[] = {
+	PR_FREEBUSY_START_RANGE,
+	PR_FREEBUSY_END_RANGE,
+	PR_FREEBUSY_ALL_MONTHS,
+	PR_FREEBUSY_ALL_EVENTS,
+	PR_FREEBUSY_TENTATIVE_MONTHS,
+	PR_FREEBUSY_TENTATIVE_EVENTS,
+	PR_FREEBUSY_BUSY_MONTHS,
+	PR_FREEBUSY_BUSY_EVENTS,
+	PR_FREEBUSY_OOF_MONTHS,
+	PR_FREEBUSY_OOF_EVENTS
+};
+static const gint n_public_freebusy_props = sizeof (public_freebusy_props) / sizeof (public_freebusy_props[0]);
+
+/**
+ * e2k_freebusy_new:
+ * @ctx: an #E2kContext
+ * @public_uri: the URI of the MAPI public folder tree
+ * @dn: the legacy Exchange DN of a user
+ *
+ * Creates a new #E2kFreebusy, filled in with information from the
+ * indicated user's published free/busy information. This uses the
+ * public free/busy folder; the caller does not need permission to
+ * access the @dn's Calendar.
+ *
+ * Note that currently, this will fail and return %NULL if the user
+ * does not already have free/busy information stored on the server.
+ *
+ * Return value: the freebusy information
+ **/
+E2kFreebusy *
+e2k_freebusy_new (E2kContext *ctx, const gchar *public_uri, const gchar *dn)
+{
+	E2kFreebusy *fb;
+	gchar *uri, *time;
+	GPtrArray *monthyears, *fbdatas;
+	E2kHTTPStatus status;
+	E2kResult *results;
+	gint nresults = 0, i;
+
+	uri = fb_uri_for_dn (public_uri, dn);
+	g_return_val_if_fail (uri, NULL);
+
+	status = e2k_context_propfind (ctx, NULL, uri,
+				       public_freebusy_props,
+				       n_public_freebusy_props,
+				       &results, &nresults);
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status) || nresults == 0) {
+		/* FIXME: create it */
+		g_free (uri);
+		return NULL;
+	}
+
+	fb = g_new0 (E2kFreebusy, 1);
+	fb->uri = uri;
+	fb->dn = g_strdup (dn);
+	fb->ctx = ctx;
+	g_object_ref (ctx);
+
+	for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
+		fb->events[i] = g_array_new (FALSE, FALSE, sizeof (E2kFreebusyEvent));
+
+	time = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_START_RANGE);
+	fb->start = time ? e2k_systime_to_time_t (strtol (time, NULL, 10)) : 0;
+	time = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_END_RANGE);
+	fb->end = time ? e2k_systime_to_time_t (strtol (time, NULL, 10)) : 0;
+
+	monthyears = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_ALL_MONTHS);
+	fbdatas = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_ALL_EVENTS);
+	add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_ALL]);
+
+	monthyears = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_TENTATIVE_MONTHS);
+	fbdatas = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_TENTATIVE_EVENTS);
+	add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_TENTATIVE]);
+
+	monthyears = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_BUSY_MONTHS);
+	fbdatas = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_BUSY_EVENTS);
+	add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_BUSY]);
+
+	monthyears = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_OOF_MONTHS);
+	fbdatas = e2k_properties_get_prop (
+		results[0].props, PR_FREEBUSY_OOF_EVENTS);
+	add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_OOF]);
+
+	e2k_results_free (results, nresults);
+	return fb;
+}
+
+/**
+ * e2k_freebusy_reset:
+ * @fb: an #E2kFreebusy
+ * @nmonths: the number of months of info @fb will store
+ *
+ * Clears all existing data in @fb and resets the start and end times
+ * to a span of @nmonths around the current date.
+ **/
+void
+e2k_freebusy_reset (E2kFreebusy *fb, gint nmonths)
+{
+	time_t now;
+	struct tm tm;
+	gint i;
+
+	/* Remove all existing events */
+	for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
+		g_array_set_size (fb->events[i], 0);
+
+	/* Set the start and end times appropriately: from the beginning
+	 * of the current month until nmonths later.
+	 * FIXME: Use default timezone, not local time.
+	 */
+	now = time (NULL);
+	tm = *gmtime (&now);
+	tm.tm_mday = 1;
+	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+
+	tm.tm_isdst = -1;
+	fb->start = mktime (&tm);
+
+	tm.tm_mon += nmonths;
+	tm.tm_isdst = -1;
+	fb->end = mktime (&tm);
+}
+
+/**
+ * e2k_freebusy_add_interval:
+ * @fb: an #E2kFreebusy
+ * @busystatus: the busy status of the interval
+ * @start: the start of the interval
+ * @end: the end of the interval
+ *
+ * This adds an interval of type @busystatus to @fb.
+ **/
+void
+e2k_freebusy_add_interval (E2kFreebusy *fb, E2kBusyStatus busystatus,
+			   time_t start, time_t end)
+{
+	E2kFreebusyEvent evt, *events;
+	gint i;
+
+	if (busystatus == E2K_BUSYSTATUS_FREE)
+		return;
+
+	/* Clip to the fb's range */
+	if (start < fb->start)
+		start = fb->start;
+	if (end > fb->end)
+		end = fb->end;
+	if (end <= start)
+		return;
+
+	events = (E2kFreebusyEvent *)(fb->events[busystatus]->data);
+
+	for (i = 0; i < fb->events[busystatus]->len; i++) {
+		if (events[i].end >= start)
+			break;
+	}
+
+	evt.start = start;
+	evt.end = end;
+
+	if (i == fb->events[busystatus]->len)
+		g_array_append_val (fb->events[busystatus], evt);
+	else {
+		/* events[i] is the first event that is not completely
+		 * before evt, meaning it is either completely after it,
+		 * or they overlap/abut.
+		 */
+		if (events[i].start > end) {
+			/* No overlap. Insert evt before events[i]. */
+			g_array_insert_val (fb->events[busystatus], i, evt);
+		} else {
+			/* They overlap or abut. Merge them. */
+			events[i].start = MIN (events[i].start, start);
+			events[i].end   = MAX (events[i].end, end);
+		}
+	}
+}
+
+/**
+ * e2k_freebusy_clear_interval:
+ * @fb: an #E2kFreebusy
+ * @start: the start of the interval
+ * @end: the end of the interval
+ *
+ * This removes any events between @start and @end in @fb.
+ **/
+void
+e2k_freebusy_clear_interval (E2kFreebusy *fb, time_t start, time_t end)
+{
+	E2kFreebusyEvent *evt;
+	gint busystatus, i;
+
+	for (busystatus = 0; busystatus < E2K_BUSYSTATUS_MAX; busystatus++) {
+		for (i = 0; i < fb->events[busystatus]->len; i++) {
+			evt = &g_array_index (fb->events[busystatus], E2kFreebusyEvent, i);
+			if (evt->end < start || evt->start > end)
+				continue;
+
+			/* evt overlaps the interval. Truncate or
+			 * remove it.
+			 */
+
+			if (evt->start > start /* && evt->start <= end */)
+				evt->start = end;
+			if (evt->end < end /* && evt->end >= start */)
+				evt->end = start;
+
+			if (evt->start >= evt->end)
+				g_array_remove_index (fb->events[busystatus], i--);
+		}
+	}
+}
+
+static const gchar *freebusy_props[] = {
+	E2K_PR_CALENDAR_DTSTART,
+	E2K_PR_CALENDAR_DTEND,
+	E2K_PR_CALENDAR_BUSY_STATUS
+};
+static const gint n_freebusy_props = sizeof (freebusy_props) / sizeof (freebusy_props[0]);
+
+/**
+ * e2k_freebusy_add_from_calendar_uri:
+ * @fb: an #E2kFreebusy
+ * @uri: the URI of a calendar folder
+ * @start_tt: start of the range to add
+ * @end_tt: end of the range to add
+ *
+ * This queries the server for events between @start_tt and @end_tt in
+ * the calendar at @uri (which the caller must have permission to
+ * read) and adds them @fb. Any previously-existing events during that
+ * range are removed.
+ *
+ * Return value: an HTTP status code.
+ **/
+E2kHTTPStatus
+e2k_freebusy_add_from_calendar_uri (E2kFreebusy *fb, const gchar *uri,
+				    time_t start_tt, time_t end_tt)
+{
+	gchar *start, *end, *busystatus;
+	E2kBusyStatus busy;
+	E2kRestriction *rn;
+	E2kResultIter *iter;
+	E2kResult *result;
+
+	e2k_freebusy_clear_interval (fb, start_tt, end_tt);
+
+	start = e2k_make_timestamp (start_tt);
+	end = e2k_make_timestamp (end_tt);
+
+	rn = e2k_restriction_andv (
+		e2k_restriction_prop_string (E2K_PR_DAV_CONTENT_CLASS,
+					     E2K_RELOP_EQ,
+					     "urn:content-classes:appointment"),
+		e2k_restriction_prop_date (E2K_PR_CALENDAR_DTEND,
+					   E2K_RELOP_GT, start),
+		e2k_restriction_prop_date (E2K_PR_CALENDAR_DTSTART,
+					   E2K_RELOP_LT, end),
+		e2k_restriction_prop_string (E2K_PR_CALENDAR_BUSY_STATUS,
+					     E2K_RELOP_NE, "FREE"),
+		NULL);
+
+	iter = e2k_context_search_start (fb->ctx, NULL, uri,
+					 freebusy_props, n_freebusy_props,
+					 rn, NULL, TRUE);
+	e2k_restriction_unref (rn);
+	g_free (start);
+	g_free (end);
+
+	while ((result = e2k_result_iter_next (iter))) {
+		start = e2k_properties_get_prop (result->props,
+						 E2K_PR_CALENDAR_DTSTART);
+		end = e2k_properties_get_prop (result->props,
+					       E2K_PR_CALENDAR_DTEND);
+		busystatus = e2k_properties_get_prop (result->props,
+						      E2K_PR_CALENDAR_BUSY_STATUS);
+		if (!start || !end || !busystatus)
+			continue;
+
+		if (!strcmp (busystatus, "TENTATIVE"))
+			busy = E2K_BUSYSTATUS_TENTATIVE;
+		else if (!strcmp (busystatus, "OUTOFOFFICE"))
+			busy = E2K_BUSYSTATUS_OOF;
+		else
+			busy = E2K_BUSYSTATUS_BUSY;
+
+		e2k_freebusy_add_interval (fb, busy,
+					   e2k_parse_timestamp (start),
+					   e2k_parse_timestamp (end));
+
+	}
+
+	return e2k_result_iter_free (iter);
+}
+
+static void
+add_events (GArray *events_array, E2kProperties *props,
+	    const gchar *month_list_prop, const gchar *data_list_prop)
+{
+	E2kFreebusyEvent *events = (E2kFreebusyEvent *)events_array->data;
+	gint i, evt_start, evt_end, monthyear;
+	struct tm start_tm, end_tm;
+	time_t start, end;
+	GPtrArray *monthyears, *datas;
+	GByteArray *data;
+	gchar startend[4];
+
+	if (!events_array->len) {
+		e2k_properties_remove (props, month_list_prop);
+		e2k_properties_remove (props, data_list_prop);
+		return;
+	}
+
+	monthyears = g_ptr_array_new ();
+	start_tm = *gmtime (&events[0].start);
+	end_tm = *gmtime (&events[events_array->len - 1].end);
+	while (start_tm.tm_year <= end_tm.tm_year ||
+	       start_tm.tm_mon <= end_tm.tm_mon) {
+		monthyear = ((start_tm.tm_year + 1900) * 16) +
+			(start_tm.tm_mon + 1);
+		g_ptr_array_add (monthyears, g_strdup_printf ("%d", monthyear));
+
+		start_tm.tm_mon++;
+		if (start_tm.tm_mon == 12) {
+			start_tm.tm_year++;
+			start_tm.tm_mon = 0;
+		}
+	}
+	e2k_properties_set_int_array (props, month_list_prop, monthyears);
+
+	datas = g_ptr_array_new ();
+	start = events[0].start;
+	i = 0;
+	while (i < events_array->len) {
+		start_tm = *gmtime (&start);
+		start_tm.tm_mon++;
+		end = e_mktime_utc (&start_tm);
+
+		data = g_byte_array_new ();
+		while (i << events_array->len &&
+		       events[i].end > start && events[i].start < end) {
+			if (events[i].start < start)
+				evt_start = 0;
+			else
+				evt_start = (events[i].start - start) / 60;
+			if (events[i].end > end)
+				evt_end = (end - start) / 60;
+			else
+				evt_end = (events[i].end - start) / 60;
+
+			startend[0] = evt_start & 0xFF;
+			startend[1] = evt_start >> 8;
+			startend[2] = evt_end & 0xFF;
+			startend[3] = evt_end >> 8;
+			g_byte_array_append (data, (guint8 *) startend, 4);
+			i++;
+		}
+
+		g_ptr_array_add (datas, data);
+		start = end;
+	}
+	e2k_properties_set_binary_array (props, data_list_prop, datas);
+}
+
+/**
+ * e2k_freebusy_save:
+ * @fb: an #E2kFreebusy
+ *
+ * Saves the data in @fb back to the server.
+ *
+ * Return value: a libsoup or HTTP status code
+ **/
+E2kHTTPStatus
+e2k_freebusy_save (E2kFreebusy *fb)
+{
+	E2kProperties *props;
+	gchar *timestamp;
+	E2kHTTPStatus status;
+
+	props = e2k_properties_new ();
+	e2k_properties_set_string (props, E2K_PR_EXCHANGE_MESSAGE_CLASS,
+				   g_strdup ("IPM.Post"));
+	e2k_properties_set_int (props, PR_FREEBUSY_START_RANGE, fb->start);
+	e2k_properties_set_int (props, PR_FREEBUSY_END_RANGE, fb->end);
+	e2k_properties_set_string (props, PR_FREEBUSY_EMAIL_ADDRESS,
+				   g_strdup (fb->dn));
+
+	add_events (fb->events[E2K_BUSYSTATUS_ALL], props,
+		    PR_FREEBUSY_ALL_MONTHS, PR_FREEBUSY_ALL_EVENTS);
+	add_events (fb->events[E2K_BUSYSTATUS_TENTATIVE], props,
+		    PR_FREEBUSY_TENTATIVE_MONTHS, PR_FREEBUSY_TENTATIVE_EVENTS);
+	add_events (fb->events[E2K_BUSYSTATUS_BUSY], props,
+		    PR_FREEBUSY_BUSY_MONTHS, PR_FREEBUSY_BUSY_EVENTS);
+	add_events (fb->events[E2K_BUSYSTATUS_OOF], props,
+		    PR_FREEBUSY_OOF_MONTHS, PR_FREEBUSY_OOF_EVENTS);
+
+	timestamp = e2k_make_timestamp (e2k_context_get_last_timestamp (fb->ctx));
+	e2k_properties_set_date (props, PR_FREEBUSY_LAST_MODIFIED, timestamp);
+
+	status = e2k_context_proppatch (fb->ctx, NULL, fb->uri, props,
+					TRUE, NULL);
+	e2k_properties_free (props);
+
+	return status;
+}
diff --git a/server/lib/e2k-freebusy.h b/server/lib/e2k-freebusy.h
new file mode 100644
index 0000000..67e5341
--- /dev/null
+++ b/server/lib/e2k-freebusy.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2002-2004 Novell, Inc. */
+
+#ifndef __E2K_FREEBUSY_H__
+#define __E2K_FREEBUSY_H__
+
+#include "e2k-context.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E2K_BUSYSTATUS_FREE = 0,
+	E2K_BUSYSTATUS_TENTATIVE = 1,
+	E2K_BUSYSTATUS_BUSY = 2,
+	E2K_BUSYSTATUS_OOF = 3,
+
+	E2K_BUSYSTATUS_MAX,
+
+	/* Alias for internal use */
+	E2K_BUSYSTATUS_ALL = E2K_BUSYSTATUS_FREE
+} E2kBusyStatus;
+
+typedef struct {
+	/*< private >*/
+	time_t start, end;
+} E2kFreebusyEvent;
+
+typedef struct {
+	/*< private >*/
+	E2kContext *ctx;
+	gchar *dn, *uri;
+
+	time_t start, end;
+
+	GArray *events[E2K_BUSYSTATUS_MAX];
+} E2kFreebusy;
+
+E2kFreebusy   *e2k_freebusy_new                   (E2kContext      *ctx,
+						   const gchar      *public_uri,
+						   const gchar      *dn);
+
+void           e2k_freebusy_reset                 (E2kFreebusy     *fb,
+						   gint              nmonths);
+
+void           e2k_freebusy_add_interval          (E2kFreebusy     *fb,
+						   E2kBusyStatus    busystatus,
+						   time_t           start,
+						   time_t           end);
+void           e2k_freebusy_clear_interval        (E2kFreebusy     *fb,
+						   time_t           start,
+						   time_t           end);
+
+E2kHTTPStatus  e2k_freebusy_add_from_calendar_uri (E2kFreebusy     *fb,
+						   const gchar      *uri,
+						   time_t           start_tt,
+						   time_t           end_tt);
+
+E2kHTTPStatus  e2k_freebusy_save                  (E2kFreebusy     *fb);
+
+void           e2k_freebusy_destroy               (E2kFreebusy     *fb);
+
+G_END_DECLS
+
+#endif /* __E2K_FREEBUSY_H__ */
diff --git a/server/lib/e2k-global-catalog-ldap.h b/server/lib/e2k-global-catalog-ldap.h
new file mode 100644
index 0000000..39d5ac4
--- /dev/null
+++ b/server/lib/e2k-global-catalog-ldap.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_GLOBAL_CATALOG_LDAP_H__
+#define __E2K_GLOBAL_CATALOG_LDAP_H__
+
+#include <glib.h>
+#ifndef G_OS_WIN32
+#include <ldap.h>
+#else
+#define interface windows_interface
+#include <windows.h>
+#undef interface
+#include <winldap.h>
+#define LDAP_ROOT_DSE		""
+#define LDAP_RANGE(n,x,y)	(((x) <= (n)) && ((n) <= (y)))
+#define LDAP_NAME_ERROR(n)	LDAP_RANGE((n),0x20,0x24) /* 32-34,36 */
+#define ldap_msgtype(lm)	(lm)->lm_msgtype
+#define ldap_msgid(lm)		(lm)->lm_msgid
+#endif
+#include "e2k-global-catalog.h"
+
+G_BEGIN_DECLS
+
+LDAP             *e2k_global_catalog_get_ldap        (E2kGlobalCatalog *gc,
+						      E2kOperation     *op,
+						      gint              *ldap_error);
+
+G_END_DECLS
+
+#endif /* __E2K_GLOBAL_CATALOG_LDAP_H__ */
diff --git a/server/lib/e2k-global-catalog.c b/server/lib/e2k-global-catalog.c
new file mode 100644
index 0000000..b5b04c9
--- /dev/null
+++ b/server/lib/e2k-global-catalog.c
@@ -0,0 +1,1247 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-global-catalog-ldap.h"
+#include "e2k-sid.h"
+#include "e2k-utils.h"
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#ifdef HAVE_LDAP_NTLM_BIND
+#include "xntlm.h"
+#endif
+
+#ifdef E2K_DEBUG
+static gboolean e2k_gc_debug = FALSE;
+#define E2K_GC_DEBUG_MSG(x) if (e2k_gc_debug) printf x
+#else
+#define E2K_GC_DEBUG_MSG(x)
+#endif
+
+struct _E2kGlobalCatalogPrivate {
+	GMutex *ldap_lock;
+	LDAP *ldap;
+
+	GPtrArray *entries;
+	GHashTable *entry_cache, *server_cache;
+
+	gchar *server, *user, *nt_domain, *password;
+	E2kAutoconfigGalAuthPref auth;
+};
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+static void finalize (GObject *);
+static gint get_gc_connection (E2kGlobalCatalog *gc, E2kOperation *op);
+
+static void
+class_init (GObjectClass *object_class)
+{
+#ifdef E2K_DEBUG
+	gchar *e2k_debug = getenv ("E2K_DEBUG");
+
+	if (e2k_debug && atoi (e2k_debug) > 3)
+		e2k_gc_debug = TRUE;
+#endif
+
+	/* For some reason, sasl_client_init (called by ldap_init
+	 * below) takes a *really* long time to scan the sasl modules
+	 * when running under gdb. We're not using sasl anyway, so...
+	 */
+	g_setenv ("SASL_PATH", "", TRUE);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* virtual method override */
+	object_class->finalize = finalize;
+}
+
+static void
+init (GObject *object)
+{
+	E2kGlobalCatalog *gc = E2K_GLOBAL_CATALOG (object);
+
+	gc->priv = g_new0 (E2kGlobalCatalogPrivate, 1);
+	gc->priv->ldap_lock = g_mutex_new ();
+	gc->priv->entries = g_ptr_array_new ();
+	gc->priv->entry_cache = g_hash_table_new (e2k_ascii_strcase_hash,
+						  e2k_ascii_strcase_equal);
+	gc->priv->server_cache = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+free_entry (E2kGlobalCatalogEntry *entry)
+{
+	gint i;
+
+	g_free (entry->dn);
+	g_free (entry->display_name);
+
+	if (entry->sid)
+		g_object_unref (entry->sid);
+
+	g_free (entry->email);
+	g_free (entry->mailbox);
+
+	if (entry->delegates) {
+		for (i = 0; i < entry->delegates->len; i++)
+			g_free (entry->delegates->pdata[i]);
+		g_ptr_array_free (entry->delegates, TRUE);
+	}
+	if (entry->delegators) {
+		for (i = 0; i < entry->delegators->len; i++)
+			g_free (entry->delegators->pdata[i]);
+		g_ptr_array_free (entry->delegators, TRUE);
+	}
+
+	g_free (entry);
+}
+
+static void
+free_server (gpointer key, gpointer value, gpointer data)
+{
+	g_free (key);
+	g_free (value);
+}
+
+static void
+finalize (GObject *object)
+{
+	E2kGlobalCatalog *gc = E2K_GLOBAL_CATALOG (object);
+	gint i;
+
+	if (gc->priv) {
+		if (gc->priv->ldap)
+			ldap_unbind (gc->priv->ldap);
+
+		for (i = 0; i < gc->priv->entries->len; i++)
+			free_entry (gc->priv->entries->pdata[i]);
+		g_ptr_array_free (gc->priv->entries, TRUE);
+
+		g_hash_table_foreach (gc->priv->server_cache, free_server, NULL);
+		g_hash_table_destroy (gc->priv->server_cache);
+
+		g_free (gc->priv->server);
+		g_free (gc->priv->user);
+		g_free (gc->priv->nt_domain);
+		if (gc->priv->password) {
+			memset (gc->priv->password, 0, strlen (gc->priv->password));
+			g_free (gc->priv->password);
+		}
+
+		g_mutex_free (gc->priv->ldap_lock);
+
+		g_free (gc->priv);
+		gc->priv = NULL;
+	}
+
+	g_free (gc->domain);
+	gc->domain = NULL;
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (e2k_global_catalog, E2kGlobalCatalog, class_init, init, PARENT_TYPE)
+
+static gint
+gc_ldap_result (LDAP *ldap, E2kOperation *op,
+		gint msgid, LDAPMessage **msg)
+{
+	struct timeval tv;
+	gint status, ldap_error;
+
+	tv.tv_sec = 1;
+	tv.tv_usec = 0;
+	*msg = NULL;
+	do {
+		status = ldap_result (ldap, msgid, TRUE, &tv, msg);
+		if (status == -1) {
+			ldap_get_option (ldap, LDAP_OPT_ERROR_NUMBER,
+					 &ldap_error);
+			return ldap_error;
+		}
+	} while (status == 0 && !e2k_operation_is_cancelled (op));
+
+	if (e2k_operation_is_cancelled (op)) {
+		ldap_abandon (ldap, msgid);
+		return LDAP_USER_CANCELLED;
+	} else
+		return LDAP_SUCCESS;
+}
+
+static gint
+gc_search (E2kGlobalCatalog *gc, E2kOperation *op,
+	   const gchar *base, gint scope, const gchar *filter,
+	   const gchar **attrs, LDAPMessage **msg)
+{
+	gint ldap_error, msgid, try;
+
+	for (try = 0; try < 2; try++) {
+		ldap_error = get_gc_connection (gc, op);
+		if (ldap_error != LDAP_SUCCESS)
+			return ldap_error;
+		ldap_error = ldap_search_ext (gc->priv->ldap, base, scope,
+					      filter, (gchar **)attrs,
+					      FALSE, NULL, NULL, NULL, 0,
+					      &msgid);
+		if (ldap_error == LDAP_SERVER_DOWN)
+			continue;
+		else if (ldap_error != LDAP_SUCCESS)
+			return ldap_error;
+
+		ldap_error = gc_ldap_result (gc->priv->ldap, op, msgid, msg);
+		if (ldap_error == LDAP_SERVER_DOWN)
+			continue;
+		else if (ldap_error != LDAP_SUCCESS)
+			return ldap_error;
+
+		return LDAP_SUCCESS;
+	}
+
+	return LDAP_SERVER_DOWN;
+}
+
+#ifdef HAVE_LDAP_NTLM_BIND
+static gint
+ntlm_bind (E2kGlobalCatalog *gc, E2kOperation *op, LDAP *ldap)
+{
+	LDAPMessage *msg;
+	gint ldap_error, msgid, err;
+	gchar *nonce, *default_domain;
+	GByteArray *ba;
+	struct berval ldap_buf;
+
+	/* Create and send NTLM request */
+	ba = xntlm_negotiate ();
+	ldap_buf.bv_len = ba->len;
+	ldap_buf.bv_val = (gchar *) ba->data;
+	ldap_error = ldap_ntlm_bind (ldap, "NTLM", LDAP_AUTH_NTLM_REQUEST,
+				     &ldap_buf, NULL, NULL, &msgid);
+	g_byte_array_free (ba, TRUE);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC: Failure sending first NTLM bind message: 0x%02x\n", ldap_error));
+		return ldap_error;
+	}
+
+	/* Extract challenge */
+	ldap_error = gc_ldap_result (ldap, op, msgid, &msg);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC: Could not parse first NTLM bind response\n"));
+		return ldap_error;
+	}
+	ldap_error = ldap_parse_ntlm_bind_result (ldap, msg, &ldap_buf);
+	ldap_msgfree (msg);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC: Could not parse NTLM bind response: 0x%02x\n", ldap_error));
+		return ldap_error;
+	}
+
+	if (!xntlm_parse_challenge (ldap_buf.bv_val, ldap_buf.bv_len,
+				    &nonce, &default_domain,
+				    &gc->domain)) {
+		E2K_GC_DEBUG_MSG(("GC: Could not find nonce in NTLM bind response\n"));
+		ber_memfree (ldap_buf.bv_val);
+
+		return LDAP_DECODING_ERROR;
+	}
+	ber_memfree (ldap_buf.bv_val);
+
+	/* Create and send response */
+	ba = xntlm_authenticate (nonce, gc->priv->nt_domain ? gc->priv->nt_domain : default_domain,
+				 gc->priv->user, gc->priv->password, NULL);
+	ldap_buf.bv_len = ba->len;
+	ldap_buf.bv_val = (gchar *) ba->data;
+	ldap_error = ldap_ntlm_bind (ldap, "NTLM", LDAP_AUTH_NTLM_RESPONSE,
+				     &ldap_buf, NULL, NULL, &msgid);
+	g_byte_array_free (ba, TRUE);
+	g_free (nonce);
+	g_free (default_domain);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC: Failure sending second NTLM bind message: 0x%02x\n", ldap_error));
+		return ldap_error;
+	}
+
+	/* And get the final result */
+	ldap_error = gc_ldap_result (ldap, op, msgid, &msg);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC: Could not parse second NTLM bind response\n"));
+		return ldap_error;
+	}
+	ldap_error = ldap_parse_result (ldap, msg, &err, NULL, NULL,
+					NULL, NULL, TRUE);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC: Could not parse second NTLM bind response: 0x%02x\n", ldap_error));
+		return ldap_error;
+	}
+
+	return err;
+}
+#endif
+
+static gint
+connect_ldap (E2kGlobalCatalog *gc, E2kOperation *op, LDAP *ldap)
+{
+	gint ldap_error;
+	gchar *nt_name;
+#ifdef G_OS_WIN32
+	SEC_WINNT_AUTH_IDENTITY_W auth;
+#endif
+
+	/* authenticate */
+#ifdef HAVE_LDAP_NTLM_BIND
+	if ((gc->priv->auth == E2K_AUTOCONFIG_USE_GAL_DEFAULT || gc->priv->auth == E2K_AUTOCONFIG_USE_GAL_NTLM)) {
+		ldap_error = ntlm_bind (gc, op, ldap);
+		if (ldap_error == LDAP_SUCCESS) {
+			E2K_GC_DEBUG_MSG(("GC: connected via NTLM\n\n"));
+			return LDAP_SUCCESS;
+		} else if (gc->priv->auth == E2K_AUTOCONFIG_USE_GAL_NTLM) {
+			E2K_GC_DEBUG_MSG(("GC: user setup NTLM, but it failed 0x%02x\n", ldap_error));
+			return ldap_error;
+		}
+	}
+#endif
+
+	if (gc->priv->auth == E2K_AUTOCONFIG_USE_GAL_NTLM) {
+		E2K_GC_DEBUG_MSG(("GC: user setup NTLM, but we do not have it\n"));
+		/* a little hack */
+		return LDAP_AUTH_METHOD_NOT_SUPPORTED;
+	}
+
+	nt_name = gc->priv->nt_domain ?
+		g_strdup_printf ("%s\\%s", gc->priv->nt_domain, gc->priv->user) :
+		g_strdup (gc->priv->user);
+#ifndef G_OS_WIN32
+	ldap_error = ldap_simple_bind_s (ldap, nt_name, gc->priv->password);
+#else
+	auth.User = g_utf8_to_utf16 (gc->priv->user, -1, NULL, NULL, NULL);
+	auth.UserLength = wcslen (auth.User);
+	auth.Domain = gc->priv->nt_domain ?
+		g_utf8_to_utf16 (gc->priv->nt_domain, -1, NULL, NULL, NULL) :
+		g_utf8_to_utf16 ("", -1, NULL, NULL, NULL);
+	auth.DomainLength = wcslen (auth.Domain);
+	auth.Password = g_utf8_to_utf16 (gc->priv->password, -1, NULL, NULL, NULL);
+	auth.PasswordLength = wcslen (auth.Password);
+	auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
+	ldap_error = ldap_bind_s (ldap, nt_name, &auth, gc->priv->auth == E2K_AUTOCONFIG_USE_GAL_BASIC ? LDAP_AUTH_SIMPLE : LDAP_AUTH_NTLM);
+	g_free (auth.Password);
+	g_free (auth.Domain);
+	g_free (auth.User);
+#endif
+	g_free (nt_name);
+
+	if (ldap_error != LDAP_SUCCESS)
+		g_warning ("LDAP authentication failed (0x%02x (%s))", ldap_error, ldap_err2string (ldap_error) ? ldap_err2string (ldap_error) : "Unknown error");
+	else
+		E2K_GC_DEBUG_MSG(("GC: connected\n\n"));
+
+	return ldap_error;
+}
+
+static gint
+get_ldap_connection (E2kGlobalCatalog *gc, E2kOperation *op,
+		     const gchar *server, gint port,
+		     LDAP **ldap)
+{
+	gint ldap_opt, ldap_error;
+
+	E2K_GC_DEBUG_MSG(("\nGC: Connecting to ldap://%s:%d/\n";, server, port));
+
+	*ldap = ldap_init (server, port);
+	if (!*ldap) {
+		E2K_GC_DEBUG_MSG(("GC: failed\n\n"));
+		g_warning ("Could not connect to ldap://%s:%d/";,
+			   server, port);
+		return LDAP_SERVER_DOWN;
+	}
+
+	/* Set options */
+	ldap_opt = LDAP_DEREF_ALWAYS;
+	ldap_set_option (*ldap, LDAP_OPT_DEREF, &ldap_opt);
+	ldap_opt = gc->response_limit;
+	ldap_set_option (*ldap, LDAP_OPT_SIZELIMIT, &ldap_opt);
+	ldap_opt = LDAP_VERSION3;
+	ldap_set_option (*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldap_opt);
+
+	ldap_error = connect_ldap (gc, op, *ldap);
+	if (ldap_error != LDAP_SUCCESS) {
+		ldap_unbind (*ldap);
+		*ldap = NULL;
+	}
+	return ldap_error;
+}
+
+static gint
+get_gc_connection (E2kGlobalCatalog *gc, E2kOperation *op)
+{
+	gint err;
+
+	if (gc->priv->ldap) {
+		ldap_get_option (gc->priv->ldap, LDAP_OPT_ERROR_NUMBER, &err);
+		if (err != LDAP_SERVER_DOWN)
+			return LDAP_SUCCESS;
+
+		return connect_ldap (gc, op, gc->priv->ldap);
+	} else {
+		return get_ldap_connection (gc, op,
+					    gc->priv->server, 3268,
+					    &gc->priv->ldap);
+	}
+}
+
+/**
+ * e2k_global_catalog_get_ldap:
+ * @gc: the global catalog
+ * @op: pointer to an initialized #E2kOperation to use for cancellation
+ * @ldap_error: set the value returned from get_ldap_connection if not NULL
+ *
+ * Returns a new LDAP handle. The caller must ldap_unbind() it when it
+ * is done.
+ *
+ * Return value: an LDAP handle, or %NULL if it can't connect
+ **/
+LDAP *
+e2k_global_catalog_get_ldap (E2kGlobalCatalog *gc, E2kOperation *op, gint *ldap_error)
+{
+	LDAP *ldap;
+	gint err;
+
+	g_return_val_if_fail (E2K_IS_GLOBAL_CATALOG (gc), NULL);
+
+	err = get_ldap_connection (gc, op, gc->priv->server, 3268, &ldap);
+
+	if (ldap_error)
+		*ldap_error = err;
+
+	return ldap;
+}
+
+/**
+ * e2k_global_catalog_new:
+ * @server: the GC server name
+ * @response_limit: the maximum number of responses to return from a search
+ * @user: username to authenticate with
+ * @domain: NT domain of @user, or %NULL to autodetect.
+ * @password: password to authenticate with
+ *
+ * Create an object for communicating with the Windows Global Catalog
+ * via LDAP.
+ *
+ * Return value: the new E2kGlobalCatalog. (This call will always succeed.
+ * If the passed-in data is bad, it will fail on a later call.)
+ **/
+E2kGlobalCatalog *
+e2k_global_catalog_new (const gchar *server, gint response_limit,
+			const gchar *user, const gchar *domain,
+			const gchar *password, E2kAutoconfigGalAuthPref use_auth)
+{
+	E2kGlobalCatalog *gc;
+
+	gc = g_object_new (E2K_TYPE_GLOBAL_CATALOG, NULL);
+	gc->priv->server = g_strdup (server);
+	gc->priv->auth = use_auth;
+	gc->priv->user = g_strdup (user);
+	gc->priv->nt_domain = g_strdup (domain);
+	gc->priv->password = g_strdup (password);
+	gc->response_limit = response_limit;
+
+	return gc;
+}
+
+static const gchar *
+lookup_mta (E2kGlobalCatalog *gc, E2kOperation *op, const gchar *mta_dn)
+{
+	gchar *hostname, **values;
+	const gchar *attrs[2];
+	LDAPMessage *resp;
+	gint ldap_error, i;
+
+	/* Skip over "CN=Microsoft MTA," */
+	mta_dn = strchr (mta_dn, ',');
+	if (!mta_dn)
+		return NULL;
+	mta_dn++;
+
+	hostname = g_hash_table_lookup (gc->priv->server_cache, mta_dn);
+	if (hostname)
+		return hostname;
+
+	E2K_GC_DEBUG_MSG(("GC:   Finding hostname for %s\n", mta_dn));
+
+	attrs[0] = "networkAddress";
+	attrs[1] = NULL;
+
+	ldap_error = gc_search (gc, op, mta_dn, LDAP_SCOPE_BASE,
+				NULL, attrs, &resp);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC:   lookup failed (0x%02x)\n", ldap_error));
+		return NULL;
+	}
+
+	values = ldap_get_values (gc->priv->ldap, resp, "networkAddress");
+	ldap_msgfree (resp);
+	if (!values) {
+		E2K_GC_DEBUG_MSG(("GC:   entry has no networkAddress\n"));
+		return NULL;
+	}
+
+	hostname = NULL;
+	for (i = 0; values[i]; i++) {
+		if (strstr (values[i], "_tcp")) {
+			hostname = strchr (values[i], ':');
+			break;
+		}
+	}
+	if (!hostname) {
+		E2K_GC_DEBUG_MSG(("GC:   host is not availble by TCP?\n"));
+		ldap_value_free (values);
+		return NULL;
+	}
+
+	hostname = g_strdup (hostname + 1);
+	g_hash_table_insert (gc->priv->server_cache, g_strdup (mta_dn), hostname);
+	ldap_value_free (values);
+
+	E2K_GC_DEBUG_MSG(("GC:   %s\n", hostname));
+	return hostname;
+}
+
+static void
+get_sid_values (E2kGlobalCatalog *gc, E2kOperation *op,
+		LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
+{
+	gchar **values;
+	struct berval **bsid_values;
+	E2kSidType type;
+
+	values = ldap_get_values (gc->priv->ldap, msg, "displayName");
+	if (values) {
+		E2K_GC_DEBUG_MSG(("GC: displayName %s\n", values[0]));
+		entry->display_name = g_strdup (values[0]);
+		ldap_value_free (values);
+	}
+
+	bsid_values = ldap_get_values_len (gc->priv->ldap, msg, "objectSid");
+	if (!bsid_values)
+		return;
+	if (bsid_values[0]->bv_len < 2 ||
+	    bsid_values[0]->bv_len != E2K_SID_BINARY_SID_LEN (bsid_values[0]->bv_val)) {
+		E2K_GC_DEBUG_MSG(("GC: invalid SID\n"));
+		return;
+	}
+
+	values = ldap_get_values (gc->priv->ldap, msg, "objectCategory");
+	if (values && values[0] && !g_ascii_strncasecmp (values[0], "CN=Group", 8))
+		type = E2K_SID_TYPE_GROUP;
+	else if (values && values[0] && !g_ascii_strncasecmp (values[0], "CN=Foreign", 10))
+		type = E2K_SID_TYPE_WELL_KNOWN_GROUP;
+	else /* FIXME? */
+		type = E2K_SID_TYPE_USER;
+	if (values)
+		ldap_value_free (values);
+
+	entry->sid = e2k_sid_new_from_binary_sid (
+		type, (guint8 *) bsid_values[0]->bv_val, entry->display_name);
+	entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_SID;
+
+	ldap_value_free_len (bsid_values);
+}
+
+static void
+get_mail_values (E2kGlobalCatalog *gc, E2kOperation *op,
+		 LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
+{
+	gchar **values, **mtavalues;
+
+	values = ldap_get_values (gc->priv->ldap, msg, "mail");
+	if (values) {
+		E2K_GC_DEBUG_MSG(("GC: mail %s\n", values[0]));
+		entry->email = g_strdup (values[0]);
+		g_hash_table_insert (gc->priv->entry_cache,
+				     entry->email, entry);
+		entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_EMAIL;
+		ldap_value_free (values);
+	}
+
+	values = ldap_get_values (gc->priv->ldap, msg, "mailNickname");
+	mtavalues = ldap_get_values (gc->priv->ldap, msg, "homeMTA");
+	if (values && mtavalues) {
+		E2K_GC_DEBUG_MSG(("GC: mailNickname %s\n", values[0]));
+		E2K_GC_DEBUG_MSG(("GC: homeMTA %s\n", mtavalues[0]));
+		entry->exchange_server = (gchar *)lookup_mta (gc, op, mtavalues[0]);
+		ldap_value_free (mtavalues);
+		if (entry->exchange_server)
+			entry->mailbox = g_strdup (values[0]);
+		ldap_value_free (values);
+		entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX;
+	}
+
+	values = ldap_get_values (gc->priv->ldap, msg, "legacyExchangeDN");
+	if (values) {
+		E2K_GC_DEBUG_MSG(("GC: legacyExchangeDN %s\n", values[0]));
+		entry->legacy_exchange_dn = g_strdup (values[0]);
+		g_hash_table_insert (gc->priv->entry_cache,
+				     entry->legacy_exchange_dn,
+				     entry);
+		entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN;
+		ldap_value_free (values);
+	}
+}
+
+static void
+get_delegation_values (E2kGlobalCatalog *gc, E2kOperation *op,
+		       LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
+{
+	gchar **values;
+	gint i;
+
+	values = ldap_get_values (gc->priv->ldap, msg, "publicDelegates");
+	if (values) {
+		E2K_GC_DEBUG_MSG(("GC: publicDelegates\n"));
+		entry->delegates = g_ptr_array_new ();
+		for (i = 0; values[i]; i++) {
+			E2K_GC_DEBUG_MSG(("GC:   %s\n", values[i]));
+			g_ptr_array_add (entry->delegates,
+					 g_strdup (values[i]));
+		}
+		entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_DELEGATES;
+		ldap_value_free (values);
+	}
+	values = ldap_get_values (gc->priv->ldap, msg, "publicDelegatesBL");
+	if (values) {
+		E2K_GC_DEBUG_MSG(("GC: publicDelegatesBL\n"));
+		entry->delegators = g_ptr_array_new ();
+		for (i = 0; values[i]; i++) {
+			E2K_GC_DEBUG_MSG(("GC:   %s\n", values[i]));
+			g_ptr_array_add (entry->delegators,
+					 g_strdup (values[i]));
+		}
+		entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_DELEGATORS;
+		ldap_value_free (values);
+	}
+}
+
+static void
+get_quota_values (E2kGlobalCatalog *gc, E2kOperation *op,
+		  LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
+{
+	gchar **quota_setting_values, **quota_limit_values;
+
+	/* Check if mailbox store default values are used */
+	quota_setting_values = ldap_get_values (gc->priv->ldap, msg, "mDBUseDefaults");
+	if (!quota_setting_values) {
+		entry->quota_warn = entry->quota_nosend = entry->quota_norecv = 0;
+		return;
+	}
+
+	entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_QUOTA;
+	E2K_GC_DEBUG_MSG(("GC: mDBUseDefaults %s\n", quota_setting_values[0]));
+
+	if (!strcmp (quota_setting_values[0], "TRUE")) {
+		/* use global mailbox store settings */
+		E2K_GC_DEBUG_MSG(("GC: Using global mailbox store limits\n"));
+	}
+	ldap_value_free (quota_setting_values);
+
+	quota_limit_values = ldap_get_values (gc->priv->ldap, msg, "mDBStorageQuota");
+	if (quota_limit_values) {
+		entry->quota_warn = atoi(quota_limit_values[0]);
+		E2K_GC_DEBUG_MSG(("GC: mDBStorageQuota %s\n", quota_limit_values[0]));
+		ldap_value_free (quota_limit_values);
+	}
+
+	quota_limit_values = ldap_get_values (gc->priv->ldap, msg, "mDBOverQuotaLimit");
+	if (quota_limit_values) {
+		entry->quota_nosend = atoi(quota_limit_values[0]);
+		E2K_GC_DEBUG_MSG(("GC: mDBOverQuotaLimit %s\n", quota_limit_values[0]));
+		ldap_value_free (quota_limit_values);
+	}
+
+	quota_limit_values = ldap_get_values (gc->priv->ldap, msg, "mDBOverHardQuotaLimit");
+	if (quota_limit_values) {
+		entry->quota_norecv = atoi(quota_limit_values[0]);
+		E2K_GC_DEBUG_MSG(("GC: mDBHardQuotaLimit %s\n", quota_limit_values[0]));
+		ldap_value_free (quota_limit_values);
+	}
+}
+
+static void
+get_account_control_values (E2kGlobalCatalog *gc, E2kOperation *op,
+			    LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
+{
+	gchar **values;
+
+	values = ldap_get_values (gc->priv->ldap, msg, "userAccountControl");
+	if (values) {
+		entry->user_account_control = atoi(values[0]);
+		E2K_GC_DEBUG_MSG(("GC: userAccountControl %s\n", values[0]));
+		entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL;
+		ldap_value_free (values);
+	}
+
+}
+
+/**
+ * e2k_global_catalog_lookup:
+ * @gc: the global catalog
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @type: the type of information in @key
+ * @key: email address or DN to look up
+ * @flags: the information to look up
+ * @entry_p: pointer to a variable to return the entry in.
+ *
+ * Look up the indicated user in the global catalog and
+ * return their information in * entry_p 
+ *
+ * Return value: the status of the lookup
+ **/
+E2kGlobalCatalogStatus
+e2k_global_catalog_lookup (E2kGlobalCatalog *gc,
+			   E2kOperation *op,
+			   E2kGlobalCatalogLookupType type,
+			   const gchar *key,
+			   E2kGlobalCatalogLookupFlags flags,
+			   E2kGlobalCatalogEntry **entry_p)
+{
+	E2kGlobalCatalogEntry *entry;
+	GPtrArray *attrs;
+	E2kGlobalCatalogLookupFlags lookup_flags, need_flags = 0;
+	const gchar *base = NULL;
+	gchar *filter = NULL, *dn;
+	gint scope = LDAP_SCOPE_BASE, ldap_error;
+	E2kGlobalCatalogStatus status;
+	LDAPMessage *msg, *resp;
+
+	g_return_val_if_fail (E2K_IS_GLOBAL_CATALOG (gc), E2K_GLOBAL_CATALOG_ERROR);
+	g_return_val_if_fail (key != NULL, E2K_GLOBAL_CATALOG_ERROR);
+
+	g_mutex_lock (gc->priv->ldap_lock);
+
+	entry = g_hash_table_lookup (gc->priv->entry_cache, key);
+	if (!entry)
+		entry = g_new0 (E2kGlobalCatalogEntry, 1);
+
+	attrs = g_ptr_array_new ();
+
+	if (!entry->display_name)
+		g_ptr_array_add (attrs, (guint8 *) "displayName");
+	if (!entry->email) {
+		g_ptr_array_add (attrs, (guint8 *) "mail");
+		if (flags & E2K_GLOBAL_CATALOG_LOOKUP_EMAIL)
+			need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_EMAIL;
+	}
+	if (!entry->legacy_exchange_dn) {
+		g_ptr_array_add (attrs, (guint8 *) "legacyExchangeDN");
+		if (flags & E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN)
+			need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN;
+	}
+
+	lookup_flags = flags & ~entry->mask;
+
+	if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_SID) {
+		g_ptr_array_add (attrs, (guint8 *) "objectSid");
+		g_ptr_array_add (attrs, (guint8 *) "objectCategory");
+		need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_SID;
+	}
+	if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX) {
+		g_ptr_array_add (attrs, (guint8 *) "mailNickname");
+		g_ptr_array_add (attrs, (guint8 *) "homeMTA");
+		need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX;
+	}
+	if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_DELEGATES)
+		g_ptr_array_add (attrs, (guint8 *) "publicDelegates");
+	if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_DELEGATORS)
+		g_ptr_array_add (attrs, (guint8 *) "publicDelegatesBL");
+	if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_QUOTA) {
+		g_ptr_array_add (attrs, (guint8 *) "mDBUseDefaults");
+		g_ptr_array_add (attrs, (guint8 *) "mDBStorageQuota");
+		g_ptr_array_add (attrs, (guint8 *) "mDBOverQuotaLimit");
+		g_ptr_array_add (attrs, (guint8 *) "mDBOverHardQuotaLimit");
+	}
+	if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL)
+		g_ptr_array_add (attrs, (guint8 *) "userAccountControl");
+
+	if (attrs->len == 0) {
+		E2K_GC_DEBUG_MSG(("\nGC: returning cached info for %s\n", key));
+		goto lookedup;
+	}
+
+	E2K_GC_DEBUG_MSG(("\nGC: looking up info for %s\n", key));
+	g_ptr_array_add (attrs, NULL);
+
+	switch (type) {
+	case E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL:
+		filter = g_strdup_printf ("(mail=%s)", key);
+		base = LDAP_ROOT_DSE;
+		scope = LDAP_SCOPE_SUBTREE;
+		break;
+
+	case E2K_GLOBAL_CATALOG_LOOKUP_BY_DN:
+		filter = NULL;
+		base = key;
+		scope = LDAP_SCOPE_BASE;
+		break;
+
+	case E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN:
+		filter = g_strdup_printf ("(legacyExchangeDN=%s)", key);
+		base = LDAP_ROOT_DSE;
+		scope = LDAP_SCOPE_SUBTREE;
+		break;
+	}
+
+	ldap_error = gc_search (gc, op, base, scope, filter,
+				(const gchar **)attrs->pdata, &msg);
+	if (ldap_error == LDAP_USER_CANCELLED) {
+		E2K_GC_DEBUG_MSG(("GC: ldap_search cancelled"));
+		status = E2K_GLOBAL_CATALOG_CANCELLED;
+		goto done;
+	} else if (ldap_error == LDAP_INVALID_CREDENTIALS) {
+		E2K_GC_DEBUG_MSG(("GC: ldap_search auth failed"));
+		status = E2K_GLOBAL_CATALOG_AUTH_FAILED;
+		goto done;
+	} else if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC: ldap_search failed: 0x%02x\n\n", ldap_error));
+		status = E2K_GLOBAL_CATALOG_ERROR;
+		goto done;
+	}
+
+	resp = ldap_first_entry (gc->priv->ldap, msg);
+	if (!resp) {
+		E2K_GC_DEBUG_MSG(("GC: no such user\n\n"));
+		status = E2K_GLOBAL_CATALOG_NO_SUCH_USER;
+		ldap_msgfree (msg);
+		goto done;
+	}
+
+	if (!entry->dn) {
+		dn = ldap_get_dn (gc->priv->ldap, resp);
+		entry->dn = g_strdup (dn);
+		E2K_GC_DEBUG_MSG(("GC: dn = %s\n\n", dn));
+		ldap_memfree (dn);
+		g_ptr_array_add (gc->priv->entries, entry);
+		g_hash_table_insert (gc->priv->entry_cache,
+				     entry->dn, entry);
+	}
+
+	get_sid_values (gc, op, resp, entry);
+	get_mail_values (gc, op, resp, entry);
+	get_delegation_values (gc, op, resp, entry);
+	get_quota_values (gc, op, resp, entry);
+	get_account_control_values (gc, op, resp, entry);
+	ldap_msgfree (msg);
+
+ lookedup:
+	if (need_flags & ~entry->mask) {
+		E2K_GC_DEBUG_MSG(("GC: no data\n\n"));
+		status = E2K_GLOBAL_CATALOG_NO_DATA;
+	} else {
+		E2K_GC_DEBUG_MSG(("\n"));
+		status = E2K_GLOBAL_CATALOG_OK;
+		entry->mask |= lookup_flags;
+		*entry_p = entry;
+	}
+
+ done:
+	g_free (filter);
+	g_ptr_array_free (attrs, TRUE);
+
+	if (status != E2K_GLOBAL_CATALOG_OK && !entry->dn)
+		g_free (entry);
+
+	g_mutex_unlock (gc->priv->ldap_lock);
+	return status;
+}
+
+struct async_lookup_data {
+	E2kGlobalCatalog *gc;
+	E2kOperation *op;
+	E2kGlobalCatalogLookupType type;
+	gchar *key;
+	E2kGlobalCatalogLookupFlags flags;
+	E2kGlobalCatalogCallback callback;
+	gpointer user_data;
+
+	E2kGlobalCatalogEntry *entry;
+	E2kGlobalCatalogStatus status;
+};
+
+static gboolean
+idle_lookup_result (gpointer user_data)
+{
+	struct async_lookup_data *ald = user_data;
+
+	ald->callback (ald->gc, ald->status, ald->entry, ald->user_data);
+	g_object_unref (ald->gc);
+	g_free (ald->key);
+	g_free (ald);
+	return FALSE;
+}
+
+static gpointer
+do_lookup_thread (gpointer user_data)
+{
+	struct async_lookup_data *ald = user_data;
+
+	ald->status = e2k_global_catalog_lookup (ald->gc, ald->op, ald->type,
+						 ald->key, ald->flags,
+						 &ald->entry);
+	g_idle_add (idle_lookup_result, ald);
+	return NULL;
+}
+
+/**
+ * e2k_global_catalog_async_lookup:
+ * @gc: the global catalog
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @type: the type of information in @key
+ * @key: email address or DN to look up
+ * @flags: the information to look up
+ * @callback: the callback to invoke after finding the user
+ * @user_data: data to pass to callback
+ *
+ * Asynchronously look up the indicated user in the global catalog and
+ * return the requested information to the callback.
+ **/
+void
+e2k_global_catalog_async_lookup (E2kGlobalCatalog *gc,
+				 E2kOperation *op,
+				 E2kGlobalCatalogLookupType type,
+				 const gchar *key,
+				 E2kGlobalCatalogLookupFlags flags,
+				 E2kGlobalCatalogCallback callback,
+				 gpointer user_data)
+{
+	struct async_lookup_data *ald;
+	pthread_t pth;
+
+	ald = g_new0 (struct async_lookup_data, 1);
+	ald->gc = g_object_ref (gc);
+	ald->op = op;
+	ald->type = type;
+	ald->key = g_strdup (key);
+	ald->flags = flags;
+	ald->callback = callback;
+	ald->user_data = user_data;
+
+	if (pthread_create (&pth, NULL, do_lookup_thread, ald) == -1) {
+		g_warning ("Could not create lookup thread\n");
+		ald->status = E2K_GLOBAL_CATALOG_ERROR;
+		g_idle_add (idle_lookup_result, ald);
+	}
+}
+
+static const gchar *
+lookup_controlling_ad_server (E2kGlobalCatalog *gc, E2kOperation *op,
+			      const gchar *dn)
+{
+	gchar *hostname, **values, *ad_dn;
+	const gchar *attrs[2];
+	LDAPMessage *resp;
+	gint ldap_error;
+
+	while (g_ascii_strncasecmp (dn, "DC=", 3) != 0) {
+		dn = strchr (dn, ',');
+		if (!dn)
+			return NULL;
+		dn++;
+	}
+
+	hostname = g_hash_table_lookup (gc->priv->server_cache, dn);
+	if (hostname)
+		return hostname;
+
+	E2K_GC_DEBUG_MSG(("GC:   Finding AD server for %s\n", dn));
+
+	attrs[0] = "masteredBy";
+	attrs[1] = NULL;
+
+	ldap_error = gc_search (gc, op, dn, LDAP_SCOPE_BASE, NULL, attrs, &resp);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC:   ldap_search failed: 0x%02x\n", ldap_error));
+		return NULL;
+	}
+
+	values = ldap_get_values (gc->priv->ldap, resp, "masteredBy");
+	ldap_msgfree (resp);
+	if (!values) {
+		E2K_GC_DEBUG_MSG(("GC:   no known AD server\n\n"));
+		return NULL;
+	}
+
+	/* Skip over "CN=NTDS Settings," */
+	ad_dn = strchr (values[0], ',');
+	if (!ad_dn) {
+		E2K_GC_DEBUG_MSG(("GC:   bad dn %s\n\n", values[0]));
+		ldap_value_free (values);
+		return NULL;
+	}
+	ad_dn++;
+
+	attrs[0] = "dNSHostName";
+	attrs[1] = NULL;
+
+	ldap_error = gc_search (gc, op, ad_dn, LDAP_SCOPE_BASE, NULL, attrs, &resp);
+	ldap_value_free (values);
+
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC:   ldap_search failed: 0x%02x\n\n", ldap_error));
+		return NULL;
+	}
+
+	values = ldap_get_values (gc->priv->ldap, resp, "dNSHostName");
+	ldap_msgfree (resp);
+	if (!values) {
+		E2K_GC_DEBUG_MSG(("GC:   entry has no dNSHostName\n\n"));
+		return NULL;
+	}
+
+	hostname = g_strdup (values[0]);
+	ldap_value_free (values);
+
+	g_hash_table_insert (gc->priv->server_cache, g_strdup (dn), hostname);
+
+	E2K_GC_DEBUG_MSG(("GC:   %s\n", hostname));
+	return hostname;
+}
+
+static gchar *
+find_domain_dn (gchar *domain)
+{
+	GString *dn_value = g_string_new (NULL);
+	gchar *dn;
+	gchar  *sub_domain=NULL;
+
+	sub_domain = strtok (domain, ".");
+	while (sub_domain != NULL) {
+		g_string_append (dn_value, "DC=");
+		g_string_append (dn_value, sub_domain);
+		g_string_append (dn_value, ",");
+		sub_domain = strtok (NULL, ".");
+	}
+	if (dn_value->str[0])
+		dn = g_strndup (dn_value->str, strlen(dn_value->str) - 1);
+	else
+		dn = NULL;
+	g_string_free (dn_value, TRUE);
+	return dn;
+}
+
+gdouble
+lookup_passwd_max_age (E2kGlobalCatalog *gc, E2kOperation *op)
+{
+	gchar **values = NULL, *filter = NULL, *val=NULL;
+	const gchar *attrs[2];
+	LDAP *ldap;
+	LDAPMessage *msg=NULL;
+	gint ldap_error, msgid;
+	gdouble maxAge=0;
+	gchar *dn=NULL;
+
+	attrs[0] = "maxPwdAge";
+	attrs[1] = NULL;
+
+	filter = g_strdup("objectClass=domainDNS");
+
+	dn = find_domain_dn (gc->domain);
+
+	ldap_error = get_ldap_connection (gc, op, gc->priv->server, LDAP_PORT, &ldap);
+	if (ldap_error != LDAP_SUCCESS) {
+		E2K_GC_DEBUG_MSG(("GC: Establishing ldap connection failed : 0x%02x\n\n",
+									ldap_error));
+		return -1;
+	}
+
+	ldap_error = ldap_search_ext (ldap, dn, LDAP_SCOPE_BASE, filter, (gchar **)attrs,
+				      FALSE, NULL, NULL, NULL, 0, &msgid);
+	if (!ldap_error) {
+		ldap_error = gc_ldap_result (ldap, op, msgid, &msg);
+		if (ldap_error) {
+			E2K_GC_DEBUG_MSG(("GC: ldap_result failed: 0x%02x\n\n", ldap_error));
+			return -1;
+		}
+	}
+	else {
+		E2K_GC_DEBUG_MSG(("GC: ldap_search failed:0x%02x \n\n", ldap_error));
+		return -1;
+	}
+
+	values = ldap_get_values (ldap, msg, "maxPwdAge");
+	if (!values) {
+		E2K_GC_DEBUG_MSG(("GC: couldn't retrieve maxPwdAge\n"));
+		return -1;
+	}
+
+	if (values[0]) {
+		val = values[0];
+		if (*val == '-')
+			++val;
+		maxAge = strtod (val, NULL);
+	}
+
+	/*g_hash_table_insert (gc->priv->server_cache, g_strdup (dn), hostname); FIXME? */
+
+	E2K_GC_DEBUG_MSG(("GC:   maxPwdAge = %f\n", maxAge));
+
+	if (msg)
+		ldap_msgfree (msg);
+	if (values)
+		ldap_value_free (values);
+	ldap_unbind (ldap);
+	g_free (filter);
+	g_free (dn);
+	return maxAge;
+}
+
+static E2kGlobalCatalogStatus
+do_delegate_op (E2kGlobalCatalog *gc, E2kOperation *op, gint deleg_op,
+		const gchar *self_dn, const gchar *delegate_dn)
+{
+	LDAP *ldap;
+	LDAPMod *mods[2], mod;
+	const gchar *ad_server;
+	gchar *values[2];
+	gint ldap_error, msgid;
+
+	g_return_val_if_fail (E2K_IS_GLOBAL_CATALOG (gc), E2K_GLOBAL_CATALOG_ERROR);
+	g_return_val_if_fail (self_dn != NULL, E2K_GLOBAL_CATALOG_ERROR);
+	g_return_val_if_fail (delegate_dn != NULL, E2K_GLOBAL_CATALOG_ERROR);
+
+	ad_server = lookup_controlling_ad_server (gc, op, self_dn);
+	if (!ad_server) {
+		if (e2k_operation_is_cancelled (op))
+			return E2K_GLOBAL_CATALOG_CANCELLED;
+		else
+			return E2K_GLOBAL_CATALOG_ERROR;
+	}
+
+	ldap_error = get_ldap_connection (gc, op, ad_server, LDAP_PORT, &ldap);
+	if (ldap_error == LDAP_USER_CANCELLED)
+		return E2K_GLOBAL_CATALOG_CANCELLED;
+	else if (ldap_error != LDAP_SUCCESS)
+		return E2K_GLOBAL_CATALOG_ERROR;
+
+	mod.mod_op = deleg_op;
+	mod.mod_type = (gchar *) "publicDelegates";
+	mod.mod_values = values;
+	values[0] = (gchar *)delegate_dn;
+	values[1] = NULL;
+
+	mods[0] = &mod;
+	mods[1] = NULL;
+
+	ldap_error = ldap_modify_ext (ldap, self_dn, mods, NULL, NULL, &msgid);
+	if (ldap_error == LDAP_SUCCESS) {
+		LDAPMessage *msg;
+
+		ldap_error = gc_ldap_result (ldap, op, msgid, &msg);
+		if (ldap_error == LDAP_SUCCESS) {
+			ldap_parse_result (ldap, msg, &ldap_error, NULL, NULL,
+					   NULL, NULL, TRUE);
+		}
+	}
+	ldap_unbind (ldap);
+
+	switch (ldap_error) {
+	case LDAP_SUCCESS:
+		E2K_GC_DEBUG_MSG(("\n"));
+		return E2K_GLOBAL_CATALOG_OK;
+
+	case LDAP_NO_SUCH_OBJECT:
+		E2K_GC_DEBUG_MSG(("GC: no such user\n\n"));
+		return E2K_GLOBAL_CATALOG_NO_SUCH_USER;
+
+	case LDAP_NO_SUCH_ATTRIBUTE:
+		E2K_GC_DEBUG_MSG(("GC: no such delegate\n\n"));
+		return E2K_GLOBAL_CATALOG_NO_DATA;
+
+	case LDAP_CONSTRAINT_VIOLATION:
+		E2K_GC_DEBUG_MSG(("GC: bad delegate\n\n"));
+		return E2K_GLOBAL_CATALOG_BAD_DATA;
+
+	case LDAP_TYPE_OR_VALUE_EXISTS:
+		E2K_GC_DEBUG_MSG(("GC: delegate already exists\n\n"));
+		return E2K_GLOBAL_CATALOG_EXISTS;
+
+	case LDAP_USER_CANCELLED:
+		E2K_GC_DEBUG_MSG(("GC: cancelled\n\n"));
+		return E2K_GLOBAL_CATALOG_CANCELLED;
+
+	default:
+		E2K_GC_DEBUG_MSG(("GC: ldap_modify failed: 0x%02x\n\n", ldap_error));
+		return E2K_GLOBAL_CATALOG_ERROR;
+	}
+}
+
+/**
+ * e2k_global_catalog_add_delegate:
+ * @gc: the global catalog
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @self_dn: Active Directory DN of the user to add a delegate to
+ * @delegate_dn: Active Directory DN of the new delegate
+ *
+ * Attempts to make @delegate_dn a delegate of @self_dn.
+ *
+ * Return value: %E2K_GLOBAL_CATALOG_OK on success,
+ * %E2K_GLOBAL_CATALOG_NO_SUCH_USER if @self_dn is invalid,
+ * %E2K_GLOBAL_CATALOG_BAD_DATA if @delegate_dn is invalid,
+ * %E2K_GLOBAL_CATALOG_EXISTS if @delegate_dn is already a delegate,
+ * %E2K_GLOBAL_CATALOG_ERROR on other errors.
+ **/
+E2kGlobalCatalogStatus
+e2k_global_catalog_add_delegate (E2kGlobalCatalog *gc,
+				 E2kOperation *op,
+				 const gchar *self_dn,
+				 const gchar *delegate_dn)
+{
+	E2K_GC_DEBUG_MSG(("\nGC: adding %s as delegate for %s\n", delegate_dn, self_dn));
+
+	return do_delegate_op (gc, op, LDAP_MOD_ADD, self_dn, delegate_dn);
+}
+
+/**
+ * e2k_global_catalog_remove_delegate:
+ * @gc: the global catalog
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @self_dn: Active Directory DN of the user to remove a delegate from
+ * @delegate_dn: Active Directory DN of the delegate to remove
+ *
+ * Attempts to remove @delegate_dn as a delegate of @self_dn.
+ *
+ * Return value: %E2K_GLOBAL_CATALOG_OK on success,
+ * %E2K_GLOBAL_CATALOG_NO_SUCH_USER if @self_dn is invalid,
+ * %E2K_GLOBAL_CATALOG_NO_DATA if @delegate_dn is not a delegate of @self_dn,
+ * %E2K_GLOBAL_CATALOG_ERROR on other errors.
+ **/
+E2kGlobalCatalogStatus
+e2k_global_catalog_remove_delegate (E2kGlobalCatalog *gc,
+				    E2kOperation *op,
+				    const gchar *self_dn,
+				    const gchar *delegate_dn)
+{
+	E2K_GC_DEBUG_MSG(("\nGC: removing %s as delegate for %s\n", delegate_dn, self_dn));
+
+	return do_delegate_op (gc, op, LDAP_MOD_DELETE, self_dn, delegate_dn);
+}
diff --git a/server/lib/e2k-global-catalog.h b/server/lib/e2k-global-catalog.h
new file mode 100644
index 0000000..096f81f
--- /dev/null
+++ b/server/lib/e2k-global-catalog.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_GLOBAL_CATALOG_H__
+#define __E2K_GLOBAL_CATALOG_H__
+
+#include <glib-object.h>
+#include "e2k-types.h"
+#include "e2k-operation.h"
+#include "e2k-validate.h"
+
+G_BEGIN_DECLS
+
+#define E2K_TYPE_GLOBAL_CATALOG            (e2k_global_catalog_get_type ())
+#define E2K_GLOBAL_CATALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E2K_TYPE_GLOBAL_CATALOG, E2kGlobalCatalog))
+#define E2K_GLOBAL_CATALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E2K_TYPE_GLOBAL_CATALOG, E2kGlobalCatalogClass))
+#define E2K_IS_GLOBAL_CATALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E2K_TYPE_GLOBAL_CATALOG))
+#define E2K_IS_GLOBAL_CATALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E2K_TYPE_GLOBAL_CATALOG))
+
+struct _E2kGlobalCatalog {
+	GObject parent;
+
+	gchar *domain;
+	gint response_limit;
+
+	E2kGlobalCatalogPrivate *priv;
+};
+
+struct _E2kGlobalCatalogClass {
+	GObjectClass parent_class;
+
+};
+
+GType             e2k_global_catalog_get_type        (void);
+E2kGlobalCatalog *e2k_global_catalog_new             (const gchar *server,
+						      gint response_limit,
+						      const gchar *user,
+						      const gchar *domain,
+						      const gchar *password,
+						      E2kAutoconfigGalAuthPref use_auth);
+
+typedef enum {
+	E2K_GLOBAL_CATALOG_OK,
+	E2K_GLOBAL_CATALOG_NO_SUCH_USER,
+	E2K_GLOBAL_CATALOG_NO_DATA,
+	E2K_GLOBAL_CATALOG_BAD_DATA,
+	E2K_GLOBAL_CATALOG_EXISTS,
+	E2K_GLOBAL_CATALOG_AUTH_FAILED,
+	E2K_GLOBAL_CATALOG_CANCELLED,
+	E2K_GLOBAL_CATALOG_ERROR
+} E2kGlobalCatalogStatus;
+
+typedef enum {
+	E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL,
+	E2K_GLOBAL_CATALOG_LOOKUP_BY_DN,
+	E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN
+} E2kGlobalCatalogLookupType;
+
+typedef enum {
+	E2K_GLOBAL_CATALOG_LOOKUP_SID                = (1 << 0),
+	E2K_GLOBAL_CATALOG_LOOKUP_EMAIL              = (1 << 1),
+	E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX            = (1 << 2),
+	E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN = (1 << 3),
+	E2K_GLOBAL_CATALOG_LOOKUP_DELEGATES          = (1 << 4),
+	E2K_GLOBAL_CATALOG_LOOKUP_DELEGATORS         = (1 << 5),
+	E2K_GLOBAL_CATALOG_LOOKUP_QUOTA		     = (1 << 6),
+	E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL    = (1 << 7)
+} E2kGlobalCatalogLookupFlags;
+
+typedef struct {
+	gchar *dn, *display_name;
+	E2kSid *sid;
+	gchar *email, *exchange_server, *mailbox, *legacy_exchange_dn;
+	GPtrArray *delegates, *delegators;
+	gint quota_warn, quota_nosend, quota_norecv;
+	gint user_account_control;
+
+	E2kGlobalCatalogLookupFlags mask;
+} E2kGlobalCatalogEntry;
+
+E2kGlobalCatalogStatus e2k_global_catalog_lookup (E2kGlobalCatalog *gc,
+						  E2kOperation     *op,
+						  E2kGlobalCatalogLookupType type,
+						  const gchar *key,
+						  E2kGlobalCatalogLookupFlags flags,
+						  E2kGlobalCatalogEntry **entry_p);
+
+typedef void         (*E2kGlobalCatalogCallback) (E2kGlobalCatalog *gc,
+						  E2kGlobalCatalogStatus status,
+						  E2kGlobalCatalogEntry *entry,
+						  gpointer user_data);
+
+void             e2k_global_catalog_async_lookup (E2kGlobalCatalog *gc,
+						  E2kOperation     *op,
+						  E2kGlobalCatalogLookupType type,
+						  const gchar *key,
+						  E2kGlobalCatalogLookupFlags flags,
+						  E2kGlobalCatalogCallback callback,
+						  gpointer user_data);
+
+gdouble		lookup_passwd_max_age (E2kGlobalCatalog *gc,
+				      E2kOperation *op);
+
+#define e2k_global_catalog_entry_free(gc, entry)
+
+E2kGlobalCatalogStatus e2k_global_catalog_add_delegate    (E2kGlobalCatalog *gc,
+							   E2kOperation     *op,
+							   const gchar *self_dn,
+							   const gchar *delegate_dn);
+E2kGlobalCatalogStatus e2k_global_catalog_remove_delegate (E2kGlobalCatalog *gc,
+							   E2kOperation     *op,
+							   const gchar *self_dn,
+							   const gchar *delegate_dn);
+
+G_END_DECLS
+
+#endif /* __E2K_GLOBAL_CATALOG_H__ */
diff --git a/server/lib/e2k-http-utils.c b/server/lib/e2k-http-utils.c
new file mode 100644
index 0000000..56da368
--- /dev/null
+++ b/server/lib/e2k-http-utils.c
@@ -0,0 +1,185 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-http-utils.h"
+
+#include <libedataserver/e-time-utils.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+extern const gchar *e2k_rfc822_months [];
+
+/**
+ * e2k_http_parse_date:
+ * @date: an HTTP Date header, returned from Exchange
+ *
+ * Converts an HTTP Date header into a time_t value. Doesn't
+ * do much sanity checking on the format since we know IIS always
+ * returns the date in RFC 1123 format, not either of the other two
+ * allowable formats.
+ *
+ * Return value: a %time_t corresponding to @date.
+ **/
+time_t
+e2k_http_parse_date (const gchar *date)
+{
+	struct tm tm;
+	gchar *p;
+
+	if (strlen (date) < 29 || date[3] != ',' || date[4] != ' ')
+		return -1;
+
+	memset (&tm, 0, sizeof (tm));
+	p = (gchar *)date + 5;
+
+	tm.tm_mday = strtol (p, &p, 10);
+	p++;
+	for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
+		if (!strncmp (p, e2k_rfc822_months[tm.tm_mon], 3))
+			break;
+	}
+	p += 3;
+
+	tm.tm_year = strtol (p, &p, 10) - 1900;
+
+	tm.tm_hour = strtol (p, &p, 10);
+	p++;
+	tm.tm_min  = strtol (p, &p, 10);
+	p++;
+	tm.tm_sec  = strtol (p, &p, 10);
+
+	return e_mktime_utc (&tm);
+}
+
+/**
+ * e2k_http_parse_status:
+ * @status_line: an HTTP Status-Line
+ *
+ * Parses an HTTP Status-Line and returns the Status-Code
+ *
+ * Return value: the Status-Code portion of @status_line
+ **/
+E2kHTTPStatus
+e2k_http_parse_status (const gchar *status_line)
+{
+	if (strncmp (status_line, "HTTP/1.", 7) != 0 ||
+	    !isdigit (status_line[7]) ||
+	    status_line[8] != ' ')
+		return E2K_HTTP_MALFORMED;
+
+	return atoi (status_line + 9);
+}
+
+/**
+ * e2k_http_accept_language:
+ *
+ * Generates an Accept-Language value to send to the Exchange server.
+ * The user's default folders (Inbox, Calendar, etc) are not created
+ * until the user connects to Exchange for the first time, and in that
+ * case, it needs to know what language to name the folders in.
+ * libexchange users are responsible for setting the Accept-Language
+ * header on any request that could be the first-ever request to a
+ * mailbox. (Exchange will return 401 Unauthorized if it receives a
+ * request with no Accept-Language header for an uninitialized
+ * mailbox.)
+ *
+ * Return value: an Accept-Language string.
+ **/
+const gchar *
+e2k_http_accept_language (void)
+{
+	static gchar *accept = NULL;
+
+	if (!accept) {
+		GString *buf;
+		const gchar *lang, *sub;
+		gint baselen;
+
+		buf = g_string_new (NULL);
+
+		lang = getenv ("LANG");
+		if (!lang || !strcmp (lang, "C") || !strcmp (lang, "POSIX"))
+			lang = "en";
+
+		/* lang is "language[_territory][ codeset][ modifier]",
+		 * eg "fr" or "de_AT utf8 euro". The Accept-Language
+		 * header should be a comma-separated list of
+		 * "language[-territory]". For the above cases we'd
+		 * generate "fr, en" and "de-AT, de, en". (We always
+		 * include "en" in case the server doesn't support the
+		 * user's preferred locale.)
+		 */
+
+		baselen = strcspn (lang, "_.@");
+		g_string_append_len (buf, lang, baselen);
+		if (lang[baselen] == '_') {
+			sub = lang + baselen + 1;
+			g_string_append_c   (buf, '-');
+			g_string_append_len (buf, sub, strcspn (sub, ".@"));
+
+			g_string_append     (buf, ", ");
+			g_string_append_len (buf, lang, baselen);
+		}
+
+		if (baselen != 2 || strncmp (lang, "en", 2) != 0)
+			g_string_append (buf, ", en");
+
+		accept = buf->str;
+		g_string_free (buf, FALSE);
+	}
+
+	return accept;
+}
+
+typedef struct {
+	const gchar *wanted_header;
+	GSList *matches;
+} GetHeadersData;
+
+static void
+maybe_append_header (const gchar *header_name, const gchar *value, gpointer data)
+{
+	GetHeadersData *ghd = data;
+
+	if (!g_ascii_strcasecmp (header_name, ghd->wanted_header))
+		ghd->matches = g_slist_append (ghd->matches, (gchar *)value);
+}
+
+/* This is a cheat to recreate the behavior of libsoup 2.2's
+ * soup_message_get_header_list. See the docs for
+ * soup_message_headers_get() for an explanation of why we shouldn't
+ * be doing this...
+ */
+GSList *
+e2k_http_get_headers (SoupMessageHeaders *hdrs, const gchar *header_name)
+{
+	GetHeadersData ghd;
+
+	ghd.wanted_header = header_name;
+	ghd.matches = NULL;
+	soup_message_headers_foreach (hdrs, maybe_append_header, &ghd);
+	return ghd.matches;
+}
+
diff --git a/server/lib/e2k-http-utils.h b/server/lib/e2k-http-utils.h
new file mode 100644
index 0000000..76f3724
--- /dev/null
+++ b/server/lib/e2k-http-utils.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_HTTP_UTILS_H__
+#define __E2K_HTTP_UTILS_H__
+
+#include "e2k-types.h"
+#include <time.h>
+#include <libsoup/soup-status.h>
+#include <libsoup/soup-message-headers.h>
+
+typedef guint E2kHTTPStatus;
+
+time_t         e2k_http_parse_date      (const gchar *date);
+E2kHTTPStatus  e2k_http_parse_status    (const gchar *status_line);
+
+const gchar    *e2k_http_accept_language (void);
+
+GSList        *e2k_http_get_headers     (SoupMessageHeaders *hdrs,
+					 const gchar         *header_name);
+
+#define E2K_HTTP_STATUS_IS_TRANSPORT_ERROR(status) SOUP_STATUS_IS_TRANSPORT_ERROR(status)
+#define E2K_HTTP_CANCELLED                         SOUP_STATUS_CANCELLED
+#define E2K_HTTP_CANT_RESOLVE                      SOUP_STATUS_CANT_RESOLVE
+#define E2K_HTTP_CANT_CONNECT                      SOUP_STATUS_CANT_CONNECT
+#define E2K_HTTP_SSL_FAILED                        SOUP_STATUS_SSL_FAILED
+#define E2K_HTTP_IO_ERROR                          SOUP_STATUS_IO_ERROR
+#define E2K_HTTP_MALFORMED                         SOUP_STATUS_MALFORMED
+
+#define E2K_HTTP_STATUS_IS_INFORMATIONAL(status)   SOUP_STATUS_IS_INFORMATIONAL(status)
+#define E2K_HTTP_CONTINUE                          100
+
+#define E2K_HTTP_STATUS_IS_SUCCESSFUL(status)      SOUP_STATUS_IS_SUCCESSFUL(status)
+#define E2K_HTTP_OK                                200
+#define E2K_HTTP_CREATED                           201
+#define E2K_HTTP_ACCEPTED                          202
+#define E2K_HTTP_NO_CONTENT                        204
+#define E2K_HTTP_MULTI_STATUS                      207
+
+#define E2K_HTTP_STATUS_IS_REDIRECTION(status)     SOUP_STATUS_IS_REDIRECTION(status)
+
+#define E2K_HTTP_STATUS_IS_CLIENT_ERROR(status)    SOUP_STATUS_IS_CLIENT_ERROR(status)
+#define E2K_HTTP_BAD_REQUEST                       400
+#define E2K_HTTP_UNAUTHORIZED                      401
+#define E2K_HTTP_FORBIDDEN                         403
+#define E2K_HTTP_NOT_FOUND                         404
+#define E2K_HTTP_METHOD_NOT_ALLOWED                405
+#define E2K_HTTP_CONFLICT                          409
+#define E2K_HTTP_PRECONDITION_FAILED               412
+#define E2K_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE   416
+#define E2K_HTTP_UNPROCESSABLE_ENTITY              422
+#define E2K_HTTP_LOCKED                            423
+#define E2K_HTTP_INSUFFICIENT_SPACE_ON_RESOURCE    425
+#define E2K_HTTP_TIMEOUT                           440
+
+#define E2K_HTTP_STATUS_IS_SERVER_ERROR(status)    SOUP_STATUS_IS_SERVER_ERROR(status)
+#define E2K_HTTP_INTERNAL_SERVER_ERROR             500
+#define E2K_HTTP_BAD_GATEWAY                       502
+
+G_END_DECLS
+
+#endif /* __E2K_HTTP_UTILS_H__ */
diff --git a/server/lib/e2k-kerberos.c b/server/lib/e2k-kerberos.c
new file mode 100644
index 0000000..bf1a99e
--- /dev/null
+++ b/server/lib/e2k-kerberos.c
@@ -0,0 +1,183 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "e2k-kerberos.h"
+#include <krb5.h>
+
+static krb5_context
+e2k_kerberos_context_new (const gchar *domain)
+{
+	krb5_context ctx;
+	gchar *realm;
+
+	if (krb5_init_context (&ctx) != 0)
+		return NULL;
+
+	realm = g_ascii_strup (domain, strlen (domain));
+	krb5_set_default_realm (ctx, realm);
+	g_free (realm);
+
+	return ctx;
+}
+
+static E2kKerberosResult
+krb5_result_to_e2k_kerberos_result (gint result)
+{
+	switch (result) {
+	case 0:
+		return E2K_KERBEROS_OK;
+
+	case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
+		return E2K_KERBEROS_USER_UNKNOWN;
+
+	case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+	case KRB5KDC_ERR_PREAUTH_FAILED:
+		return E2K_KERBEROS_PASSWORD_INCORRECT;
+
+	case KRB5KDC_ERR_KEY_EXP:
+		return E2K_KERBEROS_PASSWORD_EXPIRED;
+
+	case KRB5_KDC_UNREACH:
+		return E2K_KERBEROS_KDC_UNREACHABLE;
+
+	case KRB5KRB_AP_ERR_SKEW:
+		return E2K_KERBEROS_TIME_SKEW;
+
+	default:
+		g_warning ("Unexpected kerberos error %d", result);
+		return E2K_KERBEROS_FAILED;
+	}
+}
+
+static E2kKerberosResult
+get_init_cred (krb5_context ctx, const gchar *usr_name, const gchar *passwd,
+	       const gchar *in_tkt_service, krb5_creds *cred)
+{
+	krb5_principal principal;
+	krb5_get_init_creds_opt opt;
+	krb5_error_code result;
+
+	result = krb5_parse_name (ctx, usr_name, &principal);
+	if (result)
+		return E2K_KERBEROS_USER_UNKNOWN;
+
+	krb5_get_init_creds_opt_init (&opt);
+	krb5_get_init_creds_opt_set_tkt_life (&opt, 5*60);
+	krb5_get_init_creds_opt_set_renew_life (&opt, 0);
+	krb5_get_init_creds_opt_set_forwardable (&opt, 0);
+	krb5_get_init_creds_opt_set_proxiable (&opt, 0);
+
+	result = krb5_get_init_creds_password (ctx, cred, principal,
+					       (gchar *) passwd,
+					       NULL, NULL, 0,
+					       (gchar *) in_tkt_service, &opt);
+	krb5_free_principal (ctx, principal);
+
+	return krb5_result_to_e2k_kerberos_result (result);
+}
+
+/**
+ * e2k_kerberos_change_password
+ * @user: username
+ * @domain: Windows (2000) domain name
+ * @old_password: currrent password
+ * @new_password: password to be changed to
+ *
+ * Changes the password for the given user
+ *
+ * Return value: an #E2kKerberosResult
+ **/
+E2kKerberosResult
+e2k_kerberos_change_password (const gchar *user, const gchar *domain,
+			      const gchar *old_password, const gchar *new_password)
+{
+	krb5_context ctx;
+	krb5_creds creds;
+	krb5_data res_code_string, res_string;
+	E2kKerberosResult result;
+	gint res_code;
+
+	ctx = e2k_kerberos_context_new (domain);
+	if (!ctx)
+		return E2K_KERBEROS_FAILED;
+
+	result = get_init_cred (ctx, user, old_password,
+				"kadmin/changepw", &creds);
+	if (result != E2K_KERBEROS_OK) {
+		krb5_free_context (ctx);
+		return result;
+	}
+
+	result = krb5_change_password (ctx, &creds, (gchar *)new_password,
+				       &res_code, &res_code_string, &res_string);
+	krb5_free_cred_contents (ctx, &creds);
+	krb5_free_data_contents (ctx, &res_code_string);
+	krb5_free_data_contents (ctx, &res_string);
+	krb5_free_context (ctx);
+
+	if (result != 0)
+		return krb5_result_to_e2k_kerberos_result (result);
+	else if (res_code != 0)
+		return E2K_KERBEROS_FAILED;
+	else
+		return E2K_KERBEROS_OK;
+}
+
+/**
+ * e2k_kerberos_check_password:
+ * @user: username
+ * @domain: Windows (2000) domain name
+ * @password: current password
+ *
+ * Checks if the password is valid, invalid, or expired
+ *
+ * Return value: %E2K_KERBEROS_OK, %E2K_KERBEROS_USER_UNKNOWN,
+ * %E2K_KERBEROS_PASSWORD_INCORRECT, %E2K_KERBEROS_PASSWORD_EXPIRED,
+ * or %E2K_KERBEROS_FAILED (for unknown errors)
+ **/
+E2kKerberosResult
+e2k_kerberos_check_password (const gchar *user, const gchar *domain,
+			     const gchar *password)
+{
+	krb5_context ctx;
+	krb5_creds creds;
+	E2kKerberosResult result;
+
+	ctx = e2k_kerberos_context_new (domain);
+	if (!ctx)
+		return E2K_KERBEROS_FAILED;
+
+	result = get_init_cred (ctx, user, password, NULL, &creds);
+
+	krb5_free_context (ctx);
+	if (result == E2K_KERBEROS_OK)
+		krb5_free_cred_contents (ctx, &creds);
+
+	return result;
+}
diff --git a/server/lib/e2k-kerberos.h b/server/lib/e2k-kerberos.h
new file mode 100644
index 0000000..fcb3229
--- /dev/null
+++ b/server/lib/e2k-kerberos.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2004 Novell, Inc. */
+
+#ifndef __E2K_KERBEROS_H__
+#define __E2K_KERBEROS_H__
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E2K_KERBEROS_OK,
+	E2K_KERBEROS_USER_UNKNOWN,
+	E2K_KERBEROS_PASSWORD_INCORRECT,
+	E2K_KERBEROS_PASSWORD_EXPIRED,
+	E2K_KERBEROS_PASSWORD_TOO_WEAK,
+
+	E2K_KERBEROS_KDC_UNREACHABLE,
+	E2K_KERBEROS_TIME_SKEW,
+
+	E2K_KERBEROS_FAILED
+} E2kKerberosResult;
+
+E2kKerberosResult e2k_kerberos_check_password  (const gchar *user,
+						const gchar *domain,
+						const gchar *password);
+
+E2kKerberosResult e2k_kerberos_change_password (const gchar *user,
+						const gchar *domain,
+						const gchar *old_password,
+						const gchar *new_password);
+
+G_END_DECLS
+
+#endif /* __E2K_FREEBUSY_H__ */
diff --git a/server/lib/e2k-marshal.c b/server/lib/e2k-marshal.c
new file mode 100644
index 0000000..44c1405
--- /dev/null
+++ b/server/lib/e2k-marshal.c
@@ -0,0 +1,126 @@
+#include "e2k-marshal.h"
+
+#include	<glib-object.h>
+
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v)  g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v)     g_value_get_char (v)
+#define g_marshal_value_peek_uchar(v)    g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v)      g_value_get_int (v)
+#define g_marshal_value_peek_uint(v)     g_value_get_uint (v)
+#define g_marshal_value_peek_long(v)     g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v)    g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v)    g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v)   g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v)     g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v)    g_value_get_flags (v)
+#define g_marshal_value_peek_float(v)    g_value_get_float (v)
+#define g_marshal_value_peek_double(v)   g_value_get_double (v)
+#define g_marshal_value_peek_string(v)   (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v)    g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v)    g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v)  g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v)   g_value_get_object (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ *          Do not access GValues directly in your code. Instead, use the
+ *          g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v)  (v)->data[0].v_int
+#define g_marshal_value_peek_char(v)     (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v)    (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v)      (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v)     (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v)    (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v)   (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v)    (v)->data[0].v_float
+#define g_marshal_value_peek_double(v)   (v)->data[0].v_double
+#define g_marshal_value_peek_string(v)   (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v)  (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v)   (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+
+/* NONE:INT,INT (./e2k-marshal.list:1) */
+void
+e2k_marshal_VOID__INT_INT (GClosure     *closure,
+                           GValue       *return_value G_GNUC_UNUSED,
+                           guint         n_param_values,
+                           const GValue *param_values,
+                           gpointer      invocation_hint G_GNUC_UNUSED,
+                           gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__INT_INT) (gpointer     data1,
+                                              gint         arg_1,
+                                              gint         arg_2,
+                                              gpointer     data2);
+  register GMarshalFunc_VOID__INT_INT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__INT_INT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_int (param_values + 1),
+            g_marshal_value_peek_int (param_values + 2),
+            data2);
+}
+
+/* NONE:INT,STRING,STRING (./e2k-marshal.list:2) */
+void
+e2k_marshal_VOID__INT_STRING_STRING (GClosure     *closure,
+                                     GValue       *return_value G_GNUC_UNUSED,
+                                     guint         n_param_values,
+                                     const GValue *param_values,
+                                     gpointer      invocation_hint G_GNUC_UNUSED,
+                                     gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__INT_STRING_STRING) (gpointer     data1,
+                                                        gint         arg_1,
+                                                        gpointer     arg_2,
+                                                        gpointer     arg_3,
+                                                        gpointer     data2);
+  register GMarshalFunc_VOID__INT_STRING_STRING callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 4);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__INT_STRING_STRING) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_int (param_values + 1),
+            g_marshal_value_peek_string (param_values + 2),
+            g_marshal_value_peek_string (param_values + 3),
+            data2);
+}
+
diff --git a/server/lib/e2k-marshal.h b/server/lib/e2k-marshal.h
new file mode 100644
index 0000000..6b71df9
--- /dev/null
+++ b/server/lib/e2k-marshal.h
@@ -0,0 +1,30 @@
+
+#ifndef __e2k_marshal_MARSHAL_H__
+#define __e2k_marshal_MARSHAL_H__
+
+#include	<glib-object.h>
+
+G_BEGIN_DECLS
+
+/* NONE:INT,INT (./e2k-marshal.list:1) */
+extern void e2k_marshal_VOID__INT_INT (GClosure     *closure,
+                                       GValue       *return_value,
+                                       guint         n_param_values,
+                                       const GValue *param_values,
+                                       gpointer      invocation_hint,
+                                       gpointer      marshal_data);
+#define e2k_marshal_NONE__INT_INT	e2k_marshal_VOID__INT_INT
+
+/* NONE:INT,STRING,STRING (./e2k-marshal.list:2) */
+extern void e2k_marshal_VOID__INT_STRING_STRING (GClosure     *closure,
+                                                 GValue       *return_value,
+                                                 guint         n_param_values,
+                                                 const GValue *param_values,
+                                                 gpointer      invocation_hint,
+                                                 gpointer      marshal_data);
+#define e2k_marshal_NONE__INT_STRING_STRING	e2k_marshal_VOID__INT_STRING_STRING
+
+G_END_DECLS
+
+#endif /* __e2k_marshal_MARSHAL_H__ */
+
diff --git a/server/lib/e2k-marshal.list b/server/lib/e2k-marshal.list
new file mode 100644
index 0000000..468749b
--- /dev/null
+++ b/server/lib/e2k-marshal.list
@@ -0,0 +1,2 @@
+NONE:INT,INT
+NONE:INT,STRING,STRING
diff --git a/server/lib/e2k-operation.c b/server/lib/e2k-operation.c
new file mode 100644
index 0000000..4046773
--- /dev/null
+++ b/server/lib/e2k-operation.c
@@ -0,0 +1,171 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2003, 2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* e2k-operation.c: Cancellable operations */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "e2k-operation.h"
+
+static GStaticMutex op_mutex = G_STATIC_MUTEX_INIT;
+static GHashTable *active_ops = NULL;
+
+/**
+ * e2k_operation_init:
+ * @op: an #E2kOperation
+ *
+ * This initializes the #E2kOperation pointed to by @op.
+ * This should be called before passing @op to a cancellable function.
+ **/
+void
+e2k_operation_init (E2kOperation *op)
+{
+	g_return_if_fail (op != NULL);
+
+	memset (op, 0, sizeof (E2kOperation));
+
+	g_static_mutex_lock (&op_mutex);
+	if (!active_ops)
+		active_ops = g_hash_table_new (NULL, NULL);
+	g_hash_table_insert (active_ops, op, op);
+	g_static_mutex_unlock (&op_mutex);
+}
+
+/**
+ * e2k_operation_free:
+ * @op: an #E2kOperation
+ *
+ * This frees @op and removes it from the list of active operations.
+ * It should be called after the function it was passed to returns.
+ **/
+void
+e2k_operation_free (E2kOperation *op)
+{
+	g_return_if_fail (op != NULL);
+
+	g_static_mutex_lock (&op_mutex);
+	g_hash_table_remove (active_ops, op);
+	g_static_mutex_unlock (&op_mutex);
+}
+
+/**
+ * e2k_operation_start:
+ * @op: an #E2kOperation, or %NULL
+ * @canceller: the callback to invoke if @op is cancelled
+ * @owner: object that owns the operation
+ * @data: data to pass to @canceller
+ *
+ * This starts a single cancellable operation using @op. If @op has
+ * already been cancelled, this will invoke @canceller immediately.
+ *
+ * (If @op is %NULL, e2k_operation_start() is a no-op.)
+ **/
+void
+e2k_operation_start (E2kOperation *op,
+		     E2kOperationCancelFunc canceller,
+		     gpointer owner,
+		     gpointer data)
+{
+	if (!op)
+		return;
+
+	g_static_mutex_lock (&op_mutex);
+
+	op->canceller = canceller;
+	op->owner = owner;
+	op->data = data;
+
+	if (op->cancelled && op->canceller) {
+		g_static_mutex_unlock (&op_mutex);
+		op->canceller (op, op->owner, op->data);
+		return;
+	}
+
+	g_static_mutex_unlock (&op_mutex);
+}
+
+/**
+ * e2k_operation_finish:
+ * @op: an #E2kOperation, or %NULL
+ *
+ * This finishes the current cancellable operation on @op. Attempting
+ * to cancel @op after this point will have no effect until another
+ * operation is started on it.
+ *
+ * (If @op is %NULL, e2k_operation_finish() is a no-op.)
+ **/
+void
+e2k_operation_finish (E2kOperation *op)
+{
+	if (!op)
+		return;
+
+	g_static_mutex_lock (&op_mutex);
+	op->canceller = NULL;
+	op->owner = NULL;
+	op->data = NULL;
+	g_static_mutex_unlock (&op_mutex);
+}
+
+/**
+ * e2k_operation_cancel:
+ * @op: an #E2kOperation
+ *
+ * This cancels @op, invoking its cancellation callback. If @op is not
+ * an active operation, or has already been cancelled, this has no
+ * effect.
+ **/
+void
+e2k_operation_cancel (E2kOperation *op)
+{
+	g_return_if_fail (op != NULL);
+
+	g_static_mutex_lock (&op_mutex);
+
+	if (!g_hash_table_lookup (active_ops, op) || op->cancelled) {
+		g_static_mutex_unlock (&op_mutex);
+		return;
+	}
+
+	g_hash_table_remove (active_ops, op);
+	op->cancelled = TRUE;
+	g_static_mutex_unlock (&op_mutex);
+
+	if (op->canceller)
+		op->canceller (op, op->owner, op->data);
+}
+
+/**
+ * e2k_operation_is_cancelled:
+ * @op: an #E2kOperation (or %NULL)
+ *
+ * Checks if @op has been cancelled. Should only be called while @op
+ * is active.
+ *
+ * Return value: whether or not @op has been cancelled.
+ **/
+gboolean
+e2k_operation_is_cancelled (E2kOperation *op)
+{
+	return op && op->cancelled;
+}
diff --git a/server/lib/e2k-operation.h b/server/lib/e2k-operation.h
new file mode 100644
index 0000000..51552ed
--- /dev/null
+++ b/server/lib/e2k-operation.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2003, 2004 Novell, Inc. */
+
+#ifndef __E2K_OPERATION_H__
+#define __E2K_OPERATION_H__
+
+#include <string.h>
+#include "e2k-types.h"
+
+G_BEGIN_DECLS
+
+typedef void (*E2kOperationCancelFunc) (E2kOperation *op, gpointer owner, gpointer data);
+
+struct _E2kOperation {
+	/*< private >*/
+	gboolean cancelled;
+
+	E2kOperationCancelFunc canceller;
+	gpointer owner, data;
+};
+
+/* These are called by the caller of the cancellable function. */
+void     e2k_operation_init           (E2kOperation           *op);
+void     e2k_operation_free           (E2kOperation           *op);
+
+/* These are called by the cancellable function itself. */
+void     e2k_operation_start          (E2kOperation           *op,
+				       E2kOperationCancelFunc  canceller,
+				       gpointer                owner,
+				       gpointer                data);
+void     e2k_operation_finish         (E2kOperation           *op);
+
+void     e2k_operation_cancel         (E2kOperation           *op);
+gboolean e2k_operation_is_cancelled   (E2kOperation           *op);
+
+G_END_DECLS
+
+#endif /* __E2k_OPERATION_H__ */
diff --git a/server/lib/e2k-path.c b/server/lib/e2k-path.c
new file mode 100644
index 0000000..188c905
--- /dev/null
+++ b/server/lib/e2k-path.c
@@ -0,0 +1,251 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-path.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "e2k-path.h"
+
+#define SUBFOLDER_DIR_NAME     "subfolders"
+#define SUBFOLDER_DIR_NAME_LEN 10
+
+/**
+ * e_path_to_physical:
+ * @prefix: a prefix to prepend to the path, or %NULL
+ * @path: the virtual path to convert to a filesystem path.
+ *
+ * This converts the "virtual" path @path into an expanded form that
+ * allows a given name to refer to both a file and a directory. The
+ * expanded path will have a "subfolders" directory inserted between
+ * each path component. If the path ends with "/", the returned
+ * physical path will end with "/subfolders"
+ *
+ * If @prefix is non-%NULL, it will be prepended to the returned path.
+ *
+ * Return value: the expanded path
+ **/
+gchar *
+e_path_to_physical (const gchar *prefix, const gchar *vpath)
+{
+	const gchar *p, *newp;
+	gchar *dp;
+	gchar *ppath;
+	gint ppath_len;
+	gint prefix_len;
+
+	while (*vpath == '/')
+		vpath++;
+	if (!prefix)
+		prefix = "";
+
+	/* Calculate the length of the real path. */
+	ppath_len = strlen (vpath);
+	ppath_len++;	/* For the ending zero.  */
+
+	prefix_len = strlen (prefix);
+	ppath_len += prefix_len;
+	ppath_len++;	/* For the separating slash.  */
+
+	/* Take account of the fact that we need to translate every
+	 * separator into `subfolders/'.
+	 */
+	p = vpath;
+	while (1) {
+		newp = strchr (p, '/');
+		if (newp == NULL)
+			break;
+
+		ppath_len += SUBFOLDER_DIR_NAME_LEN;
+		ppath_len++; /* For the separating slash.  */
+
+		/* Skip consecutive slashes.  */
+		while (*newp == '/')
+			newp++;
+
+		p = newp;
+	};
+
+	ppath = g_malloc (ppath_len);
+	dp = ppath;
+
+	memcpy (dp, prefix, prefix_len);
+	dp += prefix_len;
+	*(dp++) = '/';
+
+	/* Copy the mangled path.  */
+	p = vpath;
+	while (1) {
+		newp = strchr (p, '/');
+		if (newp == NULL) {
+			strcpy (dp, p);
+			break;
+		}
+
+		memcpy (dp, p, newp - p + 1); /* `+ 1' to copy the slash too.  */
+		dp += newp - p + 1;
+
+		memcpy (dp, SUBFOLDER_DIR_NAME, SUBFOLDER_DIR_NAME_LEN);
+		dp += SUBFOLDER_DIR_NAME_LEN;
+
+		*(dp++) = '/';
+
+		/* Skip consecutive slashes.  */
+		while (*newp == '/')
+			newp++;
+
+		p = newp;
+	}
+
+	return ppath;
+}
+
+static gboolean
+find_folders_recursive (const gchar *physical_path, const gchar *path,
+			EPathFindFoldersCallback callback, gpointer data)
+{
+	GDir *dir;
+	gchar *subfolder_directory_path;
+	gboolean ok;
+
+	if (*path) {
+		if (!callback (physical_path, path, data))
+			return FALSE;
+
+		subfolder_directory_path = g_strdup_printf ("%s/%s", physical_path, SUBFOLDER_DIR_NAME);
+	} else {
+		/* On the top level, we have no folders and,
+		 * consequently, no subfolder directory.
+		 */
+
+		subfolder_directory_path = g_strdup (physical_path);
+	}
+
+	/* Now scan the subfolders and load them. */
+	dir = g_dir_open (subfolder_directory_path, 0, NULL);
+	if (dir == NULL) {
+		g_free (subfolder_directory_path);
+		return TRUE;
+	}
+
+	ok = TRUE;
+	while (ok) {
+		struct stat file_stat;
+		const gchar *dirent;
+		gchar *file_path;
+		gchar *new_path;
+
+		dirent = g_dir_read_name (dir);
+		if (dirent == NULL)
+			break;
+
+		file_path = g_strdup_printf ("%s/%s", subfolder_directory_path,
+					     dirent);
+
+		if (g_stat (file_path, &file_stat) < 0 ||
+		    ! S_ISDIR (file_stat.st_mode)) {
+			g_free (file_path);
+			continue;
+		}
+
+		new_path = g_strdup_printf ("%s/%s", path, dirent);
+
+		ok = find_folders_recursive (file_path, new_path, callback, data);
+
+		g_free (file_path);
+		g_free (new_path);
+	}
+
+	g_dir_close (dir);
+	g_free (subfolder_directory_path);
+
+	return ok;
+}
+
+/**
+ * e_path_find_folders:
+ * @prefix: directory to start from
+ * @callback: Callback to invoke on each folder
+ * @data: Data for @callback
+ *
+ * Walks the folder tree starting at @prefix and calls @callback
+ * on each folder.
+ *
+ * Return value: %TRUE on success, %FALSE if an error occurs at any point
+ **/
+gboolean
+e_path_find_folders (const gchar *prefix,
+		     EPathFindFoldersCallback callback,
+		     gpointer data)
+{
+	return find_folders_recursive (prefix, "", callback, data);
+}
+
+/**
+ * e_path_rmdir:
+ * @prefix: a prefix to prepend to the path, or %NULL
+ * @path: the virtual path to convert to a filesystem path.
+ *
+ * This removes the directory pointed to by @prefix and @path
+ * and attempts to remove its parent "subfolders" directory too
+ * if it's empty.
+ *
+ * Return value: -1 (with errno set) if it failed to rmdir the
+ * specified directory. 0 otherwise, whether or not it removed
+ * the parent directory.
+ **/
+gint
+e_path_rmdir (const gchar *prefix, const gchar *vpath)
+{
+	gchar *physical_path, *p;
+
+	/* Remove the directory itself */
+	physical_path = e_path_to_physical (prefix, vpath);
+	if (g_rmdir (physical_path) == -1) {
+		g_free (physical_path);
+		return -1;
+	}
+
+	/* Attempt to remove its parent "subfolders" directory,
+	 * ignoring errors since it might not be empty.
+	 */
+
+	p = strrchr (physical_path, '/');
+	if (p[1] == '\0') {
+		g_free (physical_path);
+		return 0;
+	}
+	*p = '\0';
+	p = strrchr (physical_path, '/');
+	if (!p || strcmp (p + 1, SUBFOLDER_DIR_NAME) != 0) {
+		g_free (physical_path);
+		return 0;
+	}
+
+	g_rmdir (physical_path);
+	g_free (physical_path);
+	return 0;
+}
diff --git a/server/lib/e2k-path.h b/server/lib/e2k-path.h
new file mode 100644
index 0000000..c6d43c6
--- /dev/null
+++ b/server/lib/e2k-path.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __E_PATH__
+#define __E_PATH__
+
+#include <glib.h>
+
+/* FIXME: deprecated
+   This is used exclusively for the legacy imap cache code.  DO NOT use this in any new code */
+
+typedef gboolean (*EPathFindFoldersCallback) (const gchar *physical_path,
+					      const gchar *path,
+					      gpointer user_data);
+
+gchar *   e_path_to_physical  (const gchar *prefix, const gchar *vpath);
+
+gboolean e_path_find_folders (const gchar *prefix,
+			      EPathFindFoldersCallback callback,
+			      gpointer data);
+
+gint      e_path_rmdir        (const gchar *prefix, const gchar *vpath);
+#endif /* __E_PATH__ */
diff --git a/server/lib/e2k-properties.c b/server/lib/e2k-properties.c
new file mode 100644
index 0000000..44ddfa4
--- /dev/null
+++ b/server/lib/e2k-properties.c
@@ -0,0 +1,798 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-properties.h"
+#include "e2k-propnames.h"
+#include "e2k-utils.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libxml/xmlmemory.h>
+
+struct E2kProperties {
+	GHashTable *set, *removed;
+};
+
+typedef struct {
+	gchar *name;
+	const gchar *namespace;
+	const gchar *short_name;
+
+	E2kPropType type;
+	guint32 proptag;
+} E2kPropInfo;
+
+static GHashTable *known_properties;
+static GStaticMutex known_properties_lock = G_STATIC_MUTEX_INIT;
+
+/**
+ * e2k_properties_new:
+ *
+ * Creates a new (empty) #E2kProperties structure
+ *
+ * Return value: the structure
+ **/
+E2kProperties *
+e2k_properties_new (void)
+{
+	E2kProperties *props;
+
+	props = g_new0 (E2kProperties, 1);
+	props->set = g_hash_table_new (g_str_hash, g_str_equal);
+	props->removed = g_hash_table_new (g_str_hash, g_str_equal);
+
+	return props;
+}
+
+static void
+copy_prop (gpointer key, gpointer value, gpointer data)
+{
+	const gchar *name = key;
+	GHashTable *props_copy = data;
+	gpointer value_copy;
+	E2kPropInfo *pi;
+
+	g_static_mutex_lock (&known_properties_lock);
+	pi = g_hash_table_lookup (known_properties, name);
+	g_static_mutex_unlock (&known_properties_lock);
+
+	switch (pi->type) {
+	case E2K_PROP_TYPE_BINARY_ARRAY:
+	{
+		GPtrArray *orig = value, *copy;
+		GByteArray *new, *old;
+		gint i;
+
+		copy = g_ptr_array_new ();
+		for (i = 0; i < orig->len; i++) {
+			old = orig->pdata[i];
+			new = g_byte_array_new ();
+			g_byte_array_append (new, old->data, old->len);
+			g_ptr_array_add (copy, new);
+		}
+		value_copy = copy;
+		break;
+	}
+
+	case E2K_PROP_TYPE_STRING_ARRAY:
+	{
+		GPtrArray *orig = value, *copy;
+		gint i;
+
+		copy = g_ptr_array_new ();
+		for (i = 0; i < orig->len; i++)
+			g_ptr_array_add (copy, g_strdup (orig->pdata[i]));
+		value_copy = copy;
+		break;
+	}
+
+	case E2K_PROP_TYPE_BINARY:
+	{
+		GByteArray *orig = value, *copy;
+
+		copy = g_byte_array_new ();
+		g_byte_array_append (copy, orig->data, orig->len);
+		value_copy = copy;
+		break;
+	}
+
+	case E2K_PROP_TYPE_XML:
+		value_copy = xmlCopyNode (value, TRUE);
+		break;
+
+	case E2K_PROP_TYPE_STRING:
+	default:
+		value_copy = g_strdup (value);
+		break;
+	}
+
+	g_hash_table_insert (props_copy, pi->name, value_copy);
+}
+
+/**
+ * e2k_properties_copy:
+ * @props: an #E2kProperties
+ *
+ * Performs a deep copy of @props
+ *
+ * Return value: a new copy of @props
+ **/
+E2kProperties *
+e2k_properties_copy (E2kProperties *props)
+{
+	E2kProperties *copy;
+
+	g_return_val_if_fail (props != NULL, NULL);
+
+	copy = e2k_properties_new ();
+	g_hash_table_foreach (props->set, copy_prop, copy->set);
+	g_hash_table_foreach (props->removed, copy_prop, copy->removed);
+	return copy;
+}
+
+static void
+free_prop (E2kPropInfo *pi, gpointer value)
+{
+	if (!value)
+		return;
+
+	switch (pi->type) {
+	case E2K_PROP_TYPE_BINARY_ARRAY:
+	{
+		GPtrArray *array = value;
+		gint i;
+
+		for (i = 0; i < array->len; i++)
+			g_byte_array_free (array->pdata[i], TRUE);
+		g_ptr_array_free (array, TRUE);
+		break;
+	}
+
+	case E2K_PROP_TYPE_STRING_ARRAY:
+	case E2K_PROP_TYPE_INT_ARRAY:
+	{
+		GPtrArray *array = value;
+		gint i;
+
+		for (i = 0; i < array->len; i++)
+			g_free (array->pdata[i]);
+		g_ptr_array_free (array, TRUE);
+		break;
+	}
+
+	case E2K_PROP_TYPE_BINARY:
+		g_byte_array_free (value, TRUE);
+		break;
+
+	case E2K_PROP_TYPE_XML:
+		xmlFreeNode (value);
+		break;
+
+	case E2K_PROP_TYPE_STRING:
+	default:
+		g_free (value);
+		break;
+	}
+}
+
+static void
+properties_free_cb (gpointer key, gpointer value, gpointer data)
+{
+	E2kPropInfo *pi;
+
+	g_static_mutex_lock (&known_properties_lock);
+	pi = g_hash_table_lookup (known_properties, key);
+	g_static_mutex_unlock (&known_properties_lock);
+	if (pi)
+		free_prop (pi, value);
+}
+
+/**
+ * e2k_properties_free:
+ * @props: an #E2kProperties
+ *
+ * Frees @props and all of the properties it contains.
+ **/
+void
+e2k_properties_free (E2kProperties *props)
+{
+	g_return_if_fail (props != NULL);
+
+	g_hash_table_foreach (props->set, properties_free_cb, NULL);
+	g_hash_table_destroy (props->set);
+	g_hash_table_destroy (props->removed);
+	g_free (props);
+}
+
+/**
+ * e2k_properties_get_prop:
+ * @props: an #E2kProperties
+ * @propname: a property name
+ *
+ * Retrieves the value of @propname in @props.
+ *
+ * Return value: the value of @propname in @props, or %NULL if it is
+ * not set. The caller should not free the value; it is owned by
+ * @props.
+ **/
+gpointer
+e2k_properties_get_prop (E2kProperties *props, const gchar *propname)
+{
+	g_return_val_if_fail (props != NULL, NULL);
+
+	return g_hash_table_lookup (props->set, propname);
+}
+
+/**
+ * e2k_properties_empty:
+ * @props: an #E2kProperties
+ *
+ * Tests if @props is empty.
+ *
+ * Return value: %TRUE if @props has no properties set, %FALSE if it
+ * has at least one value set.
+ **/
+gboolean
+e2k_properties_empty (E2kProperties *props)
+{
+	g_return_val_if_fail (props != NULL, TRUE);
+
+	return g_hash_table_size (props->set) == 0;
+}
+
+extern gchar e2k_des_key[8];
+
+static E2kPropInfo *
+get_propinfo (const gchar *propname, E2kPropType type)
+{
+	E2kPropInfo *pi;
+
+	g_static_mutex_lock (&known_properties_lock);
+	if (!known_properties)
+		known_properties = g_hash_table_new (g_str_hash, g_str_equal);
+
+	pi = g_hash_table_lookup (known_properties, propname);
+	if (pi) {
+		if (pi->type == E2K_PROP_TYPE_UNKNOWN)
+			pi->type = type;
+		g_static_mutex_unlock (&known_properties_lock);
+		return pi;
+	}
+
+	pi = g_new (E2kPropInfo, 1);
+	pi->name = g_strdup (propname);
+	pi->namespace = e2k_prop_namespace_name (pi->name);
+	pi->short_name = e2k_prop_property_name (pi->name);
+	pi->type = type;
+
+	if (pi->short_name[0] == 'x')
+		pi->proptag = strtoul (pi->short_name + 1, NULL, 16);
+	else
+		pi->proptag = 0;
+
+	g_hash_table_insert (known_properties, pi->name, pi);
+
+	g_static_mutex_unlock (&known_properties_lock);
+
+	return pi;
+}
+
+/**
+ * e2k_properties_set_string:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: an allocated string
+ *
+ * Sets @propname in @props to @value. @props assumes ownership of
+ * @value.
+ **/
+
+/**
+ * e2k_properties_set_string_array:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: an array of allocated strings
+ *
+ * Sets @propname in @props to @value. @props assumes ownership of
+ * @value.
+ **/
+
+/**
+ * e2k_properties_set_binary:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: a byte array
+ *
+ * Sets @propname in @props to @value. @props assumes ownership of
+ * @value.
+ **/
+
+/**
+ * e2k_properties_set_binary_array:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: an array of byte arrays
+ *
+ * Sets @propname in @props to @value. @props assumes ownership of
+ * @value.
+ **/
+
+/**
+ * e2k_properties_set_xml:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: an #xmlNode
+ *
+ * Sets @propname in @props to @value. @props assumes ownership of
+ * @value.
+ **/
+
+/**
+ * e2k_properties_set_int:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: an integer
+ *
+ * Sets @propname in @props to @value.
+ **/
+
+/**
+ * e2k_properties_set_int_array:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: an array of integers
+ *
+ * Sets @propname in @props to @value. @props assumes ownership of
+ * @value.
+ **/
+
+/**
+ * e2k_properties_set_float:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: a floating-point value
+ *
+ * Sets @propname in @props to @value.
+ **/
+
+/**
+ * e2k_properties_set_bool:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: a boolean value
+ *
+ * Sets @propname in @props to @value.
+ **/
+
+/**
+ * e2k_properties_set_date:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @value: an allocated string containing an Exchange timestamp
+ *
+ * Sets @propname in @props to @value. @props assumes ownership of
+ * @value.
+ **/
+
+#define E2K_PROPERTIES_SETTER(fname, valuetype, pitype, data)		\
+void									\
+e2k_properties_set_ ## fname (E2kProperties *props,			\
+			      const gchar    *propname,			\
+			      valuetype      value)			\
+{									\
+	E2kPropInfo *pi;						\
+									\
+	pi = get_propinfo (propname, E2K_PROP_TYPE_ ## pitype);		\
+	free_prop (pi, g_hash_table_lookup (props->set, pi->name));	\
+	g_hash_table_insert (props->set, pi->name, data);		\
+	g_hash_table_remove (props->removed, pi->name);			\
+}
+
+E2K_PROPERTIES_SETTER (string, gchar *, STRING, value)
+E2K_PROPERTIES_SETTER (string_array, GPtrArray *, STRING_ARRAY, value)
+E2K_PROPERTIES_SETTER (binary, GByteArray *, BINARY, value)
+E2K_PROPERTIES_SETTER (binary_array, GPtrArray *, BINARY_ARRAY, value)
+E2K_PROPERTIES_SETTER (xml, xmlNode *, XML, value)
+
+E2K_PROPERTIES_SETTER (int, int, INT, g_strdup_printf ("%d", value))
+E2K_PROPERTIES_SETTER (int_array, GPtrArray *, INT_ARRAY, value)
+E2K_PROPERTIES_SETTER (float, float, FLOAT, g_strdup_printf ("%f", value))
+E2K_PROPERTIES_SETTER (bool, gboolean, BOOL, g_strdup_printf ("%d", value != FALSE))
+E2K_PROPERTIES_SETTER (date, gchar *, DATE, value)
+
+/**
+ * e2k_properties_set_type_as_string:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @type: the type of @value
+ * @value: an allocated string
+ *
+ * Sets @propname in @props to @value, but with type @type. @props
+ * assumes ownership of @value.
+ **/
+
+/**
+ * e2k_properties_set_type_as_string_array:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ * @type: the type of @value
+ * @value: an array of allocated strings
+ *
+ * Sets @propname in @props to @value, but with type @type. @props
+ * assumes ownership of @value.
+ **/
+
+#define E2K_PROPERTIES_SETTER_AS(fname, valuetype)			\
+void									\
+e2k_properties_set_type_as_ ## fname (E2kProperties *props,		\
+				      const gchar    *propname,		\
+				      E2kPropType    type,		\
+				      valuetype      value)		\
+{									\
+	E2kPropInfo *pi;						\
+									\
+	pi = get_propinfo (propname, type);				\
+	free_prop (pi, g_hash_table_lookup (props->set, pi->name));	\
+	g_hash_table_insert (props->set, pi->name, value);		\
+	g_hash_table_remove (props->removed, pi->name);			\
+}
+
+E2K_PROPERTIES_SETTER_AS (string, gchar *)
+E2K_PROPERTIES_SETTER_AS (string_array, GPtrArray *)
+
+/**
+ * e2k_properties_remove:
+ * @props: an #E2kProperties
+ * @propname: the name of a property
+ *
+ * Marks @propname removed in @props, so that the corresponding
+ * property will be removed from the object on the server if @props is
+ * used in a PROPPATCH. If the property was formerly set in @props,
+ * this frees the old value.
+ **/
+void
+e2k_properties_remove (E2kProperties *props, const gchar *propname)
+{
+	E2kPropInfo *pi;
+
+	pi = get_propinfo (propname, E2K_PROP_TYPE_UNKNOWN);
+	free_prop (pi, g_hash_table_lookup (props->set, pi->name));
+	g_hash_table_remove (props->set, pi->name);
+	g_hash_table_insert (props->removed, pi->name, NULL);
+}
+
+struct foreach_data {
+	E2kPropertiesForeachFunc callback;
+	gpointer user_data;
+};
+
+static void
+foreach_callback (gpointer key, gpointer value, gpointer data)
+{
+	struct foreach_data *fd = data;
+	E2kPropInfo *pi;
+
+	g_static_mutex_lock (&known_properties_lock);
+	pi = g_hash_table_lookup (known_properties, key);
+	g_static_mutex_unlock (&known_properties_lock);
+	if (pi)
+		fd->callback (pi->name, pi->type, value, fd->user_data);
+}
+
+/**
+ * e2k_properties_foreach:
+ * @props: an #E2kProperties
+ * @callback: callback function to call for each set property
+ * @user_data: data to pass to @callback
+ *
+ * Calls @callback once for each property that is set in @props (in
+ * unspecified order), passing it the name of the property, the
+ * property's type, its value, and @user_data.
+ **/
+void
+e2k_properties_foreach (E2kProperties *props,
+			E2kPropertiesForeachFunc callback,
+			gpointer user_data)
+{
+	struct foreach_data fd;
+
+	g_return_if_fail (props != NULL);
+
+	fd.callback = callback;
+	fd.user_data = user_data;
+
+	g_hash_table_foreach (props->set, foreach_callback, &fd);
+}
+
+/**
+ * e2k_properties_foreach_removed:
+ * @props: an #E2kProperties
+ * @callback: callback function to call for each set property
+ * @user_data: data to pass to @callback
+ *
+ * Calls @callback once for each property marked removed in @props (in
+ * unspecified order), passing it the name of the property, the
+ * property's type (if known), a %NULL value, and @user_data.
+ **/
+void
+e2k_properties_foreach_removed (E2kProperties *props,
+				E2kPropertiesForeachFunc callback,
+				gpointer user_data)
+{
+	struct foreach_data fd;
+
+	g_return_if_fail (props != NULL);
+
+	fd.callback = callback;
+	fd.user_data = user_data;
+
+	g_hash_table_foreach (props->removed, foreach_callback, &fd);
+}
+
+struct foreach_namespace_data {
+	E2kPropertiesForeachNamespaceFunc callback;
+	gpointer user_data;
+	gboolean need_array_namespace, need_type_namespace;
+	GHashTable *seen_namespaces;
+};
+
+static void
+foreach_namespace_callback (gpointer key, gpointer value, gpointer data)
+{
+	struct foreach_namespace_data *fnd = data;
+	E2kPropInfo *pi;
+	const gchar *name;
+
+	g_static_mutex_lock (&known_properties_lock);
+	pi = g_hash_table_lookup (known_properties, key);
+	g_static_mutex_unlock (&known_properties_lock);
+	if (!pi)
+		return;
+
+	name = e2k_prop_namespace_name (pi->name);
+	if (!g_hash_table_lookup (fnd->seen_namespaces, name)) {
+		g_hash_table_insert (fnd->seen_namespaces,
+				     (gchar *)name, (gchar *)name);
+		fnd->callback (name, e2k_prop_namespace_abbrev (pi->name),
+			       fnd->user_data);
+	}
+
+	switch (pi->type) {
+	case E2K_PROP_TYPE_STRING_ARRAY:
+	case E2K_PROP_TYPE_BINARY_ARRAY:
+	case E2K_PROP_TYPE_INT_ARRAY:
+		fnd->need_array_namespace = TRUE;
+		/* fall through */
+
+	case E2K_PROP_TYPE_BINARY:
+	case E2K_PROP_TYPE_INT:
+	case E2K_PROP_TYPE_BOOL:
+	case E2K_PROP_TYPE_FLOAT:
+	case E2K_PROP_TYPE_DATE:
+		fnd->need_type_namespace = TRUE;
+		break;
+
+	default:
+		break;
+	}
+}
+
+/**
+ * e2k_properties_foreach_namespace:
+ * @props: an #E2kProperties
+ * @callback: callback function to call for each namespace
+ * @user_data: data to pass to @callback
+ *
+ * Calls @callback once for each unique namespace used by the
+ * properties (set or removed) in @props, passing it the name of the
+ * namespace, its standard abbreviation, and @user_data.
+ **/
+void
+e2k_properties_foreach_namespace (E2kProperties *props,
+				  E2kPropertiesForeachNamespaceFunc callback,
+				  gpointer user_data)
+{
+	struct foreach_namespace_data fnd;
+
+	g_return_if_fail (props != NULL);
+
+	fnd.callback = callback;
+	fnd.user_data = user_data;
+	fnd.need_array_namespace = FALSE;
+	fnd.need_type_namespace = FALSE;
+	fnd.seen_namespaces = g_hash_table_new (NULL, NULL);
+
+	g_hash_table_foreach (props->set, foreach_namespace_callback, &fnd);
+	g_hash_table_foreach (props->removed, foreach_namespace_callback, &fnd);
+
+	if (fnd.need_type_namespace)
+		callback (E2K_NS_TYPE, 'T', user_data);
+	if (fnd.need_array_namespace)
+		callback ("xml:", 'X', user_data);
+
+	g_hash_table_destroy (fnd.seen_namespaces);
+}
+
+static GHashTable *namespaces;
+static gint next_namespace = 'a';
+static GStaticMutex namespaces_lock = G_STATIC_MUTEX_INIT;
+
+static const gchar *
+get_div (const gchar *propname)
+{
+	const gchar *div;
+
+	div = strrchr (propname, '/');
+	if (div)
+		return div;
+	return strrchr (propname, ':');
+}
+
+static gint
+prop_equal (gconstpointer v1, gconstpointer v2)
+{
+	const gchar *s1 = (const gchar *)v1, *s2 = (const gchar *)v2;
+	const gchar *d1 = get_div (s1), *d2 = get_div (s2);
+
+	return (d1 - s1 == d2 - s2) && !g_ascii_strncasecmp (s1, s2, d1 - s1);
+}
+
+static guint
+prop_hash (gconstpointer v)
+{
+	const gchar *d = get_div (v);
+	const gchar *p = v;
+	guint h = g_ascii_tolower (*p);
+
+	for (p += 1; p < d; p++)
+		h = (h << 5) - h + *p;
+	return h;
+}
+
+static void
+setup_namespaces (void)
+{
+	namespaces = g_hash_table_new (prop_hash, prop_equal);
+	g_hash_table_insert (namespaces, (gpointer) "DAV", GINT_TO_POINTER ('D'));
+}
+
+/**
+ * e2k_prop_namespace_name:
+ * @prop: the name of a property
+ *
+ * Splits out the namespace portion of @prop
+ *
+ * Return value: the URI of @prop's namespace
+ **/
+const gchar *
+e2k_prop_namespace_name (const gchar *prop)
+{
+	const gchar *div = get_div (prop);
+	gpointer key, value;
+	gchar *name;
+
+	g_static_mutex_lock (&namespaces_lock);
+	if (!namespaces)
+		setup_namespaces ();
+
+	if (g_hash_table_lookup_extended (namespaces, prop, &key, &value)) {
+		g_static_mutex_unlock (&namespaces_lock);
+		return key;
+	}
+
+	name = g_strndup (prop, div - prop + 1);
+	g_hash_table_insert (namespaces, name, GINT_TO_POINTER (next_namespace));
+	next_namespace++;
+	g_static_mutex_unlock (&namespaces_lock);
+	return name;
+}
+
+/**
+ * e2k_prop_namespace_abbrev:
+ * @prop: the name of a property
+ *
+ * Splits out the namespace portion of @prop and assigns a unique
+ * abbreviation for it.
+ *
+ * Return value: the abbreviation used for prop's namespace
+ **/
+gchar
+e2k_prop_namespace_abbrev (const gchar *prop)
+{
+	const gchar *div = get_div (prop);
+	gpointer key, value;
+	gchar *name, res;
+
+	g_static_mutex_lock (&namespaces_lock);
+	if (!namespaces)
+		setup_namespaces ();
+
+	if (g_hash_table_lookup_extended (namespaces, prop, &key, &value)) {
+		g_static_mutex_unlock (&namespaces_lock);
+		return GPOINTER_TO_INT (value);
+	}
+
+	name = g_strndup (prop, div - prop + 1);
+	g_hash_table_insert (namespaces, name, GINT_TO_POINTER (next_namespace));
+	res = next_namespace++;
+	g_static_mutex_unlock (&namespaces_lock);
+
+	return res;
+}
+
+/**
+ * e2k_prop_property_name:
+ * @prop: the name of a property
+ *
+ * Splits out the non-namespace portion of @prop
+ *
+ * Return value: the non-namespaced name of @prop
+ **/
+const gchar *
+e2k_prop_property_name (const gchar *prop)
+{
+	return get_div (prop) + 1;
+}
+
+/**
+ * e2k_prop_proptag:
+ * @prop: the name of a MAPI property
+ *
+ * Computes the MAPI proptag value of @prop, which must be the name
+ * of a MAPI property.
+ *
+ * Return value: the MAPI proptag value
+ **/
+guint32
+e2k_prop_proptag (const gchar *prop)
+{
+	E2kPropInfo *pi;
+
+	pi = get_propinfo (prop, E2K_PROP_TYPE_UNKNOWN);
+	return pi->proptag;
+}
+
+/**
+ * e2k_proptag_prop:
+ * @proptag: a MAPI property
+ *
+ * Computes the WebDAV property name of the property with the
+ * given proptag.
+ *
+ * Return value: the WebDAV property name associated with @proptag
+ **/
+const gchar *
+e2k_proptag_prop (guint32 proptag)
+{
+	E2kPropInfo *pi;
+	gchar *tmpname;
+
+	tmpname = g_strdup_printf (E2K_NS_MAPI_PROPTAG "x%08x",
+				   (unsigned)proptag);
+
+	pi = get_propinfo (tmpname, E2K_PROP_TYPE_UNKNOWN);
+	g_free (tmpname);
+	return pi->name;
+}
diff --git a/server/lib/e2k-properties.h b/server/lib/e2k-properties.h
new file mode 100644
index 0000000..7f8df28
--- /dev/null
+++ b/server/lib/e2k-properties.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_PROPERTIES_H__
+#define __E2K_PROPERTIES_H__
+
+#include <glib.h>
+#include <libxml/tree.h>
+
+typedef struct E2kProperties E2kProperties;
+
+typedef enum {
+	E2K_PROP_TYPE_UNKNOWN,
+
+	E2K_PROP_TYPE_STRING,
+	E2K_PROP_TYPE_BINARY,
+	E2K_PROP_TYPE_STRING_ARRAY,
+	E2K_PROP_TYPE_BINARY_ARRAY,
+	E2K_PROP_TYPE_XML,
+
+	/* These are all stored as STRING or STRING_ARRAY */
+	E2K_PROP_TYPE_INT,
+	E2K_PROP_TYPE_INT_ARRAY,
+	E2K_PROP_TYPE_BOOL,
+	E2K_PROP_TYPE_FLOAT,
+	E2K_PROP_TYPE_DATE
+} E2kPropType;
+
+#define E2K_PROP_TYPE_IS_ARRAY(type) (((type) == E2K_PROP_TYPE_STRING_ARRAY) || ((type) == E2K_PROP_TYPE_BINARY_ARRAY) || ((type) == E2K_PROP_TYPE_INT_ARRAY))
+
+E2kProperties *e2k_properties_new               (void);
+E2kProperties *e2k_properties_copy              (E2kProperties *props);
+void           e2k_properties_free              (E2kProperties *props);
+
+gpointer       e2k_properties_get_prop          (E2kProperties *props,
+						 const gchar    *propname);
+gboolean       e2k_properties_empty             (E2kProperties *props);
+
+void           e2k_properties_set_string        (E2kProperties *props,
+						 const gchar    *propname,
+						 gchar          *value);
+void           e2k_properties_set_string_array  (E2kProperties *props,
+						 const gchar    *propname,
+						 GPtrArray     *value);
+void           e2k_properties_set_binary        (E2kProperties *props,
+						 const gchar    *propname,
+						 GByteArray    *value);
+void           e2k_properties_set_binary_array  (E2kProperties *props,
+						 const gchar    *propname,
+						 GPtrArray     *value);
+void           e2k_properties_set_int           (E2kProperties *props,
+						 const gchar    *propname,
+						 gint            value);
+void           e2k_properties_set_int_array     (E2kProperties *props,
+						 const gchar    *propname,
+						 GPtrArray     *value);
+void           e2k_properties_set_xml           (E2kProperties *props,
+						 const gchar    *propname,
+						 xmlNode       *value);
+void           e2k_properties_set_bool          (E2kProperties *props,
+						 const gchar    *propname,
+						 gboolean       value);
+void           e2k_properties_set_float         (E2kProperties *props,
+						 const gchar    *propname,
+						 gfloat          value);
+void           e2k_properties_set_date          (E2kProperties *props,
+						 const gchar    *propname,
+						 gchar          *value);
+
+void           e2k_properties_set_type_as_string       (E2kProperties *props,
+							const gchar    *propname,
+							E2kPropType    type,
+							gchar          *value);
+void           e2k_properties_set_type_as_string_array (E2kProperties *props,
+							const gchar    *propname,
+							E2kPropType    type,
+							GPtrArray     *value);
+
+void           e2k_properties_remove            (E2kProperties *props,
+						 const gchar    *propname);
+
+typedef void (*E2kPropertiesForeachFunc)        (const gchar    *propname,
+						 E2kPropType    type,
+						 gpointer       value,
+						 gpointer       user_data);
+void           e2k_properties_foreach           (E2kProperties *props,
+						 E2kPropertiesForeachFunc callback,
+						 gpointer       user_data);
+void           e2k_properties_foreach_removed   (E2kProperties *props,
+						 E2kPropertiesForeachFunc callback,
+						 gpointer       user_data);
+
+typedef void(*E2kPropertiesForeachNamespaceFunc)(const gchar    *namespace,
+						 gchar           abbrev,
+						 gpointer       user_data);
+void           e2k_properties_foreach_namespace (E2kProperties *props,
+						 E2kPropertiesForeachNamespaceFunc callback,
+						 gpointer user_data);
+
+const gchar *e2k_prop_namespace_name   (const gchar *prop);
+gchar        e2k_prop_namespace_abbrev (const gchar *prop);
+const gchar *e2k_prop_property_name    (const gchar *prop);
+
+guint32     e2k_prop_proptag          (const gchar *prop);
+const gchar *e2k_proptag_prop          (guint32     proptag);
+
+#define E2K_PROPTAG_TYPE(proptag) (proptag & 0xFFFF)
+#define E2K_PROPTAG_ID(proptag) (proptag & 0xFFFF0000)
+
+#define E2K_PT_SHORT    0x0002
+#define E2K_PT_LONG     0x0003
+#define E2K_PT_ERROR    0x000a
+#define E2K_PT_BOOLEAN  0x000b
+#define E2K_PT_OBJECT   0x000d
+#define E2K_PT_LONGLONG 0x0014
+#define E2K_PT_STRING8  0x001e
+#define E2K_PT_UNICODE  0x001f
+#define E2K_PT_SYSTIME  0x0040
+#define E2K_PT_BINARY   0x0102
+
+#endif /* __E2K_PROPERTIES_H__ */
diff --git a/server/lib/e2k-propnames.c.in b/server/lib/e2k-propnames.c.in
new file mode 100644
index 0000000..04efc84
--- /dev/null
+++ b/server/lib/e2k-propnames.c.in
@@ -0,0 +1,26 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+struct mapi_proptag {
+	char *proptag, *name;
+} mapi_proptags[] = {
+ AUTOGENERATE@
+};
+static int nmapi_proptags = sizeof (mapi_proptags) / sizeof (mapi_proptags[0]);
+
diff --git a/server/lib/e2k-propnames.h.in b/server/lib/e2k-propnames.h.in
new file mode 100644
index 0000000..6d91ee4
--- /dev/null
+++ b/server/lib/e2k-propnames.h.in
@@ -0,0 +1,224 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2002-2004 Novell, Inc. */
+
+#ifndef __E2K_PROPNAMES_H__
+#define __E2K_PROPNAMES_H__
+
+#define E2K_NS_DAV			"DAV:"
+#define E2K_PR_DAV_CONTENT_CLASS	"DAV:contentclass"
+#define E2K_PR_DAV_CONTENT_LENGTH	"DAV:getcontentlength"
+#define E2K_PR_DAV_CONTENT_TYPE		"DAV:getcontenttype"
+#define E2K_PR_DAV_CREATION_DATE	"DAV:creationdate"
+#define E2K_PR_DAV_DISPLAY_NAME		"DAV:displayname"
+#define E2K_PR_DAV_LAST_MODIFIED	"DAV:getlastmodified"
+#define E2K_PR_DAV_HAS_SUBS		"DAV:hassubs"
+#define E2K_PR_DAV_HREF			"DAV:href"
+#define E2K_PR_DAV_IS_COLLECTION	"DAV:iscollection"
+#define E2K_PR_DAV_IS_HIDDEN		"DAV:ishidden"
+#define E2K_PR_DAV_LOCATION		"DAV:location"
+#define E2K_PR_DAV_UID			"DAV:uid"
+#define E2K_PR_DAV_VISIBLE_COUNT	"DAV:visiblecount"
+
+#define E2K_NS_CALENDAR			"urn:schemas:calendar:"
+#define E2K_PR_CALENDAR_BUSY_STATUS	E2K_NS_CALENDAR "busystatus"
+#define E2K_PR_CALENDAR_DTEND		E2K_NS_CALENDAR "dtend"
+#define E2K_PR_CALENDAR_DTSTART		E2K_NS_CALENDAR "dtstart"
+#define E2K_PR_CALENDAR_INSTANCE_TYPE	E2K_NS_CALENDAR "instancetype"
+#define E2K_PR_CALENDAR_LAST_MODIFIED	E2K_NS_CALENDAR "lastmodifiedtime"
+#define E2K_PR_CALENDAR_UID		E2K_NS_CALENDAR "uid"
+#define E2K_PR_CALENDAR_URL		E2K_NS_CALENDAR "locationurl"
+#define E2K_PR_CALENDAR_FREEBUSY_URL	E2K_NS_CALENDAR "fburl"
+#define E2K_PR_CALENDAR_REMINDER_NEXT_TIME E2K_NS_CALENDAR "remindernexttime"
+
+#define E2K_NS_CONTACTS			"urn:schemas:contacts:"
+#define E2K_PR_CONTACTS_FULL_NAME	E2K_NS_CONTACTS "cn"
+#define E2K_PR_CONTACTS_FAMILY_NAME	E2K_NS_CONTACTS "sn"
+#define E2K_PR_CONTACTS_GIVEN_NAME	E2K_NS_CONTACTS "givenName"
+#define E2K_PR_CONTACTS_ADDITIONAL_NAME	E2K_NS_CONTACTS "middlename"
+#define E2K_PR_CONTACTS_NAME_SUFFIX	E2K_NS_CONTACTS "namesuffix"
+#define E2K_PR_CONTACTS_TITLE		E2K_NS_CONTACTS "title"
+#define E2K_PR_CONTACTS_ORG		E2K_NS_CONTACTS "o"
+#define E2K_PR_CONTACTS_FILE_AS		E2K_NS_CONTACTS "fileas"
+
+#define E2K_PR_CONTACTS_PHONE_CALLBACK	E2K_NS_CONTACTS "callbackphone"
+#define E2K_PR_CONTACTS_PHONE_BUSINESS_FAX E2K_NS_CONTACTS "facsimiletelephonenumber"
+#define E2K_PR_CONTACTS_PHONE_HOME_FAX	E2K_NS_CONTACTS "homefax"
+#define E2K_PR_CONTACTS_PHONE_HOME	E2K_NS_CONTACTS "homePhone"
+#define E2K_PR_CONTACTS_PHONE_HOME_2	E2K_NS_CONTACTS "homephone2"
+#define E2K_PR_CONTACTS_PHONE_ISDN	E2K_NS_CONTACTS "internationalisdnnumber"
+#define E2K_PR_CONTACTS_PHONE_MOBILE	E2K_NS_CONTACTS "mobile"
+#define E2K_PR_CONTACTS_PHONE_COMPANY	E2K_NS_CONTACTS "organizationmainphone"
+#define E2K_PR_CONTACTS_PHONE_OTHER_FAX	E2K_NS_CONTACTS "otherfax"
+#define E2K_PR_CONTACTS_PHONE_PAGER	E2K_NS_CONTACTS "pager"
+#define E2K_PR_CONTACTS_PHONE_BUSINESS	E2K_NS_CONTACTS "telephoneNumber"
+#define E2K_PR_CONTACTS_PHONE_BUSINESS_2 E2K_NS_CONTACTS "telephonenumber2"
+#define E2K_PR_CONTACTS_PHONE_TELEX	E2K_NS_CONTACTS "telexnumber"
+#define E2K_PR_CONTACTS_PHONE_TTYTDD	E2K_NS_CONTACTS "ttytddphone"
+#define E2K_PR_CONTACTS_PHONE_ASSISTANT	E2K_NS_CONTACTS "secretaryphone"
+#define E2K_PR_CONTACTS_PHONE_CAR	E2K_NS_CONTACTS "othermobile"
+#define E2K_PR_CONTACTS_PHONE_OTHER	E2K_NS_CONTACTS "otherTelephone"
+
+#define E2K_PR_CONTACTS_EMAIL1		E2K_NS_CONTACTS "email1"
+#define E2K_PR_CONTACTS_EMAIL2		E2K_NS_CONTACTS "email2"
+#define E2K_PR_CONTACTS_EMAIL3		E2K_NS_CONTACTS "email3"
+
+#define E2K_PR_CONTACTS_ADDRESS_WORK	E2K_NS_CONTACTS "workaddress"
+#define E2K_PR_CONTACTS_WORK_STREET	E2K_NS_CONTACTS "street"
+#define E2K_PR_CONTACTS_WORK_PO_BOX	E2K_NS_CONTACTS "postofficebox"
+#define E2K_PR_CONTACTS_WORK_CITY	E2K_NS_CONTACTS "l"
+#define E2K_PR_CONTACTS_WORK_STATE	E2K_NS_CONTACTS "st"
+#define E2K_PR_CONTACTS_WORK_ZIP	E2K_NS_CONTACTS "postalcode"
+#define E2K_PR_CONTACTS_WORK_COUNTRY	E2K_NS_CONTACTS "co"
+#define E2K_PR_CONTACTS_ADDRESS_HOME	E2K_NS_CONTACTS "homepostaladdress"
+#define E2K_PR_CONTACTS_HOME_STREET	E2K_NS_CONTACTS "homeStreet"
+#define E2K_PR_CONTACTS_HOME_PO_BOX	E2K_NS_CONTACTS "homePostOfficeBox"
+#define E2K_PR_CONTACTS_HOME_CITY	E2K_NS_CONTACTS "homeCity"
+#define E2K_PR_CONTACTS_HOME_STATE	E2K_NS_CONTACTS "homeState"
+#define E2K_PR_CONTACTS_HOME_ZIP	E2K_NS_CONTACTS "homePostalCode"
+#define E2K_PR_CONTACTS_HOME_COUNTRY	E2K_NS_CONTACTS "homeCountry"
+#define E2K_PR_CONTACTS_ADDRESS_OTHER	E2K_NS_CONTACTS "otherpostaladdress"
+#define E2K_PR_CONTACTS_OTHER_STREET	E2K_NS_CONTACTS "otherstreet"
+#define E2K_PR_CONTACTS_OTHER_PO_BOX	E2K_NS_CONTACTS "otherpostofficebox"
+#define E2K_PR_CONTACTS_OTHER_CITY	E2K_NS_CONTACTS "othercity"
+#define E2K_PR_CONTACTS_OTHER_STATE	E2K_NS_CONTACTS "otherstate"
+#define E2K_PR_CONTACTS_OTHER_ZIP	E2K_NS_CONTACTS "otherpostalcode"
+#define E2K_PR_CONTACTS_OTHER_COUNTRY	E2K_NS_CONTACTS "othercountry"
+
+#define E2K_PR_CONTACTS_HOMEPAGE_URL	E2K_NS_CONTACTS "businesshomepage"
+#define E2K_PR_CONTACTS_ORG_UNIT	E2K_NS_CONTACTS "department"
+#define E2K_PR_CONTACTS_OFFICE		E2K_NS_CONTACTS "roomnumber"
+#define E2K_PR_CONTACTS_ROLE		E2K_NS_CONTACTS "profession"
+#define E2K_PR_CONTACTS_MANAGER		E2K_NS_CONTACTS "manager"
+#define E2K_PR_CONTACTS_ASSISTANT	E2K_NS_CONTACTS "secretarycn"
+#define E2K_PR_CONTACTS_NICKNAME	E2K_NS_CONTACTS "nickname"
+#define E2K_PR_CONTACTS_SPOUSE		E2K_NS_CONTACTS "spousecn"
+#define E2K_PR_CONTACTS_BIRTH_DATE	E2K_NS_CONTACTS "bday"
+#define E2K_PR_CONTACTS_ANNIVERSARY	E2K_NS_CONTACTS "weddinganniversary"
+
+#define E2K_NS_HTTPMAIL			"urn:schemas:httpmail:"
+#define E2K_PR_HTTPMAIL_DATE		E2K_NS_HTTPMAIL "date"
+#define E2K_PR_HTTPMAIL_FROM_EMAIL	E2K_NS_HTTPMAIL "fromemail"
+#define E2K_PR_HTTPMAIL_FROM_NAME	E2K_NS_HTTPMAIL "fromname"
+#define E2K_PR_HTTPMAIL_HAS_ATTACHMENT	E2K_NS_HTTPMAIL "hasattachment"
+#define E2K_PR_HTTPMAIL_MESSAGE_FLAG	E2K_NS_HTTPMAIL "messageflag"
+#define E2K_PR_HTTPMAIL_READ		E2K_NS_HTTPMAIL "read"
+#define E2K_PR_HTTPMAIL_SUBJECT		E2K_NS_HTTPMAIL "subject"
+#define E2K_PR_HTTPMAIL_TEXT_DESCRIPTION E2K_NS_HTTPMAIL "textdescription"
+#define E2K_PR_HTTPMAIL_THREAD_TOPIC	E2K_NS_HTTPMAIL "thread-topic"
+#define E2K_PR_HTTPMAIL_UNREAD_COUNT	E2K_NS_HTTPMAIL "unreadcount"
+
+#define E2K_NS_STD_FOLDER		"urn:schemas:httpmail:"
+#define E2K_PR_STD_FOLDER_CALENDAR	E2K_NS_HTTPMAIL "calendar"
+#define E2K_PR_STD_FOLDER_CONTACTS	E2K_NS_HTTPMAIL "contacts"
+#define E2K_PR_STD_FOLDER_DELETED_ITEMS	E2K_NS_HTTPMAIL "deleteditems"
+#define E2K_PR_STD_FOLDER_DRAFTS	E2K_NS_HTTPMAIL "drafts"
+#define E2K_PR_STD_FOLDER_INBOX		E2K_NS_HTTPMAIL "inbox"
+#define E2K_PR_STD_FOLDER_JOURNAL	E2K_NS_HTTPMAIL "journal"
+#define E2K_PR_STD_FOLDER_ROOT		E2K_NS_HTTPMAIL "msgfolderroot"
+#define E2K_PR_STD_FOLDER_NOTES		E2K_NS_HTTPMAIL "notes"
+#define E2K_PR_STD_FOLDER_OUTBOX	E2K_NS_HTTPMAIL "outbox"
+#define E2K_PR_STD_FOLDER_SENDMSG	E2K_NS_HTTPMAIL "sendmsg"
+#define E2K_PR_STD_FOLDER_SENT_ITEMS	E2K_NS_HTTPMAIL "sentitems"
+#define E2K_PR_STD_FOLDER_TASKS		E2K_NS_HTTPMAIL "tasks"
+
+#define E2K_NS_MAILHEADER		"urn:schemas:mailheader:"
+#define E2K_PR_MAILHEADER_CC		E2K_NS_MAILHEADER "cc"
+#define E2K_PR_MAILHEADER_DATE		E2K_NS_MAILHEADER "date"
+#define E2K_PR_MAILHEADER_FROM		E2K_NS_MAILHEADER "from"
+#define E2K_PR_MAILHEADER_IMPORTANCE	E2K_NS_MAILHEADER "importance"
+#define E2K_PR_MAILHEADER_IN_REPLY_TO	E2K_NS_MAILHEADER "in-reply-to"
+#define E2K_PR_MAILHEADER_MESSAGE_ID	E2K_NS_MAILHEADER "message-id"
+#define E2K_PR_MAILHEADER_RECEIVED	E2K_NS_MAILHEADER "received"
+#define E2K_PR_MAILHEADER_REFERENCES	E2K_NS_MAILHEADER "references"
+#define E2K_PR_MAILHEADER_REPLY_BY	E2K_NS_MAILHEADER "reply-by"
+#define E2K_PR_MAILHEADER_SUBJECT	E2K_NS_MAILHEADER "subject"
+#define E2K_PR_MAILHEADER_THREAD_INDEX	E2K_NS_MAILHEADER "thread-index"
+#define E2K_PR_MAILHEADER_TO		E2K_NS_MAILHEADER "to"
+#define E2K_PR_MAILHEADER_COMPLETED	E2K_NS_MAILHEADER "x-message-completed"
+
+
+#define E2K_NS_SUBSCRIPTION		"http://schemas.microsoft.com/Exchange/";
+#define E2K_PR_SUBSCRIPTION_ID		E2K_NS_SUBSCRIPTION "subscriptionID"
+
+#define E2K_NS_EXCHANGE			"http://schemas.microsoft.com/exchange/";
+#define E2K_PR_EXCHANGE_MESSAGE_CLASS	E2K_NS_EXCHANGE "outlookmessageclass"
+#define E2K_PR_EXCHANGE_FOLDER_CLASS	E2K_NS_EXCHANGE "outlookfolderclass"
+#define E2K_PR_EXCHANGE_KEYWORDS	E2K_NS_EXCHANGE "keywords-utf8"
+#define E2K_PR_EXCHANGE_SD_BINARY	E2K_NS_EXCHANGE "ntsecuritydescriptor"
+#define E2K_PR_EXCHANGE_SD_XML		E2K_NS_EXCHANGE "security/descriptor"
+#define E2K_PR_EXCHANGE_TIMEZONE	E2K_NS_EXCHANGE "timezone"
+#define E2K_PR_EXCHANGE_PERMANENTURL	E2K_NS_EXCHANGE "permanenturl"
+#define E2K_PR_EXCHANGE_FOLDER_SIZE	E2K_NS_EXCHANGE "foldersize"
+#define E2K_PR_EXCHANGE_OOF_STATE	E2K_NS_EXCHANGE "oof-state"
+
+#define E2K_NS_REPL			"http://schemas.microsoft.com/repl/";
+#define E2K_PR_REPL_UID			E2K_NS_REPL "repl-uid"
+
+#define E2K_NS_SECURITY			"http://schemas.microsoft.com/security/";
+#define E2K_NS_TYPE			"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/"
+
+#define E2K_NS_MAPI			"http://schemas.microsoft.com/mapi/";
+#define E2K_PR_MAPI_COMMON_START	E2K_NS_MAPI "commonstart"
+#define E2K_PR_MAPI_COMMON_END		E2K_NS_MAPI "commonend"
+#define E2K_PR_MAPI_NO_AUTOARCHIVE	E2K_NS_MAPI "agingdontageme"
+#define E2K_PR_MAPI_PRIORITY		E2K_NS_MAPI "priority"
+#define E2K_PR_MAPI_REMINDER_SET	E2K_NS_MAPI "reminderset"
+#define E2K_PR_MAPI_SENSITIVITY		E2K_NS_MAPI "sensitivity"
+#define E2K_PR_MAPI_SIDE_EFFECTS	E2K_NS_MAPI "sideeffects"
+#define E2K_PR_MAPI_EMAIL_1_ENTRYID	E2K_NS_MAPI "email1originalentryid"
+#define E2K_PR_MAPI_EMAIL_1_ADDRTYPE	E2K_NS_MAPI "email1addrtype"
+#define E2K_PR_MAPI_EMAIL_1_ADDRESS	E2K_NS_MAPI "email1emailaddress"
+#define E2K_PR_MAPI_EMAIL_1_DISPLAY_NAME E2K_NS_MAPI "email1originaldisplayname"
+#define E2K_PR_MAPI_EMAIL_2_ENTRYID	E2K_NS_MAPI "email2originalentryid"
+#define E2K_PR_MAPI_EMAIL_2_ADDRTYPE	E2K_NS_MAPI "email2addrtype"
+#define E2K_PR_MAPI_EMAIL_2_ADDRESS	E2K_NS_MAPI "email2emailaddress"
+#define E2K_PR_MAPI_EMAIL_2_DISPLAY_NAME E2K_NS_MAPI "email2originaldisplayname"
+#define E2K_PR_MAPI_EMAIL_3_ENTRYID	E2K_NS_MAPI "email3originalentryid"
+#define E2K_PR_MAPI_EMAIL_3_ADDRTYPE	E2K_NS_MAPI "email3addrtype"
+#define E2K_PR_MAPI_EMAIL_3_ADDRESS	E2K_NS_MAPI "email3emailaddress"
+#define E2K_PR_MAPI_EMAIL_3_DISPLAY_NAME E2K_NS_MAPI "email3originaldisplayname"
+#define E2K_PR_MAPI_EMAIL_LIST_TYPE	E2K_NS_MAPI "emaillisttype"
+#define E2K_PR_MAPI_EMAIL_ADDRESS_LIST	E2K_NS_MAPI "emailaddresslist"
+
+
+#define E2K_NS_MAPI_ID			"http://schemas.microsoft.com/mapi/id/";
+#define E2K_NS_MAPI_ID_LEN		(sizeof (E2K_NS_MAPI_ID) - 1)
+
+#define E2K_NS_OUTLOOK_APPOINTMENT	E2K_NS_MAPI_ID "{00062002-0000-0000-C000-000000000046}/"
+
+#define E2K_NS_OUTLOOK_TASK		E2K_NS_MAPI_ID "{00062003-0000-0000-C000-000000000046}/"
+#define E2K_PR_OUTLOOK_TASK_STATUS	E2K_NS_OUTLOOK_TASK "0x00008101"
+#define E2K_PR_OUTLOOK_TASK_PERCENT	E2K_NS_OUTLOOK_TASK "0x00008102"
+#define E2K_PR_OUTLOOK_TASK_TEAM_TASK	E2K_NS_OUTLOOK_TASK "0x00008103"
+#define E2K_PR_OUTLOOK_TASK_START_DT	E2K_NS_OUTLOOK_TASK "0x00008104"
+#define E2K_PR_OUTLOOK_TASK_DUE_DT	E2K_NS_OUTLOOK_TASK "0x00008105"
+#define E2K_PR_OUTLOOK_TASK_DONE_DT	E2K_NS_OUTLOOK_TASK "0x0000810f"
+#define E2K_PR_OUTLOOK_TASK_ACTUAL_WORK	E2K_NS_OUTLOOK_TASK "0x00008110"
+#define E2K_PR_OUTLOOK_TASK_TOTAL_WORK	E2K_NS_OUTLOOK_TASK "0x00008111"
+#define E2K_PR_OUTLOOK_TASK_IS_DONE	E2K_NS_OUTLOOK_TASK "0x0000811c"
+#define E2K_PR_OUTLOOK_TASK_OWNER	E2K_NS_OUTLOOK_TASK "0x0000811f"
+#define E2K_PR_OUTLOOK_TASK_RECURRING	E2K_NS_OUTLOOK_TASK "0x00008126"
+#define E2K_PR_OUTLOOK_TASK_ASSIGNMENT	E2K_NS_OUTLOOK_TASK "0x00008129"
+
+#define E2K_NS_OUTLOOK_CONTACT		E2K_NS_MAPI_ID "{00062004-0000-0000-C000-000000000046}/"
+#define E2K_PR_OUTLOOK_CONTACT_JOURNAL	E2K_NS_OUTLOOK_CONTACT "0x00008025"
+#define E2K_PR_OUTLOOK_CONTACT_NETMEETING_URL E2K_NS_OUTLOOK_CONTACT "0x00008056"
+#define E2K_PR_OUTLOOK_CONTACT_IM_ADDR	E2K_NS_OUTLOOK_CONTACT "0x00008062"
+
+#define E2K_NS_OUTLOOK_COMMON		E2K_NS_MAPI_ID "{00062008-0000-0000-C000-000000000046}/"
+#define E2K_PR_OUTLOOK_COMMON_CONTACTS	E2K_NS_OUTLOOK_COMMON "0x00008586"
+
+
+#define E2K_NS_OUTLOOK_JOURNAL		E2K_NS_MAPI_ID "{0006200A-0000-0000-C000-000000000046}/"
+
+#define E2K_NS_OUTLOOK_STICKYNOTE	E2K_NS_MAPI_ID "{0006200E-0000-0000-C000-000000000046}/"
+#define E2K_PR_OUTLOOK_STICKYNOTE_COLOR	E2K_NS_OUTLOOK_STICKYNOTE "0x00008b00"
+#define E2K_PR_OUTLOOK_STICKYNOTE_WIDTH	E2K_NS_OUTLOOK_STICKYNOTE "0x00008b02"
+#define E2K_PR_OUTLOOK_STICKYNOTE_HEIGHT E2K_NS_OUTLOOK_STICKYNOTE "0x00008b03"
+
+
+#define E2K_NS_MAPI_PROPTAG		"http://schemas.microsoft.com/mapi/proptag/";
+
+ AUTOGENERATE@
+
+#endif /* __E2K_PROPNAMES_H__ */
diff --git a/server/lib/e2k-proptags.h.in b/server/lib/e2k-proptags.h.in
new file mode 100644
index 0000000..7891424
--- /dev/null
+++ b/server/lib/e2k-proptags.h.in
@@ -0,0 +1,9 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2003-2004 Novell, Inc. */
+
+#ifndef __E2K_PROPTAGS_H__
+#define __E2K_PROPTAGS_H__
+
+ AUTOGENERATE@
+
+#endif /* __E2K_PROPTAGS_H__ */
diff --git a/server/lib/e2k-restriction.c b/server/lib/e2k-restriction.c
new file mode 100644
index 0000000..f8a6c67
--- /dev/null
+++ b/server/lib/e2k-restriction.c
@@ -0,0 +1,1068 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* e2k-restriction.c: message restrictions (WHERE clauses / Rule conditions) */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e2k-restriction.h"
+#include "e2k-properties.h"
+#include "e2k-rule.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+static E2kRestriction *
+conjoin (E2kRestrictionType type, gint nrns, E2kRestriction **rns, gboolean unref)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+	gint i;
+
+	ret->type = type;
+	ret->res.and.nrns = nrns;
+	ret->res.and.rns = g_new (E2kRestriction *, nrns);
+	for (i = 0; i < nrns; i++) {
+		ret->res.and.rns[i] = rns[i];
+		if (!unref)
+			e2k_restriction_ref (rns[i]);
+	}
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_and:
+ * @nrns: length of @rns
+ * @rns: an array of #E2kRestriction
+ * @unref: whether or not to unref the restrictions when it is done
+ *
+ * Creates a new restriction which is true if all of the restrictions
+ * in @rns are true.
+ *
+ * If @unref is %TRUE, then e2k_restriction_and() is essentially
+ * stealing the caller's references on the restrictions. If it is
+ * %FALSE, then e2k_restriction_and() will acquire its own references
+ * to each of the restrictions.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_and (gint nrns, E2kRestriction **rns, gboolean unref)
+{
+	return conjoin (E2K_RESTRICTION_AND, nrns, rns, unref);
+}
+
+/**
+ * e2k_restriction_or:
+ * @nrns: length of @rns
+ * @rns: an array of #E2kRestriction
+ * @unref: see e2k_restriction_and()
+ *
+ * Creates a new restriction which is true if any of the restrictions
+ * in @rns are true.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_or (gint nrns, E2kRestriction **rns, gboolean unref)
+{
+	return conjoin (E2K_RESTRICTION_OR, nrns, rns, unref);
+}
+
+static E2kRestriction *
+conjoinv (E2kRestrictionType type, E2kRestriction *rn, va_list ap)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+	GPtrArray *rns;
+
+	rns = g_ptr_array_new ();
+	while (rn) {
+		g_ptr_array_add (rns, rn);
+		rn = va_arg (ap, E2kRestriction *);
+	}
+	va_end (ap);
+
+	ret->type = type;
+	ret->res.and.nrns = rns->len;
+	ret->res.and.rns = (E2kRestriction **)rns->pdata;
+	g_ptr_array_free (rns, FALSE);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_andv:
+ * @rn: an #E2kRestriction
+ * @...: a %NULL-terminated list of additional #E2kRestrictions
+ *
+ * Creates a new restriction which is true if all of the passed-in
+ * restrictions are true. e2k_restriction_andv() steals the caller's
+ * reference on each of the passed-in restrictions.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_andv (E2kRestriction *rn, ...)
+{
+	va_list ap;
+
+	va_start (ap, rn);
+	return conjoinv (E2K_RESTRICTION_AND, rn, ap);
+}
+
+/**
+ * e2k_restriction_orv:
+ * @rn: an #E2kRestriction
+ * @...: a %NULL-terminated list of additional #E2kRestrictions
+ *
+ * Creates a new restriction which is true if any of the passed-in
+ * restrictions are true. e2k_restriction_orv() steals the caller's
+ * reference on each of the passed-in restrictions.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_orv (E2kRestriction *rn, ...)
+{
+	va_list ap;
+
+	va_start (ap, rn);
+	return conjoinv (E2K_RESTRICTION_OR, rn, ap);
+}
+
+/**
+ * e2k_restriction_not:
+ * @rn: an #E2kRestriction
+ * @unref: see e2k_restriction_and()
+ *
+ * Creates a new restriction which is true if @rn is false.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_not (E2kRestriction *rn, gboolean unref)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_NOT;
+	ret->res.not.rn = rn;
+	if (!unref)
+		e2k_restriction_ref (rn);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_content:
+ * @propname: text property to compare against
+ * @fuzzy_level: how to compare
+ * @value: value to compare against
+ *
+ * Creates a new restriction that is true for objects where the
+ * indicated property's value matches @value according to @fuzzy_level.
+ *
+ * For a WebDAV SEARCH, @fuzzy_level should be %E2K_FL_FULLSTRING,
+ * %E2K_FL_SUBSTRING, %E2K_FL_PREFIX, or %E2K_FL_SUFFIX.
+ *
+ * For a MAPI restriction, @fuzzy_level may not be %E2K_FL_SUFFIX, but
+ * may be ORed with any of the additional values %E2K_FL_IGNORECASE,
+ * %E2K_FL_IGNORENONSPACE, or %E2K_FL_LOOSE.
+ *
+ * To compare a property's sort order to another string, use
+ * e2k_restriction_prop_string().
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_content (const gchar *propname,
+			 E2kRestrictionFuzzyLevel fuzzy_level,
+			 const gchar *value)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_CONTENT;
+	ret->res.content.fuzzy_level = fuzzy_level;
+	e2k_rule_prop_set (&ret->res.content.pv.prop, propname);
+	ret->res.content.pv.type = E2K_PROP_TYPE_STRING;
+	ret->res.content.pv.value = g_strdup (value);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_prop_bool:
+ * @propname: boolean property to compare against
+ * @relop: %E2K_RELOP_EQ or %E2K_RELOP_NE
+ * @value: %TRUE or %FALSE
+ *
+ * Creates a new restriction that is true for objects where the
+ * indicated property matches @relop and @value.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_prop_bool (const gchar *propname, E2kRestrictionRelop relop,
+			   gboolean value)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_PROPERTY;
+	ret->res.property.relop = relop;
+	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
+	ret->res.property.pv.type = E2K_PROP_TYPE_BOOL;
+	ret->res.property.pv.value = GUINT_TO_POINTER (value);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_prop_int:
+ * @propname: integer property to compare against
+ * @relop: an #E2kRestrictionRelop
+ * @value: number to compare against
+ *
+ * Creates a new restriction that is true for objects where the
+ * indicated property matches @value according to @relop.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_prop_int (const gchar *propname, E2kRestrictionRelop relop,
+			  gint value)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_PROPERTY;
+	ret->res.property.relop = relop;
+	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
+	ret->res.property.pv.type = E2K_PROP_TYPE_INT;
+	ret->res.property.pv.value = GINT_TO_POINTER (value);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_prop_date:
+ * @propname: date/time property to compare against
+ * @relop: an #E2kRestrictionRelop
+ * @value: date/time to compare against (as returned by e2k_make_timestamp())
+ *
+ * Creates a new restriction that is true for objects where the
+ * indicated property matches @value according to @relop.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_prop_date (const gchar *propname, E2kRestrictionRelop relop,
+			   const gchar *value)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_PROPERTY;
+	ret->res.property.relop = relop;
+	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
+	ret->res.property.pv.type = E2K_PROP_TYPE_DATE;
+	ret->res.property.pv.value = g_strdup (value);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_prop_string:
+ * @propname: text property to compare against
+ * @relop: an #E2kRestrictionRelop
+ * @value: text to compare against
+ *
+ * Creates a new restriction that is true for objects where the
+ * indicated property matches @value according to @relop.
+ *
+ * To do a substring match, use e2k_restriction_content().
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_prop_string (const gchar *propname, E2kRestrictionRelop relop,
+			     const gchar *value)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_PROPERTY;
+	ret->res.property.relop = relop;
+	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
+	ret->res.property.pv.type = E2K_PROP_TYPE_STRING;
+	ret->res.property.pv.value = g_strdup (value);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_prop_binary:
+ * @propname: binary property to compare against
+ * @relop: %E2K_RELOP_EQ or %E2K_RELOP_NE
+ * @data: data to compare against
+ * @len: length of @data
+ *
+ * Creates a new restriction that is true for objects where the
+ * indicated property matches @value according to @relop.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_prop_binary (const gchar *propname, E2kRestrictionRelop relop,
+			     gconstpointer data, gint len)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_PROPERTY;
+	ret->res.property.relop = relop;
+	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
+	ret->res.property.pv.type = E2K_PROP_TYPE_BINARY;
+	ret->res.property.pv.value = g_byte_array_new ();
+	g_byte_array_append (ret->res.property.pv.value, data, len);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_compare:
+ * @propname1: first property
+ * @relop: an #E2kRestrictionRelop
+ * @propname2: second property
+ *
+ * Creates a new restriction which is true for objects where
+ * @propname1 and @propname2 have the relationship described by
+ * @relop.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_compare (const gchar *propname1, E2kRestrictionRelop relop,
+			 const gchar *propname2)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_COMPAREPROPS;
+	ret->res.compare.relop = relop;
+	e2k_rule_prop_set (&ret->res.compare.prop1, propname1);
+	e2k_rule_prop_set (&ret->res.compare.prop2, propname2);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_bitmask:
+ * @propname: integer property to compare
+ * @bitop: an #E2kRestrictionBitop
+ * @mask: mask of bits to compare against
+ *
+ * Creates a new restriction that is true for objects where the
+ * indicated bits of the value of @propname either are or aren't zero,
+ * as indicated by @bitop.
+ *
+ * This cannot be used for WebDAV SEARCH restrictions.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_bitmask (const gchar *propname, E2kRestrictionBitop bitop,
+			 guint32 mask)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_BITMASK;
+	ret->res.bitmask.bitop = bitop;
+	e2k_rule_prop_set (&ret->res.bitmask.prop, propname);
+	ret->res.bitmask.mask = mask;
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_size:
+ * @propname: property to compare
+ * @relop: an #E2kRestrictionRelop
+ * @size: the size to compare @propname to
+ *
+ * Creates a new restriction which is true for objects where the size
+ * of the value of @propname matches @size according to @relop.
+ *
+ * This cannot be used for WebDAV SEARCH restrictions.
+ *
+ * You probably do not want to use this. The standard idiom for
+ * checking the size of a message is to use e2k_restriction_prop_int()
+ * on its %PR_MESSAGE_SIZE property, not to use e2k_restriction_size()
+ * on its %PR_BODY.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_size (const gchar *propname, E2kRestrictionRelop relop,
+		      guint32 size)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_SIZE;
+	ret->res.size.relop = relop;
+	e2k_rule_prop_set (&ret->res.size.prop, propname);
+	ret->res.size.size = size;
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_exist:
+ * @propname: property to check
+ *
+ * Creates a new restriction which is true for objects that have
+ * a @propname property.
+ *
+ * This cannot be used for WebDAV SEARCH restrictions.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_exist (const gchar *propname)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_EXIST;
+	e2k_rule_prop_set (&ret->res.exist.prop, propname);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_sub:
+ * @subtable: the WebDAV name of a MAPI property of type PT_OBJECT
+ * @rn: the restriction to apply against the values of @subtable
+ * @unref: see e2k_restriction_and()
+ *
+ * Creates a new restriction that is true for objects where @rn is
+ * true when applied to the value of @subtable on that object.
+ *
+ * @subtable is generally %PR_MESSAGE_RECIPIENTS (for finding messages
+ * whose recipients match a given restriction) or
+ * %PR_MESSAGE_ATTACHMENTS (for finding messages whose attachments
+ * match a given restriction).
+ *
+ * This cannot be used for WebDAV SEARCH restrictions.
+ *
+ * Return value: the new restriction
+ **/
+E2kRestriction *
+e2k_restriction_sub (const gchar *subtable, E2kRestriction *rn, gboolean unref)
+{
+	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+
+	ret->type = E2K_RESTRICTION_SUBRESTRICTION;
+	e2k_rule_prop_set (&ret->res.sub.subtable, subtable);
+	ret->res.sub.rn = rn;
+	if (!unref)
+		e2k_restriction_ref (rn);
+
+	return ret;
+}
+
+/**
+ * e2k_restriction_unref:
+ * @rn: a restriction
+ *
+ * Unrefs @rn. If there are no more references to @rn, it is freed.
+ **/
+void
+e2k_restriction_unref (E2kRestriction *rn)
+{
+	gint i;
+
+	if (rn->ref_count--)
+		return;
+
+	switch (rn->type) {
+	case E2K_RESTRICTION_AND:
+	case E2K_RESTRICTION_OR:
+		for (i = 0; i < rn->res.and.nrns; i++)
+			e2k_restriction_unref (rn->res.and.rns[i]);
+		g_free (rn->res.and.rns);
+		break;
+
+	case E2K_RESTRICTION_NOT:
+		e2k_restriction_unref (rn->res.not.rn);
+		break;
+
+	case E2K_RESTRICTION_CONTENT:
+		e2k_rule_free_propvalue (&rn->res.content.pv);
+		break;
+
+	case E2K_RESTRICTION_PROPERTY:
+		e2k_rule_free_propvalue (&rn->res.property.pv);
+		break;
+
+	default:
+		break;
+	}
+
+	g_free (rn);
+}
+
+/**
+ * e2k_restriction_ref:
+ * @rn: a restriction
+ *
+ * Refs @rn.
+ **/
+void
+e2k_restriction_ref (E2kRestriction *rn)
+{
+	rn->ref_count++;
+}
+
+/* SQL export */
+
+static gboolean rn_to_sql (E2kRestriction *rn, GString *sql, E2kRestrictionType inside);
+
+static const gchar *sql_relops[] = { "<", "<=", ">", ">=", "=", "!=" };
+static const gint n_sql_relops = G_N_ELEMENTS (sql_relops);
+
+static gboolean
+rns_to_sql (E2kRestrictionType type, E2kRestriction **rns, gint nrns, GString *sql)
+{
+	gint i;
+	gboolean need_op = FALSE;
+	gboolean rv = FALSE;
+
+	for (i = 0; i < nrns; i++) {
+		if (need_op) {
+			g_string_append (sql, type == E2K_RESTRICTION_AND ?
+					 " AND " : " OR ");
+			need_op = FALSE;
+		}
+		if (rn_to_sql (rns[i], sql, type)) {
+			need_op = TRUE;
+			rv = TRUE;
+		}
+	}
+	return rv;
+}
+
+static void
+append_sql_quoted (GString *sql, const gchar *string)
+{
+	while (*string) {
+		if (*string == '\'')
+			g_string_append (sql, "''");
+		else
+			g_string_append_c (sql, *string);
+		string++;
+	}
+}
+
+static gboolean
+rn_to_sql (E2kRestriction *rn, GString *sql, E2kRestrictionType inside)
+{
+	E2kPropValue *pv;
+
+	switch (rn->type) {
+	case E2K_RESTRICTION_AND:
+	case E2K_RESTRICTION_OR: {
+		GString *subsql = g_string_new ("");
+		gboolean rv;
+		if ((rv = rns_to_sql (rn->type, rn->res.and.rns, rn->res.and.nrns, subsql))) {
+			if (rn->type != inside)
+				g_string_append (sql, "(");
+			g_string_append (sql, subsql->str);
+			if (rn->type != inside)
+				g_string_append (sql, ")");
+		}
+		g_string_free (subsql, TRUE);
+
+		return rv;
+	}
+
+	case E2K_RESTRICTION_NOT: {
+		GString *subsql = g_string_new ("");
+		gboolean rv;
+		if ((rv = rn_to_sql (rn->res.not.rn, subsql, rn->type))) {
+			g_string_append (sql, "NOT (");
+			g_string_append (sql, subsql->str);
+			g_string_append (sql, ")");
+		}
+		g_string_free (subsql, TRUE);
+
+		return rv;
+	}
+
+	case E2K_RESTRICTION_CONTENT:
+		pv = &rn->res.content.pv;
+		g_string_append_printf (sql, "\"%s\" ", pv->prop.name);
+
+		switch (E2K_FL_MATCH_TYPE (rn->res.content.fuzzy_level)) {
+		case E2K_FL_SUBSTRING:
+			g_string_append (sql, "LIKE '%");
+			append_sql_quoted (sql, pv->value);
+			g_string_append (sql, "%'");
+			break;
+
+		case E2K_FL_PREFIX:
+			g_string_append (sql, "LIKE '");
+			append_sql_quoted (sql, pv->value);
+			g_string_append (sql, "%'");
+			break;
+
+		case E2K_FL_SUFFIX:
+			g_string_append (sql, "LIKE '%");
+			append_sql_quoted (sql, pv->value);
+			g_string_append_c (sql, '\'');
+			break;
+
+		case E2K_FL_FULLSTRING:
+		default:
+			g_string_append (sql, "= '");
+			append_sql_quoted (sql, pv->value);
+			g_string_append_c (sql, '\'');
+			break;
+		}
+		return TRUE;
+
+	case E2K_RESTRICTION_PROPERTY:
+		if (rn->res.property.relop >= n_sql_relops)
+			return FALSE;
+
+		pv = &rn->res.property.pv;
+		g_string_append_printf (sql, "\"%s\" %s ", pv->prop.name,
+					sql_relops[rn->res.property.relop]);
+
+		switch (pv->type) {
+		case E2K_PROP_TYPE_INT:
+			g_string_append_printf (sql, "%d",
+						GPOINTER_TO_UINT (pv->value));
+			break;
+
+		case E2K_PROP_TYPE_BOOL:
+			g_string_append (sql, pv->value ? "True" : "False");
+			break;
+
+		case E2K_PROP_TYPE_DATE:
+			g_string_append_printf (sql,
+						"cast (\"%s\" as 'dateTime.tz')",
+						(gchar *)pv->value);
+			break;
+
+		default:
+			g_string_append_c (sql, '\'');
+			append_sql_quoted (sql, pv->value);
+			g_string_append_c (sql, '\'');
+			break;
+		}
+		return TRUE;
+
+	case E2K_RESTRICTION_COMPAREPROPS:
+		if (rn->res.compare.relop >= n_sql_relops)
+			return FALSE;
+
+		g_string_append_printf (sql, "\"%s\" %s \"%s\"",
+					rn->res.compare.prop1.name,
+					sql_relops[rn->res.compare.relop],
+					rn->res.compare.prop2.name);
+		return TRUE;
+
+	case E2K_RESTRICTION_COMMENT:
+		return TRUE;
+
+	case E2K_RESTRICTION_BITMASK:
+	case E2K_RESTRICTION_EXIST:
+	case E2K_RESTRICTION_SIZE:
+	case E2K_RESTRICTION_SUBRESTRICTION:
+	default:
+		return FALSE;
+
+	}
+}
+
+/**
+ * e2k_restriction_to_sql:
+ * @rn: a restriction
+ *
+ * Converts @rn to an SQL WHERE clause to be used with the WebDAV
+ * SEARCH method. Note that certain restriction types cannot be used
+ * in SQL, as mentioned in their descriptions above.
+ *
+ * If the restriction matches all objects, the return value will
+ * be the empty string. Otherwise it will start with "WHERE ".
+ *
+ * Return value: the SQL WHERE clause, which the caller must free,
+ * or %NULL if @rn could not be converted to SQL.
+ **/
+gchar *
+e2k_restriction_to_sql (E2kRestriction *rn)
+{
+	GString *sql;
+	gchar *ret;
+
+	sql = g_string_new (NULL);
+	if (!rn_to_sql (rn, sql, E2K_RESTRICTION_AND)) {
+		g_string_free (sql, TRUE);
+		return NULL;
+	}
+
+	if (sql->len)
+		g_string_prepend (sql, "WHERE ");
+
+	ret = sql->str;
+	g_string_free (sql, FALSE);
+	return ret;
+}
+
+/* Binary import/export */
+
+static gboolean
+extract_restriction (guint8 **data, gint *len, E2kRestriction **rn)
+{
+	gint type;
+
+	if (*len == 0)
+		return FALSE;
+	type = (*data)[0];
+	(*data)++;
+	(*len)--;
+
+	switch (type) {
+	case E2K_RESTRICTION_AND:
+	case E2K_RESTRICTION_OR:
+	{
+		E2kRestriction **rns;
+		guint16 nrns;
+		gint i;
+
+		if (!e2k_rule_extract_uint16 (data, len, &nrns))
+			return FALSE;
+		rns = g_new0 (E2kRestriction *, nrns);
+		for (i = 0; i < nrns; i++) {
+			if (!extract_restriction (data, len, &rns[i])) {
+				while (i--)
+					e2k_restriction_unref (rns[i]);
+				g_free (rns);
+				return FALSE;
+			}
+		}
+
+		*rn = conjoin (type, nrns, rns, TRUE);
+		return TRUE;
+	}
+
+	case E2K_RESTRICTION_NOT:
+	{
+		E2kRestriction *subrn;
+
+		if (!extract_restriction (data, len, &subrn))
+			return FALSE;
+		*rn = e2k_restriction_not (subrn, TRUE);
+		return TRUE;
+	}
+
+	case E2K_RESTRICTION_CONTENT:
+	{
+		guint32 fuzzy_level;
+		E2kRuleProp prop;
+		E2kPropValue pv;
+
+		if (!e2k_rule_extract_uint32 (data, len, &fuzzy_level) ||
+		    !e2k_rule_extract_proptag (data, len, &prop) ||
+		    !e2k_rule_extract_propvalue (data, len, &pv))
+			return FALSE;
+
+		pv.prop = prop;
+
+		*rn = g_new0 (E2kRestriction, 1);
+		(*rn)->type = type;
+		(*rn)->res.content.fuzzy_level = fuzzy_level;
+		(*rn)->res.content.pv = pv;
+		return TRUE;
+	}
+
+	case E2K_RESTRICTION_PROPERTY:
+	{
+		guint8 relop;
+		E2kRuleProp prop;
+		E2kPropValue pv;
+
+		if (!e2k_rule_extract_byte (data, len, &relop) ||
+		    !e2k_rule_extract_proptag (data, len, &prop) ||
+		    !e2k_rule_extract_propvalue (data, len, &pv))
+			return FALSE;
+
+		pv.prop = prop;
+
+		*rn = g_new0 (E2kRestriction, 1);
+		(*rn)->type = type;
+		(*rn)->res.property.relop = relop;
+		(*rn)->res.property.pv = pv;
+		return TRUE;
+	}
+
+	case E2K_RESTRICTION_COMPAREPROPS:
+	{
+		/* FIXME */
+		return FALSE;
+	}
+
+	case E2K_RESTRICTION_BITMASK:
+	{
+		guint8 bitop;
+		guint32 mask;
+		E2kRuleProp prop;
+
+		if (!e2k_rule_extract_byte (data, len, &bitop) ||
+		    !e2k_rule_extract_proptag (data, len, &prop) ||
+		    !e2k_rule_extract_uint32 (data, len, &mask))
+			return FALSE;
+
+		*rn = g_new0 (E2kRestriction, 1);
+		(*rn)->type = type;
+		(*rn)->res.bitmask.bitop = bitop;
+		(*rn)->res.bitmask.prop = prop;
+		(*rn)->res.bitmask.mask = mask;
+		return TRUE;
+	}
+
+	case E2K_RESTRICTION_SIZE:
+	{
+		/* FIXME */
+		return FALSE;
+	}
+
+	case E2K_RESTRICTION_EXIST:
+	{
+		E2kRuleProp prop;
+
+		if (!e2k_rule_extract_proptag (data, len, &prop))
+			return FALSE;
+
+		*rn = g_new0 (E2kRestriction, 1);
+		(*rn)->type = type;
+		(*rn)->res.exist.prop = prop;
+		return TRUE;
+	}
+
+	case E2K_RESTRICTION_SUBRESTRICTION:
+	{
+		E2kRuleProp subtable;
+		E2kRestriction *subrn;
+
+		if (!e2k_rule_extract_proptag (data, len, &subtable) ||
+		    !extract_restriction (data, len, &subrn))
+			return FALSE;
+
+		*rn = g_new0 (E2kRestriction, 1);
+		(*rn)->type = type;
+		(*rn)->res.sub.subtable = subtable;
+		(*rn)->res.sub.rn = subrn;
+		return TRUE;
+	}
+
+	case E2K_RESTRICTION_COMMENT:
+	{
+		guint8 nprops, dummy;
+		E2kPropValue *props;
+		gint i;
+
+		if (!e2k_rule_extract_byte (data, len, &nprops))
+			return FALSE;
+
+		props = g_new0 (E2kPropValue, nprops);
+		for (i = 0; i < nprops; i++) {
+			if (!e2k_rule_extract_propvalue (data, len, &props[i])) {
+				while (i--)
+					e2k_rule_free_propvalue (&props[i]);
+				g_free (props);
+				return FALSE;
+			}
+		}
+
+		*rn = g_new0 (E2kRestriction, 1);
+		(*rn)->type = type;
+		(*rn)->res.comment.nprops = nprops;
+		(*rn)->res.comment.props = props;
+
+		/* FIXME: There is always a "1" byte here, but I don't
+		 * know why.
+		 */
+		if (!e2k_rule_extract_byte (data, len, &dummy) || dummy != 1) {
+			e2k_restriction_unref (*rn);
+			return FALSE;
+		}
+
+		if (!extract_restriction (data, len, &(*rn)->res.comment.rn)) {
+			e2k_restriction_unref (*rn);
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	default:
+		return FALSE;
+	}
+}
+
+/**
+ * e2k_restriction_extract:
+ * @data: pointer to data pointer
+ * @len: pointer to data length
+ * @rn: pointer to variable to store the extracted restriction in
+ *
+ * Attempts to extract a restriction from * data, which contains
+ * a binary-encoded restriction from a server-side rule.
+ *
+ * On success, * rn will contain the extracted restriction, * data
+ * will be advanced past the end of the restriction data, and * len
+ * will be decremented accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_restriction_extract (guint8 **data, gint *len, E2kRestriction **rn)
+{
+	guint32 rnlen;
+
+	if (!e2k_rule_extract_uint32 (data, len, &rnlen))
+		return FALSE;
+	if (rnlen > *len)
+		return FALSE;
+
+	if (rnlen == 1 && (*data)[0] == 0xFF) {
+		(*data)++;
+		(*len)--;
+		*rn = NULL;
+		return TRUE;
+	}
+
+	if (*len < 2)
+		return FALSE;
+	if ((*data)[0] != 0 || (*data)[1] != 0)
+		return FALSE;
+	(*data) += 2;
+	(*len) -= 2;
+
+	return extract_restriction (data, len, rn);
+}
+
+static void
+append_restriction (GByteArray *ba, E2kRestriction *rn)
+{
+	gint i;
+
+	e2k_rule_append_byte (ba, rn->type);
+
+	switch (rn->type) {
+	case E2K_RESTRICTION_AND:
+	case E2K_RESTRICTION_OR:
+		e2k_rule_append_uint16 (ba, rn->res.and.nrns);
+		for (i = 0; i < rn->res.and.nrns; i++)
+			append_restriction (ba, rn->res.and.rns[i]);
+		break;
+
+	case E2K_RESTRICTION_NOT:
+		append_restriction (ba, rn->res.not.rn);
+		break;
+
+	case E2K_RESTRICTION_CONTENT:
+		e2k_rule_append_uint32 (ba, rn->res.content.fuzzy_level);
+		e2k_rule_append_proptag (ba, &rn->res.content.pv.prop);
+		e2k_rule_append_propvalue (ba, &rn->res.content.pv);
+		break;
+
+	case E2K_RESTRICTION_PROPERTY:
+		e2k_rule_append_byte (ba, rn->res.property.relop);
+		e2k_rule_append_proptag (ba, &rn->res.property.pv.prop);
+		e2k_rule_append_propvalue (ba, &rn->res.property.pv);
+		break;
+
+	case E2K_RESTRICTION_COMPAREPROPS:
+		/* FIXME */
+		break;
+
+	case E2K_RESTRICTION_BITMASK:
+		e2k_rule_append_byte (ba, rn->res.bitmask.bitop);
+		e2k_rule_append_proptag (ba, &rn->res.bitmask.prop);
+		e2k_rule_append_uint32 (ba, rn->res.bitmask.mask);
+		break;
+
+	case E2K_RESTRICTION_SIZE:
+		break;
+
+	case E2K_RESTRICTION_EXIST:
+		e2k_rule_append_proptag (ba, &rn->res.exist.prop);
+		break;
+
+	case E2K_RESTRICTION_SUBRESTRICTION:
+		e2k_rule_append_proptag (ba, &rn->res.sub.subtable);
+		append_restriction (ba, rn->res.sub.rn);
+		break;
+
+	case E2K_RESTRICTION_COMMENT:
+		e2k_rule_append_byte (ba, rn->res.comment.nprops);
+
+		for (i = 0; i < rn->res.comment.nprops; i++)
+			e2k_rule_append_propvalue (ba, &rn->res.comment.props[i]);
+
+		/* FIXME: There is always a "1" byte here, but I don't
+		 * know why.
+		 */
+		e2k_rule_append_byte (ba, 1);
+
+		append_restriction (ba, rn->res.comment.rn);
+		break;
+
+	default:
+		break;
+	}
+}
+
+/**
+ * e2k_restriction_append:
+ * @ba: a buffer into which a server-side rule is being constructed
+ * @rn: the restriction to append to @ba
+ *
+ * Appends @rn to @ba as part of a server-side rule.
+ **/
+void
+e2k_restriction_append (GByteArray *ba, E2kRestriction *rn)
+{
+	gint rnlen_offset, rnlen;
+
+	if (!rn) {
+		e2k_rule_append_uint32 (ba, 1);
+		e2k_rule_append_byte (ba, 0xFF);
+		return;
+	}
+
+	/* Save space for the length field */
+	rnlen_offset = ba->len;
+	e2k_rule_append_uint32 (ba, 0);
+
+	/* FIXME: ??? */
+	e2k_rule_append_uint16 (ba, 0);
+
+	append_restriction (ba, rn);
+
+	rnlen = ba->len - rnlen_offset - 4;
+	e2k_rule_write_uint32 (ba->data + rnlen_offset, rnlen);
+}
diff --git a/server/lib/e2k-restriction.h b/server/lib/e2k-restriction.h
new file mode 100644
index 0000000..a725641
--- /dev/null
+++ b/server/lib/e2k-restriction.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_RESTRICTION_H__
+#define __E2K_RESTRICTION_H__
+
+#include "e2k-types.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E2K_RELOP_LT = 0,
+	E2K_RELOP_LE = 1,
+	E2K_RELOP_GT = 2,
+	E2K_RELOP_GE = 3,
+	E2K_RELOP_EQ = 4,
+	E2K_RELOP_NE = 5,
+	E2K_RELOP_RE = 6,
+
+	E2K_RELOP_DL_MEMBER = 100
+} E2kRestrictionRelop;
+
+typedef enum {
+	E2K_BMR_EQZ = 0,
+	E2K_BMR_NEZ = 1
+} E2kRestrictionBitop;
+
+typedef enum {
+	E2K_FL_FULLSTRING     = 0x00000,
+	E2K_FL_SUBSTRING      = 0x00001,
+	E2K_FL_PREFIX         = 0x00002,
+	E2K_FL_SUFFIX         = 0x00003, /* Not a MAPI constant */
+
+	E2K_FL_IGNORECASE     = 0x10000,
+	E2K_FL_IGNORENONSPACE = 0x20000,
+	E2K_FL_LOOSE          = 0x40000
+} E2kRestrictionFuzzyLevel;
+
+#define E2K_FL_MATCH_TYPE(fl) ((fl) & 0x3)
+
+E2kRestriction *e2k_restriction_and         (gint                   nrns,
+					     E2kRestriction      **rns,
+					     gboolean              unref);
+E2kRestriction *e2k_restriction_andv        (E2kRestriction       *rn, ...);
+E2kRestriction *e2k_restriction_or          (gint                   nrns,
+					     E2kRestriction      **rns,
+					     gboolean              unref);
+E2kRestriction *e2k_restriction_orv         (E2kRestriction       *rn, ...);
+E2kRestriction *e2k_restriction_not         (E2kRestriction       *rn,
+					     gboolean              unref);
+E2kRestriction *e2k_restriction_content     (const gchar           *propname,
+					     E2kRestrictionFuzzyLevel fuzzy_level,
+					     const gchar           *value);
+E2kRestriction *e2k_restriction_prop_bool   (const gchar           *propname,
+					     E2kRestrictionRelop   relop,
+					     gboolean              value);
+E2kRestriction *e2k_restriction_prop_int    (const gchar           *propname,
+					     E2kRestrictionRelop   relop,
+					     gint                   value);
+E2kRestriction *e2k_restriction_prop_date   (const gchar           *propname,
+					     E2kRestrictionRelop   relop,
+					     const gchar           *value);
+E2kRestriction *e2k_restriction_prop_string (const gchar           *propname,
+					     E2kRestrictionRelop   relop,
+					     const gchar           *value);
+E2kRestriction *e2k_restriction_prop_binary (const gchar           *propname,
+					     E2kRestrictionRelop   relop,
+					     gconstpointer         data,
+					     gint                   len);
+E2kRestriction *e2k_restriction_compare     (const gchar           *propname1,
+					     E2kRestrictionRelop   relop,
+					     const gchar           *propname2);
+E2kRestriction *e2k_restriction_bitmask     (const gchar           *propname,
+					     E2kRestrictionBitop   bitop,
+					     guint32               mask);
+E2kRestriction *e2k_restriction_size        (const gchar           *propname,
+					     E2kRestrictionRelop   relop,
+					     guint32               size);
+E2kRestriction *e2k_restriction_exist       (const gchar           *propname);
+E2kRestriction *e2k_restriction_sub         (const gchar           *subtable,
+					     E2kRestriction       *rn,
+					     gboolean              unref);
+
+void            e2k_restriction_ref         (E2kRestriction       *rn);
+void            e2k_restriction_unref       (E2kRestriction       *rn);
+
+gchar           *e2k_restriction_to_sql      (E2kRestriction       *rn);
+
+gboolean        e2k_restriction_extract     (guint8              **data,
+					     gint                  *len,
+					     E2kRestriction      **rn);
+void            e2k_restriction_append      (GByteArray           *ba,
+					     E2kRestriction       *rn);
+
+G_END_DECLS
+
+#endif /* __E2K_RESTRICTION_H__ */
diff --git a/server/lib/e2k-result.c b/server/lib/e2k-result.c
new file mode 100644
index 0000000..12be98a
--- /dev/null
+++ b/server/lib/e2k-result.c
@@ -0,0 +1,629 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-result.h"
+#include "e2k-http-utils.h"
+#include "e2k-propnames.h"
+#include "e2k-xml-utils.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+
+static void
+prop_get_binary_array (E2kResult *result, const gchar *propname, xmlNode *node)
+{
+	GPtrArray *array;
+
+	array = g_ptr_array_new ();
+	for (node = node->xmlChildrenNode; node; node = node->next) {
+		GByteArray *byte_array;
+
+		byte_array = g_byte_array_new ();
+		if (node->xmlChildrenNode && node->xmlChildrenNode->content) {
+			guchar *data;
+			gsize length = 0;
+
+			data = g_base64_decode (
+				(gchar *) node->xmlChildrenNode->content, &length);
+			g_byte_array_append (byte_array, data, length);
+			g_free (data);
+		}
+		g_ptr_array_add (array, byte_array);
+	}
+
+	e2k_properties_set_binary_array (result->props, propname, array);
+}
+
+static void
+prop_get_string_array (E2kResult *result, const gchar *propname,
+		       E2kPropType real_type, xmlNode *node)
+{
+	GPtrArray *array;
+
+	array = g_ptr_array_new ();
+	for (node = node->xmlChildrenNode; node; node = node->next) {
+		if (node->xmlChildrenNode && node->xmlChildrenNode->content)
+			g_ptr_array_add (array, g_strdup ((gchar *) node->xmlChildrenNode->content));
+		else
+			g_ptr_array_add (array, g_strdup (""));
+	}
+
+	e2k_properties_set_type_as_string_array (result->props, propname,
+						 real_type, array);
+}
+
+static void
+prop_get_binary (E2kResult *result, const gchar *propname, xmlNode *node)
+{
+	GByteArray *byte_array;
+
+	byte_array = g_byte_array_new ();
+	if (node->xmlChildrenNode && node->xmlChildrenNode->content) {
+		guchar *data;
+		gsize length = 0;
+
+		data = g_base64_decode (
+			(gchar *) node->xmlChildrenNode->content, &length);
+		g_byte_array_append (byte_array, data, length);
+		g_free (data);
+	}
+
+	e2k_properties_set_binary (result->props, propname, byte_array);
+}
+
+static void
+prop_get_string (E2kResult *result, const gchar *propname,
+		 E2kPropType real_type, xmlNode *node)
+{
+	gchar *content;
+
+	if (node->xmlChildrenNode && node->xmlChildrenNode->content)
+		content = g_strdup ((gchar *) node->xmlChildrenNode->content);
+	else
+		content = g_strdup ("");
+
+	e2k_properties_set_type_as_string (result->props, propname,
+					   real_type, content);
+}
+
+static void
+prop_get_xml (E2kResult *result, const gchar *propname, xmlNode *node)
+{
+	e2k_properties_set_xml (result->props, propname,
+				xmlCopyNode (node, TRUE));
+}
+
+static void
+prop_parse (xmlNode *node, E2kResult *result)
+{
+	gchar *name;
+	xmlChar *type;
+
+	g_return_if_fail (node->ns != NULL);
+
+	if (!result->props)
+		result->props = e2k_properties_new ();
+
+	if (!strncmp ((gchar *) node->ns->href, E2K_NS_MAPI_ID, E2K_NS_MAPI_ID_LEN)) {
+		/* Reinsert the illegal initial '0' that was stripped out
+		 * by sanitize_bad_multistatus. (This also covers us in
+		 * the cases where the server returns the property without
+		 * the '0'.)
+		 */
+		name = g_strdup_printf ("%s0%s", node->ns->href, node->name);
+	} else
+		name = g_strdup_printf ("%s%s", node->ns->href, node->name);
+
+	type = xmlGetNsProp (
+		node,
+		(xmlChar *) "dt",
+		(xmlChar *) E2K_NS_TYPE);
+	if (type && !xmlStrcmp (type, (xmlChar *) "mv.bin.base64"))
+		prop_get_binary_array (result, name, node);
+	else if (type && !xmlStrcmp (type, (xmlChar *) "mv.int"))
+		prop_get_string_array (result, name, E2K_PROP_TYPE_INT_ARRAY, node);
+	else if (type && !xmlStrncmp (type, (xmlChar *) "mv.", 3))
+		prop_get_string_array (result, name, E2K_PROP_TYPE_STRING_ARRAY, node);
+	else if (type && !xmlStrcmp (type, (xmlChar *) "bin.base64"))
+		prop_get_binary (result, name, node);
+	else if (type && !xmlStrcmp (type, (xmlChar *) "int"))
+		prop_get_string (result, name, E2K_PROP_TYPE_INT, node);
+	else if (type && !xmlStrcmp (type, (xmlChar *) "boolean"))
+		prop_get_string (result, name, E2K_PROP_TYPE_BOOL, node);
+	else if (type && !xmlStrcmp (type, (xmlChar *) "float"))
+		prop_get_string (result, name, E2K_PROP_TYPE_FLOAT, node);
+	else if (type && !xmlStrcmp (type, (xmlChar *) "dateTime.tz"))
+		prop_get_string (result, name, E2K_PROP_TYPE_DATE, node);
+	else if (!node->xmlChildrenNode ||
+		 !node->xmlChildrenNode->xmlChildrenNode)
+		prop_get_string (result, name, E2K_PROP_TYPE_STRING, node);
+	else
+		prop_get_xml (result, name, node);
+
+	if (type)
+		xmlFree (type);
+	g_free (name);
+}
+
+static void
+propstat_parse (xmlNode *node, E2kResult *result)
+{
+	node = node->xmlChildrenNode;
+	if (!E2K_IS_NODE (node, "DAV:", "status"))
+		return;
+	result->status = e2k_http_parse_status (
+		(gchar *) node->xmlChildrenNode->content);
+	if (result->status != E2K_HTTP_OK)
+		return;
+
+	node = node->next;
+	if (!E2K_IS_NODE (node, "DAV:", "prop"))
+		return;
+
+	for (node = node->xmlChildrenNode; node; node = node->next) {
+		if (node->type == XML_ELEMENT_NODE)
+			prop_parse (node, result);
+	}
+}
+
+static void
+e2k_result_clear (E2kResult *result)
+{
+	xmlFree (result->href);
+	result->href = NULL;
+	if (result->props) {
+		e2k_properties_free (result->props);
+		result->props = NULL;
+	}
+}
+
+/**
+ * e2k_results_array_new:
+ *
+ * Creates a new results array
+ *
+ * Return value: the array
+ **/
+GArray *
+e2k_results_array_new (void)
+{
+	return g_array_new (FALSE, FALSE, sizeof (E2kResult));
+}
+
+/* Properties in the /mapi/id/{...} namespaces are usually (though not
+ * always) returned with names that start with '0', which is illegal
+ * and makes libxml choke. So we preprocess them to fix that.
+ */
+static gchar *
+sanitize_bad_multistatus (const gchar *buf, gint len)
+{
+	GString *body;
+	const gchar *p;
+	gint start, end;
+	gchar ns, badprop[7], *ret;
+
+	/* If there are no "mapi/id/{...}" namespace declarations, then
+	 * we don't need any cleanup.
+	 */
+	if (!memchr (buf, '{', len))
+		return NULL;
+
+	body = g_string_new_len (buf, len);
+
+	/* Find the start and end of namespace declarations */
+	p = strstr (body->str, " xmlns:");
+	g_return_val_if_fail (p != NULL, NULL);
+	start = p + 1 - body->str;
+
+	p = strchr (p, '>');
+	g_return_val_if_fail (p != NULL, NULL);
+	end = p - body->str;
+
+	while (1) {
+		if (strncmp (body->str + start, "xmlns:", 6) != 0)
+			break;
+		if (strncmp (body->str + start + 7, "=\"", 2) != 0)
+			break;
+		if (strncmp (body->str + start + 9, E2K_NS_MAPI_ID, E2K_NS_MAPI_ID_LEN) != 0)
+			goto next;
+
+		ns = body->str[start + 6];
+
+		/* Find properties in this namespace and strip the
+		 * initial '0' from their names to make them valid
+		 * XML NCNames.
+		 */
+		snprintf (badprop, 6, "<%c:0x", ns);
+		while ((p = strstr (body->str, badprop)))
+			g_string_erase (body, p + 3 - body->str, 1);
+		snprintf (badprop, 7, "</%c:0x", ns);
+		while ((p = strstr (body->str, badprop)))
+			g_string_erase (body, p + 4 - body->str, 1);
+
+	next:
+		p = strchr (body->str + start, '"');
+		if (!p)
+			break;
+		p = strchr (p + 1, '"');
+		if (!p)
+			break;
+		if (p[1] != ' ')
+			break;
+
+		start = p + 2 - body->str;
+	}
+
+	ret = body->str;
+	g_string_free (body, FALSE);
+	return ret;
+}
+
+/**
+ * e2k_results_array_add_from_multistatus:
+ * @results_array: a results array, created by e2k_results_array_new()
+ * @msg: a 207 Multi-Status response
+ *
+ * Constructs an #E2kResult for each response in @msg and appends them
+ * to @results_array.
+ **/
+void
+e2k_results_array_add_from_multistatus (GArray *results_array,
+					SoupMessage *msg)
+{
+	xmlDoc *doc;
+	xmlNode *node, *rnode;
+	E2kResult result;
+	gchar *body;
+
+	g_return_if_fail (msg->status_code == E2K_HTTP_MULTI_STATUS);
+
+	body = sanitize_bad_multistatus (msg->response_body->data,
+					 msg->response_body->length);
+	if (body) {
+		doc = e2k_parse_xml (body, -1);
+		g_free (body);
+	} else {
+		doc = e2k_parse_xml (msg->response_body->data,
+				     msg->response_body->length);
+	}
+	if (!doc)
+		return;
+	node = doc->xmlRootNode;
+	if (!node || !E2K_IS_NODE (node, "DAV:", "multistatus")) {
+		xmlFree (doc);
+		return;
+	}
+
+	for (node = node->xmlChildrenNode; node; node = node->next) {
+		if (!E2K_IS_NODE (node, "DAV:", "response") ||
+		    !node->xmlChildrenNode)
+			continue;
+
+		memset (&result, 0, sizeof (result));
+		result.status = E2K_HTTP_OK; /* sometimes omitted if Brief */
+
+		for (rnode = node->xmlChildrenNode; rnode; rnode = rnode->next) {
+			if (rnode->type != XML_ELEMENT_NODE)
+				continue;
+
+			if (E2K_IS_NODE (rnode, "DAV:", "href"))
+				result.href = (gchar *) xmlNodeGetContent (rnode);
+			else if (E2K_IS_NODE (rnode, "DAV:", "status")) {
+				result.status = e2k_http_parse_status (
+					(gchar *) rnode->xmlChildrenNode->content);
+			} else if (E2K_IS_NODE (rnode, "DAV:", "propstat"))
+				propstat_parse (rnode, &result);
+			else
+				prop_parse (rnode, &result);
+		}
+
+		if (result.href) {
+			if (E2K_HTTP_STATUS_IS_SUCCESSFUL (result.status) &&
+			    !result.props)
+				result.props = e2k_properties_new ();
+			g_array_append_val (results_array, result);
+		} else
+			e2k_result_clear (&result);
+	}
+
+	xmlFreeDoc (doc);
+}
+
+/**
+ * e2k_results_array_free:
+ * @results_array: the array
+ * @free_results: whether or not to also free the contents of the array
+ *
+ * Frees @results_array, and optionally its contents
+ **/
+void
+e2k_results_array_free (GArray *results_array, gboolean free_results)
+{
+	if (free_results) {
+		e2k_results_free ((E2kResult *)results_array->data,
+				  results_array->len);
+	}
+	g_array_free (results_array, FALSE);
+}
+
+/**
+ * e2k_results_from_multistatus:
+ * @msg: a 207 Multi-Status response
+ * @results: pointer to a variable to store an array of E2kResult in
+ * @nresults: pointer to a variable to store the length of * results in
+ *
+ * Parses @msg and puts the results in * results and * nresults 
+ * The caller should free the data with e2k_results_free()
+ **/
+void
+e2k_results_from_multistatus (SoupMessage *msg,
+			      E2kResult **results, gint *nresults)
+{
+	GArray *results_array;
+
+	results_array = e2k_results_array_new ();
+	e2k_results_array_add_from_multistatus (results_array, msg);
+
+	*results = (E2kResult *)results_array->data;
+	*nresults = results_array->len;
+	e2k_results_array_free (results_array, FALSE);
+}
+
+/**
+ * e2k_results_copy:
+ * @results: a results array returned from e2k_results_from_multistatus()
+ * @nresults: the length of @results
+ *
+ * Performs a deep copy of @results
+ *
+ * Return value: a copy of @results.
+ **/
+E2kResult *
+e2k_results_copy (E2kResult *results, gint nresults)
+{
+	GArray *results_array = NULL;
+	E2kResult result, *new_results;
+	gint i;
+
+	results_array = g_array_new (TRUE, FALSE, sizeof (E2kResult));
+	for (i = 0; i < nresults; i++) {
+		result.href   = xmlMemStrdup (results[i].href);
+		result.status = results[i].status;
+		result.props  = e2k_properties_copy (results[i].props);
+
+		g_array_append_val (results_array, result);
+	}
+
+	new_results = (E2kResult *) (results_array->data);
+	g_array_free (results_array, FALSE);
+	return new_results;
+}
+
+/**
+ * e2k_results_free:
+ * @results: a results array
+ * @nresults: the length of @results
+ *
+ * Frees the data in @results.
+ **/
+void
+e2k_results_free (E2kResult *results, gint nresults)
+{
+	gint i;
+
+	for (i = 0; i < nresults; i++)
+		e2k_result_clear (&results[i]);
+	g_free (results);
+}
+
+/* Iterators */
+struct E2kResultIter {
+	E2kContext *ctx;
+	E2kOperation *op;
+	E2kHTTPStatus status;
+
+	E2kResult *results;
+	gint nresults, next;
+	gint first, total;
+	gboolean ascending;
+
+	E2kResultIterFetchFunc fetch_func;
+	E2kResultIterFreeFunc free_func;
+	gpointer user_data;
+};
+
+static void
+iter_fetch (E2kResultIter *iter)
+{
+	if (iter->nresults) {
+		if (iter->ascending)
+			iter->first += iter->nresults;
+		else
+			iter->first -= iter->nresults;
+		e2k_results_free (iter->results, iter->nresults);
+		iter->nresults = 0;
+	}
+
+	iter->status = iter->fetch_func (iter, iter->ctx, iter->op,
+					 &iter->results,
+					 &iter->nresults,
+					 &iter->first,
+					 &iter->total,
+					 iter->user_data);
+	iter->next = 0;
+}
+
+/**
+ * e2k_result_iter_new:
+ * @ctx: an #E2kContext
+ * @op: an #E2kOperation, to use for cancellation
+ * @ascending: %TRUE if results should be returned in ascending
+ * order, %FALSE if they should be returned in descending order
+ * @total: the total number of results that will be returned, or -1
+ * if not yet known
+ * @fetch_func: function to call to fetch more results
+ * @free_func: function to call when the iterator is freed
+ * @user_data: data to pass to @fetch_func and @free_func
+ *
+ * Creates a object that can be used to return the results of
+ * a Multi-Status query on @ctx.
+ *
+ * @fetch_func will be called to fetch results, and it may update the
+ * #first and #total fields if necessary. If @ascending is %TRUE, then
+ * e2k_result_iter_next() will first return the first result, then the
+ * second result, etc. If @ascending is %FALSE, it will return the
+ * last result, then the second-to-last result, etc.
+ *
+ * When all of the results returned by the first @fetch_func call have
+ * been returned to the caller, @fetch_func will be called again to
+ * get more results. This will continue until @fetch_func returns 0
+ * results, or returns an error code.
+ *
+ * Return value: the new iterator
+ **/
+E2kResultIter *
+e2k_result_iter_new (E2kContext *ctx, E2kOperation *op,
+		     gboolean ascending, gint total,
+		     E2kResultIterFetchFunc fetch_func,
+		     E2kResultIterFreeFunc free_func,
+		     gpointer user_data)
+{
+	E2kResultIter *iter;
+
+	iter = g_new0 (E2kResultIter, 1);
+	iter->ctx = g_object_ref (ctx);
+	iter->op = op;
+	iter->ascending = ascending;
+	iter->total = total;
+	iter->fetch_func = fetch_func;
+	iter->free_func = free_func;
+	iter->user_data = user_data;
+
+	iter_fetch (iter);
+
+	return iter;
+}
+
+/**
+ * e2k_result_iter_next:
+ * @iter: an #E2kResultIter
+ *
+ * Returns the next result in the operation being iterated by @iter.
+ * If there are no more results, or if an error occurs, it will return
+ * %NULL. (The return value of e2k_result_iter_free() distinguishes
+ * these two cases.)
+ *
+ * Return value: the result, or %NULL
+ **/
+E2kResult *
+e2k_result_iter_next (E2kResultIter *iter)
+{
+	g_return_val_if_fail (iter != NULL, NULL);
+
+	if (iter->nresults == 0)
+		return NULL;
+
+	if (iter->next >= iter->nresults) {
+		iter_fetch (iter);
+		if (iter->nresults == 0)
+			return NULL;
+		if (iter->total <= 0)
+			iter->status = E2K_HTTP_MALFORMED;
+		if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (iter->status))
+			return NULL;
+	}
+
+	return iter->ascending ?
+		&iter->results[iter->next++] :
+		&iter->results[iter->nresults - ++iter->next];
+}
+
+/**
+ * e2k_result_iter_get_index:
+ * @iter: an #E2kResultIter
+ *
+ * Returns the index of the current result in the complete list of
+ * results. Note that for a descending search, %index will start at
+ * %total - 1 and count backwards to 0.
+ *
+ * Return value: the index of the current result
+ **/
+gint
+e2k_result_iter_get_index (E2kResultIter *iter)
+{
+	g_return_val_if_fail (iter != NULL, -1);
+
+	return iter->ascending ?
+		iter->first + iter->next - 1 :
+		iter->first + (iter->nresults - iter->next);
+}
+
+/**
+ * e2k_result_iter_get_total:
+ * @iter: an #E2kResultIter
+ *
+ * Returns the total number of results expected for @iter. Note that
+ * in some cases, this may change while the results are being iterated
+ * (if objects that match the query are added to or removed from the
+ * folder).
+ *
+ * Return value: the total number of results expected
+ **/
+gint
+e2k_result_iter_get_total (E2kResultIter *iter)
+{
+	g_return_val_if_fail (iter != NULL, -1);
+
+	return iter->total;
+}
+
+/**
+ * e2k_result_iter_free:
+ * @iter: an #E2kResultIter
+ *
+ * Frees @iter and all associated memory, and returns a status code
+ * indicating whether it ended successfully or not. (Note that the
+ * status may be %E2K_HTTP_OK rather than %E2K_HTTP_MULTI_STATUS.)
+ *
+ * Return value: the final status
+ **/
+E2kHTTPStatus
+e2k_result_iter_free (E2kResultIter *iter)
+{
+	E2kHTTPStatus status;
+
+	g_return_val_if_fail (iter != NULL, E2K_HTTP_MALFORMED);
+
+	status = iter->status;
+	if (iter->nresults)
+		e2k_results_free (iter->results, iter->nresults);
+	iter->free_func (iter, iter->user_data);
+	g_object_unref (iter->ctx);
+	g_free (iter);
+
+	return status;
+}
diff --git a/server/lib/e2k-result.h b/server/lib/e2k-result.h
new file mode 100644
index 0000000..20c418a
--- /dev/null
+++ b/server/lib/e2k-result.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_RESULT_H__
+#define __E2K_RESULT_H__
+
+#include <libsoup/soup-message.h>
+#include "e2k-properties.h"
+#include "e2k-types.h"
+#include "e2k-http-utils.h"
+
+typedef struct {
+	gchar *href;
+	gint status;
+	E2kProperties *props;
+} E2kResult;
+
+void       e2k_results_from_multistatus           (SoupMessage  *msg,
+						   E2kResult   **results,
+						   gint          *nresults);
+
+E2kResult *e2k_results_copy                       (E2kResult    *results,
+						   gint           nresults);
+
+void       e2k_results_free                       (E2kResult    *results,
+						   gint           nresults);
+
+GArray    *e2k_results_array_new                  (void);
+
+void       e2k_results_array_add_from_multistatus (GArray       *results_array,
+						   SoupMessage  *msg);
+
+void       e2k_results_array_free                 (GArray       *results_array,
+						   gboolean      free_results);
+
+typedef struct E2kResultIter E2kResultIter;
+
+typedef E2kHTTPStatus (*E2kResultIterFetchFunc) (E2kResultIter *iter,
+						 E2kContext *ctx,
+						 E2kOperation *op,
+						 E2kResult **results,
+						 gint *nresults,
+						 gint *first,
+						 gint *total,
+						 gpointer user_data);
+typedef void          (*E2kResultIterFreeFunc)  (E2kResultIter *iter,
+						 gpointer user_data);
+
+E2kResultIter *e2k_result_iter_new         (E2kContext            *ctx,
+					    E2kOperation          *op,
+					    gboolean               ascending,
+					    gint                    total,
+					    E2kResultIterFetchFunc fetch_func,
+					    E2kResultIterFreeFunc  free_func,
+					    gpointer               user_data);
+
+E2kResult     *e2k_result_iter_next        (E2kResultIter         *iter);
+gint            e2k_result_iter_get_index   (E2kResultIter         *iter);
+gint            e2k_result_iter_get_total   (E2kResultIter         *iter);
+E2kHTTPStatus  e2k_result_iter_free        (E2kResultIter         *iter);
+
+#endif /* __E2K_RESULT_H__ */
diff --git a/server/lib/e2k-rule-xml.c b/server/lib/e2k-rule-xml.c
new file mode 100644
index 0000000..4ccda13
--- /dev/null
+++ b/server/lib/e2k-rule-xml.c
@@ -0,0 +1,932 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "e2k-rule-xml.h"
+#include "e2k-action.h"
+#include "e2k-properties.h"
+#include "e2k-propnames.h"
+#include "e2k-proptags.h"
+#include "e2k-utils.h"
+#include "mapi.h"
+
+static const gchar *contains_types[] = { NULL, "contains", NULL, NULL, NULL, "not contains", NULL, NULL };
+static const gchar *subject_types[] = { "is", "contains", "starts with", NULL, "is not", "not contains", "not starts with", NULL };
+#define E2K_FL_NEGATE 4
+#define E2K_FL_MAX 8
+
+#if 0
+static gboolean
+fuzzy_level_from_name (const gchar *name, const gchar *map[],
+		       gint *fuzzy_level, gboolean *negated)
+{
+	gint i;
+
+	for (i = 0; i < E2K_FL_MAX; i++) {
+		if (map[i] && !strcmp (name, map[i])) {
+			*fuzzy_level = i & ~E2K_FL_NEGATE;
+			*negated = (*fuzzy_level != i);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+#endif
+
+static inline const gchar *
+fuzzy_level_to_name (gint fuzzy_level, gboolean negated, const gchar *map[])
+{
+	fuzzy_level = E2K_FL_MATCH_TYPE (fuzzy_level);
+	if (negated)
+		fuzzy_level |= E2K_FL_NEGATE;
+
+	return map[fuzzy_level];
+}
+
+static const gchar *is_types[] = { NULL, NULL, NULL, NULL, "is", "is not" };
+static const gchar *date_types[] = { "before", "before", "after", "after", NULL, NULL };
+static const gchar *gsizeypes[] = { "less than", "less than", "greater than", "greater than", NULL, NULL };
+
+#if 0
+static gboolean
+relop_from_name (const gchar *name, const gchar *map[],
+		 E2kRestrictionRelop *relop)
+{
+	gint i;
+
+	for (i = 0; i < E2K_RELOP_RE; i++) {
+		if (map[i] && !strcmp (name, map[i])) {
+			*relop = i;
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+#endif
+
+static inline const gchar *
+relop_to_name (E2kRestrictionRelop relop, gboolean negated, const gchar *map[])
+{
+	static const gint negate_map[] = {
+		E2K_RELOP_GE, E2K_RELOP_GT, E2K_RELOP_LE, E2K_RELOP_LT,
+		E2K_RELOP_NE, E2K_RELOP_EQ
+	};
+
+	if (negated)
+		relop = negate_map[relop];
+
+	return map[relop];
+}
+
+/* Check if @rn encodes Outlook's "Message was sent only to me" rule */
+static gboolean
+restriction_is_only_to_me (E2kRestriction *rn)
+{
+	E2kRestriction *sub;
+
+	if (rn->type != E2K_RESTRICTION_AND || rn->res.and.nrns != 3)
+		return FALSE;
+
+	sub = rn->res.and.rns[0];
+	if (sub->type != E2K_RESTRICTION_PROPERTY ||
+	    sub->res.property.relop != E2K_RELOP_EQ ||
+	    sub->res.property.pv.prop.proptag != E2K_PROPTAG_PR_MESSAGE_TO_ME ||
+	    sub->res.property.pv.value == NULL)
+		return FALSE;
+
+	sub = rn->res.and.rns[1];
+	if (sub->type != E2K_RESTRICTION_NOT)
+		return FALSE;
+	sub = sub->res.not.rn;
+	if (sub->type != E2K_RESTRICTION_CONTENT ||
+	    !(sub->res.content.fuzzy_level & E2K_FL_SUBSTRING) ||
+	    sub->res.content.pv.prop.proptag != E2K_PROPTAG_PR_DISPLAY_TO ||
+	    strcmp (sub->res.content.pv.value, ";") != 0)
+		return FALSE;
+
+	sub = rn->res.and.rns[2];
+	if (sub->type != E2K_RESTRICTION_PROPERTY ||
+	    sub->res.content.pv.prop.proptag != E2K_PROPTAG_PR_DISPLAY_CC ||
+	    strcmp (sub->res.content.pv.value, "") != 0)
+		return FALSE;
+
+	return TRUE;
+}
+
+/* Check if @rn encodes Outlook's "is a delegatable meeting request" rule */
+static gboolean
+restriction_is_delegation (E2kRestriction *rn)
+{
+	E2kRestriction *sub;
+
+	if (rn->type != E2K_RESTRICTION_AND || rn->res.and.nrns != 3)
+		return FALSE;
+
+	sub = rn->res.and.rns[0];
+	if (sub->type != E2K_RESTRICTION_CONTENT ||
+	    E2K_FL_MATCH_TYPE (sub->res.content.fuzzy_level) != E2K_FL_PREFIX ||
+	    sub->res.content.pv.prop.proptag != E2K_PROPTAG_PR_MESSAGE_CLASS ||
+	    strcmp (sub->res.content.pv.value, "IPM.Schedule.Meeting") != 0)
+		return FALSE;
+
+	sub = rn->res.and.rns[1];
+	if (sub->type != E2K_RESTRICTION_NOT ||
+	    sub->res.not.rn->type != E2K_RESTRICTION_EXIST ||
+	    sub->res.not.rn->res.exist.prop.proptag != E2K_PROPTAG_PR_DELEGATED_BY_RULE)
+		return FALSE;
+
+	sub = rn->res.and.rns[2];
+	if (sub->type != E2K_RESTRICTION_OR || sub->res.or.nrns != 2)
+		return FALSE;
+
+	sub = rn->res.and.rns[2]->res.or.rns[0];
+	if (sub->type != E2K_RESTRICTION_NOT ||
+	    sub->res.not.rn->type != E2K_RESTRICTION_EXIST ||
+	    sub->res.not.rn->res.exist.prop.proptag != E2K_PROPTAG_PR_SENSITIVITY)
+		return FALSE;
+
+	sub = rn->res.and.rns[2]->res.or.rns[1];
+	if (sub->type != E2K_RESTRICTION_PROPERTY ||
+	    sub->res.property.relop != E2K_RELOP_NE ||
+	    sub->res.property.pv.prop.proptag != E2K_PROPTAG_PR_SENSITIVITY ||
+	    GPOINTER_TO_INT (sub->res.property.pv.value) != MAPI_SENSITIVITY_PRIVATE)
+		return FALSE;
+
+	return TRUE;
+}
+
+static xmlNode *
+new_value (xmlNode *part,
+           const xmlChar *name,
+           const xmlChar *type,
+           const xmlChar *value)
+{
+	xmlNode *node;
+
+	node = xmlNewChild (part, NULL, (xmlChar *) "value", NULL);
+	xmlSetProp (node, (xmlChar *) "name", name);
+	xmlSetProp (node, (xmlChar *) "type", type);
+	if (value)
+		xmlSetProp (node, (xmlChar *) "value", value);
+
+	return node;
+}
+
+static xmlNode *
+new_value_int (xmlNode *part,
+               const xmlChar *name,
+               const xmlChar *type,
+               const xmlChar *value_name,
+               glong value)
+{
+	xmlNode *node;
+	gchar *str;
+
+	node = xmlNewChild (part, NULL, (xmlChar *) "value", NULL);
+	xmlSetProp (node, (xmlChar *) "name", name);
+	xmlSetProp (node, (xmlChar *) "type", type);
+
+	str = g_strdup_printf ("%ld", value);
+	xmlSetProp (node, (xmlChar *) value_name, (xmlChar *) str);
+	g_free (str);
+
+	return node;
+}
+
+static xmlNode *
+new_part (const xmlChar *part_name)
+{
+	xmlNode *part;
+
+	part = xmlNewNode (NULL, (xmlChar *) "part");
+	xmlSetProp (part, (xmlChar *) "name", part_name);
+	return part;
+}
+
+static xmlNode *
+match (const xmlChar *part_name,
+       const xmlChar *value_name,
+       const xmlChar *value_value,
+       const xmlChar *string_name,
+       const xmlChar *string_value)
+{
+	xmlNode *part, *value;
+
+	part = new_part (part_name);
+	value = new_value (
+		part, value_name, (xmlChar *) "option", value_value);
+	value = new_value (part, string_name, (xmlChar *) "string", NULL);
+	xmlNewTextChild (value, NULL, (xmlChar *) "string", string_value);
+
+	return part;
+}
+
+static xmlNode *
+message_is (const xmlChar *name,
+            const xmlChar *type_name,
+            const xmlChar *kind,
+            gboolean negated)
+{
+	xmlNode *part;
+
+	part = new_part (name);
+	new_value (
+		part, type_name, (xmlChar *) "option",
+		negated ? (xmlChar *) "is not" : (xmlChar *) "is");
+	new_value (part, (xmlChar *) "kind", (xmlChar *) "option", kind);
+
+	return part;
+}
+
+static xmlNode *
+address_is (E2kRestriction *comment_rn, gboolean recipients, gboolean negated)
+{
+	xmlNode *part;
+	E2kRestriction *rn;
+	E2kPropValue *pv;
+	const gchar *relation, *display_name, *p;
+	gchar *addr, *full_addr;
+	GByteArray *ba;
+	gint i;
+
+	rn = comment_rn->res.comment.rn;
+	if (rn->type != E2K_RESTRICTION_PROPERTY ||
+	    rn->res.property.relop != E2K_RELOP_EQ)
+		return NULL;
+	pv = &rn->res.property.pv;
+
+	if ((recipients && pv->prop.proptag != E2K_PROPTAG_PR_SEARCH_KEY) ||
+	    (!recipients && pv->prop.proptag != E2K_PROPTAG_PR_SENDER_SEARCH_KEY))
+		return NULL;
+
+	relation = relop_to_name (rn->res.property.relop, negated, is_types);
+	if (!relation)
+		return NULL;
+
+	/* Extract the address part */
+	ba = pv->value;
+	p = strchr ((gchar *)ba->data, ':');
+	if (p)
+		addr = g_ascii_strdown (p + 1, -1);
+	else
+		addr = g_ascii_strdown ((gchar *)ba->data, -1);
+
+	/* Find the display name in the comment */
+	display_name = NULL;
+	for (i = 0; i < comment_rn->res.comment.nprops; i++) {
+		pv = &comment_rn->res.comment.props[i];
+		if (E2K_PROPTAG_TYPE (pv->prop.proptag) == E2K_PT_UNICODE) {
+			display_name = pv->value;
+			break;
+		}
+	}
+
+	if (display_name)
+		full_addr = g_strdup_printf ("%s <%s>", display_name, addr);
+	else
+		full_addr = g_strdup_printf ("<%s>", addr);
+
+	if (recipients) {
+		part = match (
+			(xmlChar *) "recipient",
+			(xmlChar *) "recipient-type",
+			(xmlChar *) relation,
+			(xmlChar *) "recipient",
+			(xmlChar *) full_addr);
+	} else {
+		part = match (
+			(xmlChar *) "sender",
+			(xmlChar *) "sender-type",
+			(xmlChar *) relation,
+			(xmlChar *) "sender",
+			(xmlChar *) full_addr);
+	}
+
+	g_free (full_addr);
+	g_free (addr);
+	return part;
+}
+
+static gboolean
+restriction_to_xml (E2kRestriction *rn, xmlNode *partset,
+		    E2kRestrictionType wrap_type, gboolean negated)
+{
+	xmlNode *part, *value, *node;
+	E2kPropValue *pv;
+	const gchar *match_type;
+	gint i;
+
+	switch (rn->type) {
+	case E2K_RESTRICTION_AND:
+	case E2K_RESTRICTION_OR:
+		/* Check for special rules */
+		if (restriction_is_only_to_me (rn)) {
+			part = message_is (
+				(xmlChar *) "message-to-me",
+				(xmlChar *) "message-to-me-type",
+				(xmlChar *) "only", negated);
+			break;
+		} else if (restriction_is_delegation (rn)) {
+			part = message_is (
+				(xmlChar *) "special-message",
+				(xmlChar *) "special-message-type",
+				(xmlChar *) "delegated-meeting-request",
+				negated);
+			break;
+		}
+
+		/* If we are inside an "and" and hit another "and",
+		 * we can just remove the extra level:
+		 *    (and foo (and bar baz) quux) =>
+		 *    (and foo bar baz quux)
+		 * Likewise for "or"s.
+		 *
+		 * If we are inside an "and" and hit a "(not (or" (or
+		 * vice versa), we can use DeMorgan's Law and then
+		 * apply the above rule:
+		 *    (and foo (not (or bar baz)) quux) =>
+		 *    (and foo (and (not bar) (not baz)) quux) =>
+		 *    (and foo (not bar) (not baz) quux)
+		 *
+		 * This handles both cases.
+		 */
+		if ((rn->type == wrap_type && !negated) ||
+		    (rn->type != wrap_type && negated)) {
+			for (i = 0; i < rn->res.and.nrns; i++) {
+				if (!restriction_to_xml (rn->res.and.rns[i],
+							 partset, wrap_type,
+							 negated))
+					return FALSE;
+			}
+			return TRUE;
+		}
+
+		/* Otherwise, we have a rule that can't be expressed
+		 * as "match all" or "match any".
+		 */
+		return FALSE;
+
+	case E2K_RESTRICTION_NOT:
+		return restriction_to_xml (rn->res.not.rn, partset,
+					   wrap_type, !negated);
+
+	case E2K_RESTRICTION_CONTENT:
+	{
+		gint fuzzy_level = E2K_FL_MATCH_TYPE (rn->res.content.fuzzy_level);
+
+		pv = &rn->res.content.pv;
+
+		switch (pv->prop.proptag) {
+		case E2K_PROPTAG_PR_BODY:
+			match_type = fuzzy_level_to_name (fuzzy_level, negated,
+							  contains_types);
+			if (!match_type)
+				return FALSE;
+
+			part = match (
+				(xmlChar *) "body",
+				(xmlChar *) "body-type",
+				(xmlChar *) match_type,
+				(xmlChar *) "word",
+				(xmlChar *) pv->value);
+			break;
+
+		case E2K_PROPTAG_PR_SUBJECT:
+			match_type = fuzzy_level_to_name (fuzzy_level, negated,
+							  subject_types);
+			if (!match_type)
+				return FALSE;
+
+			part = match (
+				(xmlChar *) "subject",
+				(xmlChar *) "subject-type",
+				(xmlChar *) match_type,
+				(xmlChar *) "subject",
+				(xmlChar *) pv->value);
+			break;
+
+		case E2K_PROPTAG_PR_TRANSPORT_MESSAGE_HEADERS:
+			match_type = fuzzy_level_to_name (fuzzy_level, negated,
+							  contains_types);
+			if (!match_type)
+				return FALSE;
+
+			part = match (
+				(xmlChar *) "full-headers",
+				(xmlChar *) "full-headers-type",
+				(xmlChar *) match_type,
+				(xmlChar *) "word",
+				(xmlChar *) pv->value);
+			break;
+
+		case E2K_PROPTAG_PR_MESSAGE_CLASS:
+			if ((fuzzy_level == E2K_FL_FULLSTRING) &&
+			    !strcmp (pv->value, "IPM.Note.Rules.OofTemplate.Microsoft")) {
+				part = message_is (
+					(xmlChar *) "special-message",
+					(xmlChar *) "special-message-type",
+					(xmlChar *) "oof", negated);
+			} else if ((fuzzy_level == E2K_FL_PREFIX) &&
+				   !strcmp (pv->value, "IPM.Schedule.Meeting")) {
+				part = message_is (
+					(xmlChar *) "special-message",
+					(xmlChar *) "special-message-type",
+					(xmlChar *) "meeting-request", negated);
+			} else
+				return FALSE;
+
+			break;
+
+		default:
+			return FALSE;
+		}
+		break;
+	}
+
+	case E2K_RESTRICTION_PROPERTY:
+	{
+		E2kRestrictionRelop relop;
+		const gchar *relation;
+
+		relop = rn->res.property.relop;
+		if (relop >= E2K_RELOP_RE)
+			return FALSE;
+
+		pv = &rn->res.property.pv;
+
+		switch (pv->prop.proptag) {
+		case E2K_PROPTAG_PR_MESSAGE_TO_ME:
+			if ((relop == E2K_RELOP_EQ && !pv->value) ||
+			    (relop == E2K_RELOP_NE && pv->value))
+				negated = !negated;
+
+			part = message_is (
+				(xmlChar *) "message-to-me",
+				(xmlChar *) "message-to-me-type",
+				(xmlChar *) "to", negated);
+			break;
+
+		case E2K_PROPTAG_PR_MESSAGE_CC_ME:
+			if ((relop == E2K_RELOP_EQ && !pv->value) ||
+			    (relop == E2K_RELOP_NE && pv->value))
+				negated = !negated;
+
+			part = message_is (
+				(xmlChar *) "message-to-me",
+				(xmlChar *) "message-to-me-type",
+				(xmlChar *) "cc", negated);
+			break;
+
+		case E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME:
+		case E2K_PROPTAG_PR_CLIENT_SUBMIT_TIME:
+		{
+			gchar *timestamp;
+
+			relation = relop_to_name (relop, negated, date_types);
+			if (!relation)
+				return FALSE;
+
+			if (pv->prop.proptag == E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME)
+				part = new_part ((xmlChar *) "received-date");
+			else
+				part = new_part ((xmlChar *) "sent-date");
+
+			value = new_value (
+				part,
+				(xmlChar *) "date-spec-type",
+				(xmlChar *) "option",
+				(xmlChar *) relation);
+			value = new_value (
+				part,
+				(xmlChar *) "versus",
+				(xmlChar *) "datespec", NULL);
+
+			node = xmlNewChild (
+				value, NULL, (xmlChar *) "datespec", NULL);
+			xmlSetProp (
+				node,
+				(xmlChar *) "type",
+				(xmlChar *) "1");
+
+			timestamp = g_strdup_printf ("%lu", (gulong)e2k_parse_timestamp (pv->value));
+			xmlSetProp (
+				node,
+				(xmlChar *) "value",
+				(xmlChar *) timestamp);
+			g_free (timestamp);
+			break;
+		}
+
+		case E2K_PROPTAG_PR_MESSAGE_SIZE:
+			relation = relop_to_name (relop, negated, gsizeypes);
+			if (!relation)
+				return FALSE;
+
+			part = new_part ((xmlChar *) "size");
+			new_value (
+				part,
+				(xmlChar *) "size-type",
+				(xmlChar *) "option",
+				(xmlChar *) relation);
+			new_value_int (
+				part,
+				(xmlChar *) "versus",
+				(xmlChar *) "integer",
+				(xmlChar *) "integer",
+				GPOINTER_TO_INT (pv->value) / 1024);
+			break;
+
+		case E2K_PROPTAG_PR_IMPORTANCE:
+			relation = relop_to_name (relop, negated, is_types);
+			if (!relation)
+				return FALSE;
+
+			part = new_part ((xmlChar *) "importance");
+			new_value (
+				part,
+				(xmlChar *) "importance-type",
+				(xmlChar *) "option",
+				(xmlChar *) relation);
+			new_value_int (
+				part,
+				(xmlChar *) "importance",
+				(xmlChar *) "option",
+				(xmlChar *) "value",
+				GPOINTER_TO_INT (pv->value));
+			break;
+
+		case E2K_PROPTAG_PR_SENSITIVITY:
+			relation = relop_to_name (relop, negated, is_types);
+			if (!relation)
+				return FALSE;
+
+			part = new_part ((xmlChar *) "sensitivity");
+			xmlSetProp (
+				part,
+				(xmlChar *) "name",
+				(xmlChar *) "sensitivity");
+			new_value (
+				part,
+				(xmlChar *) "sensitivity-type",
+				(xmlChar *) "option",
+				(xmlChar *) relation);
+			new_value_int (
+				part,
+				(xmlChar *) "sensitivity",
+				(xmlChar *) "option",
+				(xmlChar *) "value",
+				GPOINTER_TO_INT (pv->value));
+			break;
+
+		default:
+			return FALSE;
+		}
+		break;
+	}
+
+	case E2K_RESTRICTION_COMMENT:
+		part = address_is (rn, FALSE, negated);
+		if (!part)
+			return FALSE;
+		break;
+
+	case E2K_RESTRICTION_BITMASK:
+		if (rn->res.bitmask.prop.proptag != E2K_PROPTAG_PR_MESSAGE_FLAGS ||
+		    rn->res.bitmask.mask != MAPI_MSGFLAG_HASATTACH)
+			return FALSE;
+
+		part = new_part ((xmlChar *) "attachments");
+		if (rn->res.bitmask.bitop == E2K_BMR_NEZ) {
+			new_value (
+				part,
+				(xmlChar *) "match-type",
+				(xmlChar *) "option",
+				negated ?
+				(xmlChar *) "not exist" :
+				(xmlChar *) "exist");
+		} else {
+			new_value (
+				part,
+				(xmlChar *) "match-type",
+				(xmlChar *) "option",
+				negated ?
+				(xmlChar *) "exist" :
+				(xmlChar *) "not exist");
+		}
+		break;
+
+	case E2K_RESTRICTION_SUBRESTRICTION:
+		if (rn->res.sub.subtable.proptag != E2K_PROPTAG_PR_MESSAGE_RECIPIENTS)
+			return FALSE;
+		if (rn->res.sub.rn->type != E2K_RESTRICTION_COMMENT)
+			return FALSE;
+
+		part = address_is (rn->res.sub.rn, TRUE, negated);
+		if (!part)
+			return FALSE;
+		break;
+
+	default:
+		return FALSE;
+	}
+
+	xmlAddChild (partset, part);
+	return TRUE;
+}
+
+static gchar *
+stringify_entryid (guint8 *data, gint len)
+{
+	GString *string;
+	gchar *ret;
+	gint i;
+
+	string = g_string_new (NULL);
+
+	for (i = 0; i < len && i < 22; i++)
+		g_string_append_printf (string, "%02x", data[i]);
+	if (i < len && data[i]) {
+		for (; i < len; i++)
+			g_string_append_printf (string, "%02x", data[i]);
+	}
+
+	ret = string->str;
+	g_string_free (string, FALSE);
+	return ret;
+}
+
+static gboolean
+action_to_xml (E2kAction *act, xmlNode *actionset)
+{
+	xmlNode *part, *value;
+	gchar *entryid;
+
+	switch (act->type) {
+	case E2K_ACTION_MOVE:
+	case E2K_ACTION_COPY:
+		part = new_part (
+			act->type == E2K_ACTION_MOVE ?
+			(xmlChar *) "move-to-folder" :
+			(xmlChar *) "copy-to-folder");
+		value = new_value (
+			part,
+			(xmlChar *) "folder",
+			(xmlChar *) "folder-source-key", NULL);
+		entryid = stringify_entryid (
+			act->act.xfer.folder_source_key->data + 1,
+			act->act.xfer.folder_source_key->len - 1);
+		xmlNewTextChild (
+			value, NULL,
+			(xmlChar *) "entryid",
+			(xmlChar *) entryid);
+		g_free (entryid);
+		break;
+
+	case E2K_ACTION_REPLY:
+	case E2K_ACTION_OOF_REPLY:
+		part = new_part (
+			act->type == E2K_ACTION_REPLY ?
+			(xmlChar *) "reply" :
+			(xmlChar *) "oof-reply");
+		value = new_value (
+			part,
+			(xmlChar *) "template",
+			(xmlChar *) "message-entryid", NULL);
+		entryid = stringify_entryid (
+			act->act.reply.entryid->data,
+			act->act.reply.entryid->len);
+		xmlNewTextChild (
+			value, NULL,
+			(xmlChar *) "entryid",
+			(xmlChar *) entryid);
+		g_free (entryid);
+		break;
+
+	case E2K_ACTION_DEFER:
+		part = new_part ((xmlChar *) "defer");
+		break;
+
+	case E2K_ACTION_BOUNCE:
+		part = new_part ((xmlChar *) "bounce");
+		switch (act->act.bounce_code) {
+		case E2K_ACTION_BOUNCE_CODE_TOO_LARGE:
+			new_value (
+				part,
+				(xmlChar *) "bounce_code",
+				(xmlChar *) "option",
+				(xmlChar *) "size");
+			break;
+		case E2K_ACTION_BOUNCE_CODE_FORM_MISMATCH:
+			new_value (
+				part,
+				(xmlChar *) "bounce_code",
+				(xmlChar *) "option",
+				(xmlChar *) "form-mismatch");
+			break;
+		case E2K_ACTION_BOUNCE_CODE_ACCESS_DENIED:
+			new_value (
+				part,
+				(xmlChar *) "bounce_code",
+				(xmlChar *) "option",
+				(xmlChar *) "permission");
+			break;
+		}
+		break;
+
+	case E2K_ACTION_FORWARD:
+	case E2K_ACTION_DELEGATE:
+	{
+		gint i, j;
+		E2kAddrList *list;
+		E2kAddrEntry *entry;
+		E2kPropValue *pv;
+		const gchar *display_name, *email;
+		gchar *full_addr;
+
+		list = act->act.addr_list;
+		for (i = 0; i < list->nentries; i++) {
+			entry = &list->entry[i];
+			display_name = email = NULL;
+			for (j = 0; j < entry->nvalues; j++) {
+				pv = &entry->propval[j];
+				if (pv->prop.proptag == E2K_PROPTAG_PR_TRANSMITTABLE_DISPLAY_NAME)
+					display_name = pv->value;
+				else if (pv->prop.proptag == E2K_PROPTAG_PR_EMAIL_ADDRESS)
+					email = pv->value;
+			}
+
+			if (!email)
+				continue;
+			if (display_name)
+				full_addr = g_strdup_printf ("%s <%s>", display_name, email);
+			else
+				full_addr = g_strdup_printf ("<%s>", email);
+
+			part = new_part (
+				act->type == E2K_ACTION_FORWARD ?
+				(xmlChar *) "forward" :
+				(xmlChar *) "delegate");
+			value = new_value (
+				part,
+				(xmlChar *) "recipient",
+				(xmlChar *) "recipient", NULL);
+			xmlNewTextChild (
+				value, NULL,
+				(xmlChar *) "recipient",
+				(xmlChar *) full_addr);
+			g_free (full_addr);
+
+			xmlAddChild (actionset, part);
+		}
+		return TRUE;
+	}
+
+	case E2K_ACTION_TAG:
+		if (act->act.proptag.prop.proptag != E2K_PROPTAG_PR_IMPORTANCE)
+			return FALSE;
+
+		part = new_part ((xmlChar *) "set-importance");
+		new_value_int (
+			part,
+			(xmlChar *) "importance",
+			(xmlChar *) "option",
+			(xmlChar *) "value",
+			GPOINTER_TO_INT (act->act.proptag.value));
+		break;
+
+	case E2K_ACTION_DELETE:
+		part = new_part ((xmlChar *) "delete");
+		break;
+
+	case E2K_ACTION_MARK_AS_READ:
+		part = new_part ((xmlChar *) "mark-read");
+		break;
+
+	default:
+		return FALSE;
+	}
+
+	xmlAddChild (actionset, part);
+	return TRUE;
+}
+
+static gboolean
+rule_to_xml (E2kRule *rule, xmlNode *ruleset)
+{
+	xmlNode *top, *set;
+	E2kRestriction *rn;
+	gint i;
+
+	top = xmlNewChild (ruleset, NULL, (xmlChar *) "rule", NULL);
+
+	xmlSetProp (
+		top,
+		(xmlChar *) "source",
+		(rule->state & E2K_RULE_STATE_ONLY_WHEN_OOF) ?
+		(xmlChar *) "oof" : (xmlChar *) "incoming");
+	xmlSetProp (
+		top,
+		(xmlChar *) "enabled",
+		(rule->state & E2K_RULE_STATE_ENABLED) ?
+		(xmlChar *) "1" : (xmlChar *) "0");
+
+	if (rule->name)
+		xmlNewTextChild (
+			top, NULL,
+			(xmlChar *) "title",
+			(xmlChar *) rule->name);
+
+	set = xmlNewChild (top, NULL, (xmlChar *) "partset", NULL);
+	rn = rule->condition;
+	if (rn) {
+		E2kRestrictionType wrap_type;
+
+		if (rn->type == E2K_RESTRICTION_OR) {
+			xmlSetProp (
+				top,
+				(xmlChar *) "grouping",
+				(xmlChar *) "any");
+			wrap_type = E2K_RESTRICTION_OR;
+		} else {
+			xmlSetProp (
+				top,
+				(xmlChar *) "grouping",
+				(xmlChar *) "all");
+			wrap_type = E2K_RESTRICTION_AND;
+		}
+
+		if (!restriction_to_xml (rn, set, wrap_type, FALSE)) {
+			g_warning ("could not express restriction as xml");
+			xmlUnlinkNode (top);
+			xmlFreeNode (top);
+			return FALSE;
+		}
+	} else
+		xmlSetProp (top, (xmlChar *) "grouping", (xmlChar *) "all");
+
+	set = xmlNewChild (top, NULL, (xmlChar *) "actionset", NULL);
+	for (i = 0; i < rule->actions->len; i++) {
+		if (!action_to_xml (rule->actions->pdata[i], set)) {
+			g_warning ("could not express action as xml");
+			xmlUnlinkNode (top);
+			xmlFreeNode (top);
+			return FALSE;
+		}
+	}
+
+	if (rule->state & E2K_RULE_STATE_EXIT_LEVEL)
+		xmlAddChild (set, new_part ((xmlChar *) "stop"));
+
+	return TRUE;
+}
+
+/**
+ * e2k_rules_to_xml:
+ * @rules: an #E2kRules
+ *
+ * Encodes @rules into an XML format like that used by the evolution
+ * filter code.
+ *
+ * Return value: the XML rules
+ **/
+xmlDoc *
+e2k_rules_to_xml (E2kRules *rules)
+{
+	xmlDoc *doc;
+	xmlNode *top, *ruleset;
+	gint i;
+
+	doc = xmlNewDoc (NULL);
+	top = xmlNewNode (NULL, (xmlChar *) "filteroptions");
+	xmlDocSetRootElement (doc, top);
+
+	ruleset = xmlNewChild (top, NULL, (xmlChar *) "ruleset", NULL);
+
+	for (i = 0; i < rules->rules->len; i++)
+		rule_to_xml (rules->rules->pdata[i], ruleset);
+
+	return doc;
+}
diff --git a/server/lib/e2k-rule-xml.h b/server/lib/e2k-rule-xml.h
new file mode 100644
index 0000000..2b34697
--- /dev/null
+++ b/server/lib/e2k-rule-xml.h
@@ -0,0 +1,12 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2003, 2004 Novell, Inc. */
+
+#ifndef __E2K_RULE_XML_H__
+#define __E2K_RULE_XML_H__
+
+#include "e2k-types.h"
+#include "e2k-rule.h"
+
+xmlDoc     *e2k_rules_to_xml      (E2kRules   *rules);
+
+#endif /* __E2K_RULE_XML_H__ */
diff --git a/server/lib/e2k-rule.c b/server/lib/e2k-rule.c
new file mode 100644
index 0000000..c025636
--- /dev/null
+++ b/server/lib/e2k-rule.c
@@ -0,0 +1,686 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2003, 2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "e2k-rule.h"
+#include "e2k-action.h"
+#include "e2k-properties.h"
+#include "e2k-propnames.h"
+#include "e2k-utils.h"
+
+/**
+ * e2k_rule_prop_set:
+ * @prop: an #E2kRuleProp
+ * @propname: a MAPI property name
+ *
+ * This is a convenience function to set both the %name and %proptag
+ * fields of @prop.
+ **/
+void
+e2k_rule_prop_set (E2kRuleProp *prop, const gchar *propname)
+{
+	prop->name = propname;
+	prop->proptag = e2k_prop_proptag (propname);
+}
+
+/**
+ * e2k_rule_write_uint32:
+ * @ptr: pointer into a binary rule
+ * @val: a uint32 value
+ *
+ * Writes @val into the rule at @ptr
+ **/
+void
+e2k_rule_write_uint32 (guint8 *ptr, guint32 val)
+{
+	*ptr++ = ( val        & 0xFF);
+	*ptr++ = ((val >>  8) & 0xFF);
+	*ptr++ = ((val >> 16) & 0xFF);
+	*ptr++ = ((val >> 24) & 0xFF);
+}
+
+/**
+ * e2k_rule_append_uint32:
+ * @ba: a byte array containing a binary rule
+ * @val: a uint32 value
+ *
+ * Appends @val to the rule in @ba
+ **/
+void
+e2k_rule_append_uint32 (GByteArray *ba, guint32 val)
+{
+	g_byte_array_set_size (ba, ba->len + 4);
+	e2k_rule_write_uint32 (ba->data + ba->len - 4, val);
+}
+
+/**
+ * e2k_rule_read_uint32:
+ * @ptr: pointer into a binary rule
+ *
+ * Reads a uint32 value from the rule at @ptr
+ *
+ * Return value: the uint32 value
+ **/
+guint32
+e2k_rule_read_uint32 (guint8 *ptr)
+{
+	return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
+}
+
+/**
+ * e2k_rule_extract_uint32:
+ * @ptr: pointer to a pointer into a binary rule
+ * @len: pointer to the remaining length of * ptr
+ * @val: pointer to a uint32 value
+ *
+ * Reads a uint32 value from the rule at ** ptr into * val and updates
+ * * ptr and * len accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_rule_extract_uint32 (guint8 **ptr, gint *len, guint32 *val)
+{
+	if (*len < 4)
+		return FALSE;
+
+	*val = e2k_rule_read_uint32 (*ptr);
+
+	*ptr += 4;
+	*len -= 4;
+	return TRUE;
+}
+
+/**
+ * e2k_rule_write_uint16:
+ * @ptr: pointer into a binary rule
+ * @val: a uint16 value
+ *
+ * Writes @val into the rule at @ptr
+ **/
+void
+e2k_rule_write_uint16 (guint8 *ptr, guint16 val)
+{
+	*ptr++ = ( val        & 0xFF);
+	*ptr++ = ((val >>  8) & 0xFF);
+}
+
+/**
+ * e2k_rule_append_uint16:
+ * @ba: a byte array containing a binary rule
+ * @val: a uint16 value
+ *
+ * Appends @val to the rule in @ba
+ **/
+void
+e2k_rule_append_uint16 (GByteArray *ba, guint16 val)
+{
+	g_byte_array_set_size (ba, ba->len + 2);
+	e2k_rule_write_uint16 (ba->data + ba->len - 2, val);
+}
+
+/**
+ * e2k_rule_read_uint16:
+ * @ptr: pointer into a binary rule
+ *
+ * Reads a uint16 value from the rule at @ptr
+ *
+ * Return value: the uint16 value
+ **/
+guint16
+e2k_rule_read_uint16 (guint8 *ptr)
+{
+	return ptr[0] | (ptr[1] << 8);
+}
+
+/**
+ * e2k_rule_extract_uint16:
+ * @ptr: pointer to a pointer into a binary rule
+ * @len: pointer to the remaining length of * ptr
+ * @val: pointer to a uint16 value
+ *
+ * Reads a uint16 value from the rule at ** ptr into * val and updates
+ * * ptr and * len accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_rule_extract_uint16 (guint8 **ptr, gint *len, guint16 *val)
+{
+	if (*len < 2)
+		return FALSE;
+
+	*val = e2k_rule_read_uint16 (*ptr);
+
+	*ptr += 2;
+	*len -= 2;
+	return TRUE;
+}
+
+/**
+ * e2k_rule_append_byte:
+ * @ba: a byte array containing a binary rule
+ * @val: a byte value
+ *
+ * Appends @val to the rule in @ba
+ **/
+void
+e2k_rule_append_byte (GByteArray *ba, guint8 val)
+{
+	g_byte_array_append (ba, &val, 1);
+}
+
+/**
+ * e2k_rule_extract_byte:
+ * @ptr: pointer to a pointer into a binary rule
+ * @len: pointer to the remaining length of * ptr
+ * @val: pointer to a byte value
+ *
+ * Reads a byte value from the rule at ** ptr into * val and updates
+ * * ptr and * len accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_rule_extract_byte (guint8 **ptr, gint *len, guint8 *val)
+{
+	if (*len < 1)
+		return FALSE;
+
+	*val = **ptr;
+
+	*ptr += 1;
+	*len -= 1;
+	return TRUE;
+}
+
+/**
+ * e2k_rule_append_string:
+ * @ba: a byte array containing a binary rule
+ * @str: a (Windows) locale-encoded string
+ *
+ * Appends @str to the rule in @ba
+ **/
+void
+e2k_rule_append_string (GByteArray *ba, const gchar *str)
+{
+	/* FIXME: verify encoding */
+	g_byte_array_append (ba, (guint8 *) str, strlen (str) + 1);
+}
+
+/**
+ * e2k_rule_extract_string:
+ * @ptr: pointer to a pointer into a binary rule
+ * @len: pointer to the remaining length of * ptr
+ * @str: pointer to a string pointer
+ *
+ * Reads a (Windows) locale-encoded string from the rule at ** ptr
+ * into * str and updates * ptr and * len accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_rule_extract_string (guint8 **ptr, gint *len, gchar **str)
+{
+	gint slen;
+
+	for (slen = 0; slen < *len; slen++) {
+		if ((*ptr)[slen] == '\0') {
+			*str = g_strdup ((gchar *) *ptr);
+			*ptr += slen + 1;
+			*len -= slen + 1;
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+/**
+ * e2k_rule_append_unicode:
+ * @ba: a byte array containing a binary rule
+ * @str: a UTF-8 string
+ *
+ * Appends @str to the rule in @ba
+ **/
+void
+e2k_rule_append_unicode (GByteArray *ba, const gchar *str)
+{
+	gunichar2 *utf16;
+	gint i;
+
+	utf16 = g_utf8_to_utf16 (str, -1, NULL, NULL, NULL);
+	g_return_if_fail (utf16 != NULL);
+
+	for (i = 0; utf16[i]; i++)
+		e2k_rule_append_uint16 (ba, utf16[i]);
+	e2k_rule_append_uint16 (ba, 0);
+	g_free (utf16);
+}
+
+/**
+ * e2k_rule_extract_unicode:
+ * @ptr: pointer to a pointer into a binary rule
+ * @len: pointer to the remaining length of * ptr
+ * @str: pointer to a string pointer
+ *
+ * Reads a Unicode-encoded string from the rule at ** ptr into * str
+ * and updates * ptr and * len accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_rule_extract_unicode (guint8 **ptr, gint *len, gchar **str)
+{
+	guint8 *start, *end;
+	gunichar2 *utf16;
+
+	start = *ptr;
+	end = *ptr + *len;
+
+	for (; *ptr < end - 1; (*ptr) += 2) {
+		if ((*ptr)[0] == '\0' && (*ptr)[1] == '\0') {
+			*ptr += 2;
+			*len -= *ptr - start;
+
+			utf16 = g_memdup (start, *ptr - start);
+			*str = g_utf16_to_utf8 (utf16, -1, NULL, NULL, NULL);
+			g_free (utf16);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * e2k_rule_append_binary:
+ * @ba: a byte array containing a binary rule
+ * @data: binary data
+ *
+ * Appends @data (with a 2-byte length prefix) to the rule in @ba
+ **/
+void
+e2k_rule_append_binary (GByteArray *ba, GByteArray *data)
+{
+	e2k_rule_append_uint16 (ba, data->len);
+	g_byte_array_append (ba, data->data, data->len);
+}
+
+/**
+ * e2k_rule_extract_binary:
+ * @ptr: pointer to a pointer into a binary rule
+ * @len: pointer to the remaining length of * ptr
+ * @data: pointer to a #GByteArray
+ *
+ * Reads binary data (preceded by a 2-byte length) from the rule at
+ * ** ptr into * data and updates * ptr and * len accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_rule_extract_binary (guint8 **ptr, gint *len, GByteArray **data)
+{
+	guint16 datalen;
+
+	if (!e2k_rule_extract_uint16 (ptr, len, &datalen))
+		return FALSE;
+	if (*len < datalen)
+		return FALSE;
+
+	*data = g_byte_array_sized_new (datalen);
+	memcpy ((*data)->data, *ptr, datalen);
+	(*data)->len = datalen;
+
+	*ptr += datalen;
+	*len -= datalen;
+	return TRUE;
+}
+
+#define E2K_PT_UNICODE_RULE 0x84b0
+
+/**
+ * e2k_rule_append_proptag:
+ * @ba: a byte array containing a binary rule
+ * @prop: an #E2kRuleProp
+ *
+ * Appends a representation of @prop to the rule in @ba
+ **/
+void
+e2k_rule_append_proptag (GByteArray *ba, E2kRuleProp *prop)
+{
+	guint32 proptag = prop->proptag;
+
+	if (E2K_PROPTAG_TYPE (proptag) == E2K_PT_STRING8 ||
+	    E2K_PROPTAG_TYPE (proptag) == E2K_PT_UNICODE)
+		proptag = E2K_PROPTAG_ID (proptag) | E2K_PT_UNICODE_RULE;
+
+	e2k_rule_append_uint32 (ba, proptag);
+}
+
+/**
+ * e2k_rule_extract_proptag:
+ * @ptr: pointer to a pointer into a binary rule
+ * @len: pointer to the remaining length of * ptr
+ * @prop: poitner to an #E2kRuleProp
+ *
+ * Reads a proptag from the rule at ** ptr into * prop and updates
+ * * ptr and * len accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_rule_extract_proptag (guint8 **ptr, gint *len, E2kRuleProp *prop)
+{
+	if (!e2k_rule_extract_uint32 (ptr, len, &prop->proptag))
+		return FALSE;
+
+	if (E2K_PROPTAG_TYPE (prop->proptag) == E2K_PT_UNICODE_RULE)
+		prop->proptag = E2K_PROPTAG_ID (prop->proptag) | E2K_PT_UNICODE;
+	prop->name = e2k_proptag_prop (prop->proptag);
+
+	return TRUE;
+}
+
+/**
+ * e2k_rule_append_propvalue:
+ * @ba: a byte array containing a binary rule
+ * @pv: an #E2kPropValue
+ *
+ * Appends a representation of @pv (the proptag and its value) to the
+ * rule in @ba
+ **/
+void
+e2k_rule_append_propvalue (GByteArray *ba, E2kPropValue *pv)
+{
+	g_return_if_fail (pv->prop.proptag != 0);
+
+	e2k_rule_append_proptag (ba, &pv->prop);
+
+	switch (E2K_PROPTAG_TYPE (pv->prop.proptag)) {
+	case E2K_PT_UNICODE:
+	case E2K_PT_STRING8:
+		e2k_rule_append_unicode (ba, pv->value);
+		break;
+
+	case E2K_PT_BINARY:
+		e2k_rule_append_binary (ba, pv->value);
+		break;
+
+	case E2K_PT_LONG:
+		e2k_rule_append_uint32 (ba, GPOINTER_TO_UINT (pv->value));
+		break;
+
+	case E2K_PT_BOOLEAN:
+		e2k_rule_append_byte (ba, GPOINTER_TO_UINT (pv->value));
+		break;
+
+	default:
+		/* FIXME */
+		break;
+	}
+}
+
+/**
+ * e2k_rule_extract_propvalue:
+ * @ptr: pointer to a pointer into a binary rule
+ * @len: pointer to the remaining length of * ptr
+ * @pv: pointer to an #E2kPropValue
+ *
+ * Reads a representation of an #E2kPropValue from the rule at ** ptr
+ * into * pv and updates * ptr and * len accordingly.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e2k_rule_extract_propvalue (guint8 **ptr, gint *len, E2kPropValue *pv)
+{
+	if (!e2k_rule_extract_proptag (ptr, len, &pv->prop))
+		return FALSE;
+
+	switch (E2K_PROPTAG_TYPE (pv->prop.proptag)) {
+	case E2K_PT_UNICODE:
+	case E2K_PT_STRING8:
+		pv->type = E2K_PROP_TYPE_STRING;
+		return e2k_rule_extract_unicode (ptr, len, (gchar **)&pv->value);
+
+	case E2K_PT_BINARY:
+		pv->type = E2K_PROP_TYPE_BINARY;
+		return e2k_rule_extract_binary (ptr, len, (GByteArray **)&pv->value);
+
+	case E2K_PT_SYSTIME:
+	{
+		guint64 temp;
+
+		if (*len < 8)
+			return FALSE;
+
+		memcpy (&temp, *ptr, 8);
+		*ptr += 8;
+		*len -= 8;
+
+		temp = GUINT64_FROM_LE (temp);
+		pv->type = E2K_PROP_TYPE_DATE;
+		pv->value = e2k_make_timestamp (e2k_filetime_to_time_t (temp));
+		return TRUE;
+	}
+
+	case E2K_PT_LONG:
+	{
+		guint32 temp;
+
+		if (!e2k_rule_extract_uint32 (ptr, len, &temp))
+			return FALSE;
+		pv->type = E2K_PROP_TYPE_INT;
+		pv->value = GUINT_TO_POINTER (temp);
+		return TRUE;
+	}
+
+	case E2K_PT_BOOLEAN:
+	{
+		guint8 temp;
+
+		if (!e2k_rule_extract_byte (ptr, len, &temp))
+			return FALSE;
+		pv->type = E2K_PROP_TYPE_BOOL;
+		pv->value = GUINT_TO_POINTER ((guint)temp);
+		return TRUE;
+	}
+
+	default:
+		/* FIXME */
+		return FALSE;
+	}
+}
+
+/**
+ * e2k_rule_free_propvalue:
+ * @pv: an #E2kPropValue
+ *
+ * Frees @pv
+ **/
+void
+e2k_rule_free_propvalue (E2kPropValue *pv)
+{
+	if (pv->type == E2K_PROP_TYPE_STRING ||
+	    pv->type == E2K_PROP_TYPE_DATE)
+		g_free (pv->value);
+	else if (pv->type == E2K_PROP_TYPE_BINARY && pv->value)
+		g_byte_array_free (pv->value, TRUE);
+}
+
+/**
+ * e2k_rule_free:
+ * @rule: an #E2kRule
+ *
+ * Frees @rule
+ **/
+void
+e2k_rule_free (E2kRule *rule)
+{
+	if (rule->name)
+		g_free (rule->name);
+	if (rule->condition)
+		e2k_restriction_unref (rule->condition);
+	if (rule->actions)
+		e2k_actions_free (rule->actions);
+	if (rule->provider)
+		g_free (rule->provider);
+	if (rule->provider_data)
+		g_byte_array_free (rule->provider_data, TRUE);
+}
+
+/**
+ * e2k_rules_free:
+ * @rules: an #E2kRules structure
+ *
+ * Frees @rules and the rules it contains
+ **/
+void
+e2k_rules_free (E2kRules *rules)
+{
+	gint i;
+
+	for (i = 0; i < rules->rules->len; i++)
+		e2k_rule_free (rules->rules->pdata[i]);
+	g_ptr_array_free (rules->rules, TRUE);
+	g_free (rules);
+}
+
+/**
+ * e2k_rules_from_binary:
+ * @rules_data: binary-encoded rules data
+ *
+ * Extract rules from @rules_data and returns them in an #E2kRules
+ * structure.
+ *
+ * Return value: the rules, or %NULL on error.
+ **/
+E2kRules *
+e2k_rules_from_binary (GByteArray *rules_data)
+{
+	guint8 *data;
+	gint len, i;
+	guint32 nrules, pdlen;
+	E2kRules *rules;
+	E2kRule *rule;
+
+	data = rules_data->data;
+	len = rules_data->len;
+
+	if (len < 9)
+		return NULL;
+	if (*data != 2)
+		return NULL;
+	data++;
+	len--;
+
+	rules = g_new0 (E2kRules, 1);
+	rules->version = 2;
+
+	if (!e2k_rule_extract_uint32 (&data, &len, &nrules) ||
+	    !e2k_rule_extract_uint32 (&data, &len, &rules->codepage)) {
+		g_free (rules);
+		return NULL;
+	}
+
+	rules->rules = g_ptr_array_new ();
+	for (i = 0; i < nrules; i++) {
+		rule = g_new0 (E2kRule, 1);
+		g_ptr_array_add (rules->rules, rule);
+
+		if (!e2k_rule_extract_uint32 (&data, &len, &rule->sequence) ||
+		    !e2k_rule_extract_uint32 (&data, &len, &rule->state) ||
+		    !e2k_rule_extract_uint32 (&data, &len, &rule->user_flags) ||
+		    !e2k_rule_extract_uint32 (&data, &len, &rule->condition_lcid) ||
+		    !e2k_restriction_extract (&data, &len, &rule->condition) ||
+		    !e2k_actions_extract (&data, &len, &rule->actions) ||
+		    !e2k_rule_extract_string (&data, &len, &rule->provider) ||
+		    !e2k_rule_extract_string (&data, &len, &rule->name) ||
+		    !e2k_rule_extract_uint32 (&data, &len, &rule->level))
+			goto error;
+
+		/* The provider data has a 4-byte length, unlike the
+		 * binary fields in a condition or rule.
+		 */
+		if (!e2k_rule_extract_uint32 (&data, &len, &pdlen))
+			goto error;
+		if (len < pdlen)
+			goto error;
+		rule->provider_data = g_byte_array_sized_new (pdlen);
+		rule->provider_data->len = pdlen;
+		memcpy (rule->provider_data->data, data, pdlen);
+		data += pdlen;
+		len -= pdlen;
+	}
+
+	return rules;
+
+ error:
+	e2k_rules_free (rules);
+	return NULL;
+}
+
+/**
+ * e2k_rules_to_binary:
+ * @rules: an #E2kRules structure
+ *
+ * Encodes @rules into binary form
+ *
+ * Return value: the binary-encoded rules
+ **/
+GByteArray *
+e2k_rules_to_binary (E2kRules *rules)
+{
+	GByteArray *ba;
+	E2kRule *rule;
+	gint i;
+
+	ba = g_byte_array_new ();
+	e2k_rule_append_byte (ba, rules->version);
+	e2k_rule_append_uint32 (ba, rules->rules->len);
+	e2k_rule_append_uint32 (ba, rules->codepage);
+
+	for (i = 0; i < rules->rules->len; i++) {
+		rule = rules->rules->pdata[i];
+
+		e2k_rule_append_uint32 (ba, rule->sequence);
+		e2k_rule_append_uint32 (ba, rule->state);
+		e2k_rule_append_uint32 (ba, rule->user_flags);
+		e2k_rule_append_uint32 (ba, rule->condition_lcid);
+		e2k_restriction_append (ba, rule->condition);
+		e2k_actions_append (ba, rule->actions);
+		e2k_rule_append_string (ba, rule->provider);
+		e2k_rule_append_string (ba, rule->name);
+		e2k_rule_append_uint32 (ba, rule->level);
+
+		/* The provider data has a 4-byte length, unlike the
+		 * binary fields in a condition or rule.
+		 */
+		e2k_rule_append_uint32 (ba, rule->provider_data->len);
+		g_byte_array_append (ba, rule->provider_data->data,
+				     rule->provider_data->len);
+	}
+
+	return ba;
+}
diff --git a/server/lib/e2k-rule.h b/server/lib/e2k-rule.h
new file mode 100644
index 0000000..f226062
--- /dev/null
+++ b/server/lib/e2k-rule.h
@@ -0,0 +1,251 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2003, 2004 Novell, Inc. */
+
+#ifndef __E2K_RULE_H__
+#define __E2K_RULE_H__
+
+#include "e2k-types.h"
+#include "e2k-properties.h"
+#include "e2k-restriction.h"
+
+/* We define these types here because they're private to libexchange,
+ * so code that #includes e2k-restriction, etc, shouldn't see them.
+ */
+
+typedef struct {
+	const gchar *name;
+	guint32     proptag;
+} E2kRuleProp;
+
+void e2k_rule_prop_set (E2kRuleProp *prop, const gchar *propname);
+
+typedef struct {
+	E2kRuleProp  prop;
+	E2kPropType  type;
+	gpointer     value;
+} E2kPropValue;
+
+typedef enum {
+	E2K_RESTRICTION_AND		= 0,
+	E2K_RESTRICTION_OR		= 1,
+	E2K_RESTRICTION_NOT		= 2,
+	E2K_RESTRICTION_CONTENT		= 3,
+	E2K_RESTRICTION_PROPERTY	= 4,
+	E2K_RESTRICTION_COMPAREPROPS	= 5,
+	E2K_RESTRICTION_BITMASK		= 6,
+	E2K_RESTRICTION_SIZE		= 7,
+	E2K_RESTRICTION_EXIST		= 8,
+	E2K_RESTRICTION_SUBRESTRICTION	= 9,
+	E2K_RESTRICTION_COMMENT		= 10
+} E2kRestrictionType;
+
+struct _E2kRestriction {
+	/*< private >*/
+
+	E2kRestrictionType type;
+	gint ref_count;
+
+	union {
+		struct {
+			guint            nrns;
+			E2kRestriction **rns;
+		} and;
+
+		struct {
+			guint            nrns;
+			E2kRestriction **rns;
+		} or;
+
+		struct {
+			E2kRestriction *rn;
+		} not;
+
+		struct {
+			E2kRestrictionFuzzyLevel fuzzy_level;
+			E2kPropValue             pv;
+		} content;
+
+		struct {
+			E2kRestrictionRelop  relop;
+			E2kPropValue         pv;
+		} property;
+
+		struct {
+			E2kRestrictionRelop  relop;
+			E2kRuleProp          prop1;
+			E2kRuleProp          prop2;
+		} compare;
+
+		struct {
+			E2kRestrictionBitop  bitop;
+			E2kRuleProp          prop;
+			guint32              mask;
+		} bitmask;
+
+		struct {
+			E2kRestrictionRelop  relop;
+			E2kRuleProp          prop;
+			guint32              size;
+		} size;
+
+		struct {
+			E2kRuleProp prop;
+		} exist;
+
+		struct {
+			E2kRuleProp     subtable;
+			E2kRestriction *rn;
+		} sub;
+
+		struct {
+			guint32         nprops;
+			E2kRestriction *rn;
+			E2kPropValue   *props;
+		} comment;
+	} res;
+};
+
+typedef enum {
+	E2K_ACTION_MOVE         = 1,
+	E2K_ACTION_COPY         = 2,
+	E2K_ACTION_REPLY        = 3,
+	E2K_ACTION_OOF_REPLY    = 4,
+	E2K_ACTION_DEFER        = 5,
+	E2K_ACTION_BOUNCE       = 6,
+	E2K_ACTION_FORWARD      = 7,
+	E2K_ACTION_DELEGATE     = 8,
+	E2K_ACTION_TAG          = 9,
+	E2K_ACTION_DELETE       = 10,
+	E2K_ACTION_MARK_AS_READ = 11
+} E2kActionType;
+
+typedef enum {
+	E2K_ACTION_REPLY_FLAVOR_NOT_ORIGINATOR = 1,
+	E2K_ACTION_REPLY_FLAVOR_STOCK_TEMPLATE = 2
+} E2kActionReplyFlavor;
+
+typedef enum {
+	E2K_ACTION_FORWARD_FLAVOR_PRESERVE_SENDER = 1,
+	E2K_ACTION_FORWARD_FLAVOR_DO_NOT_MUNGE    = 2,
+	E2K_ACTION_FORWARD_FLAVOR_REDIRECT        = 3,
+	E2K_ACTION_FORWARD_FLAVOR_AS_ATTACHMENT   = 4
+} E2kActionForwardFlavor;
+
+typedef enum {
+	E2K_ACTION_BOUNCE_CODE_TOO_LARGE     = 13,
+	E2K_ACTION_BOUNCE_CODE_FORM_MISMATCH = 31,
+	E2K_ACTION_BOUNCE_CODE_ACCESS_DENIED = 38
+} E2kActionBounceCode;
+
+struct _E2kAddrEntry {
+	/*< private >*/
+	guint32        nvalues;
+	E2kPropValue  *propval;
+};
+
+struct _E2kAddrList {
+	/*< private >*/
+	guint32        nentries;
+	E2kAddrEntry   entry[1];
+};
+
+struct _E2kAction {
+	/*< private >*/
+
+	E2kActionType type;
+	guint32       flavor;
+	guint32       flags;
+
+	union {
+		struct {
+			GByteArray *store_entryid;
+			GByteArray *folder_source_key;
+		} xfer;
+
+		struct {
+			GByteArray *entryid;
+			guint8      reply_template_guid[16];
+		} reply;
+
+		GByteArray   *defer_data;
+		guint32       bounce_code;
+		E2kAddrList  *addr_list;
+		E2kPropValue  proptag;
+	} act;
+};
+
+typedef enum {
+	E2K_RULE_STATE_DISABLED          = 0x00,
+	E2K_RULE_STATE_ENABLED           = 0x01,
+	E2K_RULE_STATE_ERROR             = 0x02,
+	E2K_RULE_STATE_ONLY_WHEN_OOF     = 0x04,
+	E2K_RULE_STATE_KEEP_OOF_HISTORY  = 0x08,
+	E2K_RULE_STATE_EXIT_LEVEL        = 0x10,
+
+	E2K_RULE_STATE_CLEAR_OOF_HISTORY = 0x80000000
+} E2kRuleState;
+
+typedef struct {
+	gchar           *name;
+	guint32         sequence;
+	guint32         state;
+	guint32         user_flags;
+	guint32         level;
+	guint32         condition_lcid;
+	E2kRestriction *condition;
+	GPtrArray      *actions;
+	gchar           *provider;
+	GByteArray     *provider_data;
+} E2kRule;
+
+typedef struct {
+	guint8     version;
+	guint32    codepage;
+	GPtrArray *rules;
+} E2kRules;
+
+E2kRules   *e2k_rules_from_binary (GByteArray *rules_data);
+GByteArray *e2k_rules_to_binary   (E2kRules   *rules);
+void        e2k_rules_free        (E2kRules   *rules);
+void        e2k_rule_free         (E2kRule    *rule);
+
+/* Generic rule read/write code */
+
+void     e2k_rule_write_uint32      (guint8 *ptr, guint32 val);
+void     e2k_rule_append_uint32     (GByteArray *ba, guint32 val);
+guint32  e2k_rule_read_uint32       (guint8 *ptr);
+gboolean e2k_rule_extract_uint32    (guint8 **ptr, gint *len,
+				     guint32 *val);
+
+void     e2k_rule_write_uint16      (guint8 *ptr, guint16 val);
+void     e2k_rule_append_uint16     (GByteArray *ba, guint16 val);
+guint16  e2k_rule_read_uint16       (guint8 *ptr);
+gboolean e2k_rule_extract_uint16    (guint8 **ptr, gint *len,
+				     guint16 *val);
+
+void     e2k_rule_append_byte       (GByteArray *ba, guint8 val);
+gboolean e2k_rule_extract_byte      (guint8 **ptr, gint *len,
+				     guint8 *val);
+
+void     e2k_rule_append_string     (GByteArray *ba, const gchar *str);
+gboolean e2k_rule_extract_string    (guint8 **ptr, gint *len,
+				     gchar **str);
+
+void     e2k_rule_append_unicode    (GByteArray *ba, const gchar *str);
+gboolean e2k_rule_extract_unicode   (guint8 **ptr, gint *len,
+				     gchar **str);
+
+void     e2k_rule_append_binary     (GByteArray *ba, GByteArray *data);
+gboolean e2k_rule_extract_binary    (guint8 **ptr, gint *len,
+				     GByteArray **data);
+
+void     e2k_rule_append_proptag    (GByteArray *ba, E2kRuleProp *prop);
+gboolean e2k_rule_extract_proptag   (guint8 **ptr, gint *len,
+				     E2kRuleProp *prop);
+
+void     e2k_rule_append_propvalue  (GByteArray *ba, E2kPropValue *pv);
+gboolean e2k_rule_extract_propvalue (guint8 **ptr, gint *len,
+				     E2kPropValue *pv);
+void     e2k_rule_free_propvalue    (E2kPropValue *pv);
+
+#endif /* __E2K_RULE_H__ */
diff --git a/server/lib/e2k-security-descriptor.c b/server/lib/e2k-security-descriptor.c
new file mode 100644
index 0000000..abf2342
--- /dev/null
+++ b/server/lib/e2k-security-descriptor.c
@@ -0,0 +1,959 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-security-descriptor.h"
+#include "e2k-sid.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+
+struct _E2kSecurityDescriptorPrivate {
+	GByteArray *header;
+	guint16 control_flags;
+	GArray *aces;
+
+	E2kSid *default_sid, *owner, *group;
+	GHashTable *sids, *sid_order;
+};
+
+typedef struct {
+	guint8  Revision;
+	guint8  Sbz1;
+	guint16 Control;
+	guint32 Owner;
+	guint32 Group;
+	guint32 Sacl;
+	guint32 Dacl;
+} E2k_SECURITY_DESCRIPTOR_RELATIVE;
+
+#define E2K_SECURITY_DESCRIPTOR_REVISION 1
+#define E2K_SE_DACL_PRESENT              GUINT16_TO_LE(0x0004)
+#define E2K_SE_SACL_PRESENT              GUINT16_TO_LE(0x0010)
+#define E2K_SE_DACL_PROTECTED            GUINT16_TO_LE(0x1000)
+
+typedef struct {
+	guint8  AclRevision;
+	guint8  Sbz1;
+	guint16 AclSize;
+	guint16 AceCount;
+	guint16 Sbz2;
+} E2k_ACL;
+
+#define E2K_ACL_REVISION 2
+
+typedef struct {
+	guint8  AceType;
+	guint8  AceFlags;
+	guint16 AceSize;
+} E2k_ACE_HEADER;
+
+#define E2K_ACCESS_ALLOWED_ACE_TYPE (0x00)
+#define E2K_ACCESS_DENIED_ACE_TYPE  (0x01)
+
+#define E2K_OBJECT_INHERIT_ACE      (0x01)
+#define E2K_CONTAINER_INHERIT_ACE   (0x02)
+#define E2K_INHERIT_ONLY_ACE        (0x08)
+
+typedef struct {
+	E2k_ACE_HEADER  Header;
+	guint32         Mask;
+	E2kSid         *Sid;
+} E2k_ACE;
+
+typedef struct {
+	guint32 mapi_permission;
+	guint32 container_allowed, container_not_denied;
+	guint32 object_allowed, object_not_denied;
+} E2kPermissionsMap;
+
+/* The magic numbers are from the WSS SDK, except modified to match
+ * Outlook a bit.
+ */
+#define LE(x) (GUINT32_TO_LE (x))
+static E2kPermissionsMap permissions_map[] = {
+	{ E2K_PERMISSION_READ_ANY,
+	  LE(0x000000), LE(0x000000), LE(0x1208a9), LE(0x0008a9) },
+	{ E2K_PERMISSION_CREATE,
+	  LE(0x000002), LE(0x000002), LE(0x000000), LE(0x000000) },
+	{ E2K_PERMISSION_CREATE_SUBFOLDER,
+	  LE(0x000004), LE(0x000004), LE(0x000000), LE(0x000000) },
+	{ E2K_PERMISSION_EDIT_OWNED,
+	  LE(0x000000), LE(0x000000), LE(0x000200), LE(0x000000) },
+	{ E2K_PERMISSION_DELETE_OWNED,
+	  LE(0x000000), LE(0x000000), LE(0x000400), LE(0x000000) },
+	{ E2K_PERMISSION_EDIT_ANY,
+	  LE(0x000000), LE(0x000000), LE(0x0c0116), LE(0x1e0316) },
+	{ E2K_PERMISSION_DELETE_ANY,
+	  LE(0x000000), LE(0x000000), LE(0x010000), LE(0x010400) },
+	{ E2K_PERMISSION_OWNER,
+	  LE(0x0d4110), LE(0x0d4110), LE(0x000000), LE(0x000000) },
+	{ E2K_PERMISSION_CONTACT,
+	  LE(0x008000), LE(0x008000), LE(0x000000), LE(0x000000) },
+	{ E2K_PERMISSION_FOLDER_VISIBLE,
+	  LE(0x1208a9), LE(0x1200a9), LE(0x000000), LE(0x000000) }
+};
+static const gint permissions_map_size =
+	sizeof (permissions_map) / sizeof (permissions_map[0]);
+
+static const guint32 container_permissions_all = LE(0x1fc9bf);
+static const guint32 object_permissions_all    = LE(0x1f0fbf);
+#undef LE
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+static void dispose (GObject *object);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	object_class->dispose = dispose;
+}
+
+static void
+init (E2kSecurityDescriptor *sd)
+{
+	sd->priv = g_new0 (E2kSecurityDescriptorPrivate, 1);
+
+	sd->priv->sids = g_hash_table_new (e2k_sid_binary_sid_hash,
+					   e2k_sid_binary_sid_equal);
+	sd->priv->sid_order = g_hash_table_new (NULL, NULL);
+	sd->priv->aces = g_array_new (FALSE, TRUE, sizeof (E2k_ACE));
+}
+
+static void
+free_sid (gpointer key, gpointer sid, gpointer data)
+{
+	g_object_unref (sid);
+}
+
+static void
+dispose (GObject *object)
+{
+	E2kSecurityDescriptor *sd = (E2kSecurityDescriptor *) object;
+
+	if (sd->priv) {
+		g_hash_table_foreach (sd->priv->sids, free_sid, NULL);
+		g_hash_table_destroy (sd->priv->sids);
+		g_hash_table_destroy (sd->priv->sid_order);
+
+		g_array_free (sd->priv->aces, TRUE);
+
+		if (sd->priv->header)
+			g_byte_array_free (sd->priv->header, TRUE);
+
+		g_free (sd->priv);
+		sd->priv = NULL;
+	}
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+E2K_MAKE_TYPE (e2k_security_descriptor, E2kSecurityDescriptor, class_init, init, PARENT_TYPE)
+
+/* This determines the relative ordering of any two ACEs in a SID.
+ * See docs/security for details.
+ */
+static gint
+ace_compar (E2k_ACE *ace1, E2k_ACE *ace2, E2kSecurityDescriptor *sd)
+{
+	E2kSidType t1;
+	E2kSidType t2;
+	gint order1, order2;
+
+	if (ace1 == ace2)
+		return 0;
+
+	/* Figure out which overall section the SID will go in and
+	 * what its order within that group is.
+	 */
+	if (ace1->Sid == sd->priv->default_sid)
+		t1 = E2K_SID_TYPE_GROUP;
+	else
+		t1 = e2k_sid_get_sid_type (ace1->Sid);
+	order1 = GPOINTER_TO_INT (g_hash_table_lookup (sd->priv->sid_order,
+						       ace1->Sid));
+
+	if (ace2->Sid == sd->priv->default_sid)
+		t2 = E2K_SID_TYPE_GROUP;
+	else
+		t2 = e2k_sid_get_sid_type (ace2->Sid);
+	order2 = GPOINTER_TO_INT (g_hash_table_lookup (sd->priv->sid_order,
+						       ace2->Sid));
+
+	if (t1 != t2) {
+		if (t1 == E2K_SID_TYPE_USER)
+			return -1;
+		else if (t2 == E2K_SID_TYPE_USER)
+			return 1;
+		else if (t1 == E2K_SID_TYPE_GROUP)
+			return 1;
+		else /* (t2 == E2K_SID_TYPE_GROUP) */
+			return -1;
+	}
+
+	if (t1 != E2K_SID_TYPE_GROUP) {
+		/* Object-level ACEs go before Container-level ACEs */
+		if ((ace1->Header.AceFlags & E2K_OBJECT_INHERIT_ACE) &&
+		    !(ace2->Header.AceFlags & E2K_OBJECT_INHERIT_ACE))
+			return -1;
+		else if ((ace2->Header.AceFlags & E2K_OBJECT_INHERIT_ACE) &&
+			 !(ace1->Header.AceFlags & E2K_OBJECT_INHERIT_ACE))
+			return 1;
+
+		/* Compare SID order */
+		if (order1 < order2)
+			return -1;
+		else if (order1 > order2)
+			return 1;
+
+		/* Allowed ACEs for a given SID go before Denied ACEs */
+		if (ace1->Header.AceType == ace2->Header.AceType)
+			return 0;
+		else if (ace1->Header.AceType == E2K_ACCESS_ALLOWED_ACE_TYPE)
+			return -1;
+		else
+			return 1;
+	} else {
+		/* For groups, object-level ACEs go after Container-level */
+		if ((ace1->Header.AceFlags & E2K_OBJECT_INHERIT_ACE) &&
+		    !(ace2->Header.AceFlags & E2K_OBJECT_INHERIT_ACE))
+			return 1;
+		else if ((ace2->Header.AceFlags & E2K_OBJECT_INHERIT_ACE) &&
+			 !(ace1->Header.AceFlags & E2K_OBJECT_INHERIT_ACE))
+			return -1;
+
+		/* Default comes after groups in each section */
+		if (ace1->Sid != ace2->Sid) {
+			if (ace1->Sid == sd->priv->default_sid)
+				return 1;
+			else if (ace2->Sid == sd->priv->default_sid)
+				return -1;
+		}
+
+		/* All Allowed ACEs go before all Denied ACEs */
+		if (ace1->Header.AceType == E2K_ACCESS_ALLOWED_ACE_TYPE &&
+		    ace2->Header.AceType == E2K_ACCESS_DENIED_ACE_TYPE)
+			return -1;
+		else if (ace1->Header.AceType == E2K_ACCESS_DENIED_ACE_TYPE &&
+			 ace2->Header.AceType == E2K_ACCESS_ALLOWED_ACE_TYPE)
+			return 1;
+
+		/* Compare SID order */
+		if (order1 < order2)
+			return -1;
+		else if (order1 > order2)
+			return 1;
+		else
+			return 0;
+	}
+}
+
+static xmlNode *
+find_child (xmlNode *node, const xmlChar *name)
+{
+	for (node = node->xmlChildrenNode; node; node = node->next) {
+		if (node->name && !xmlStrcmp (node->name, name))
+			return node;
+	}
+	return NULL;
+}
+
+static void
+extract_sids (E2kSecurityDescriptor *sd, xmlNodePtr node)
+{
+	xmlNodePtr string_sid_node, type_node, display_name_node;
+	xmlChar *string_sid, *content, *display_name;
+	const guint8 *bsid;
+	E2kSid *sid;
+	E2kSidType type;
+
+	for (; node; node = node->next) {
+		if (xmlStrcmp (node->name, (xmlChar *) "sid") != 0) {
+			if (node->xmlChildrenNode)
+				extract_sids (sd, node->xmlChildrenNode);
+			continue;
+		}
+
+		string_sid_node = find_child (node, (xmlChar *) "string_sid");
+		type_node = find_child (node, (xmlChar *) "type");
+		display_name_node = find_child (node, (xmlChar *) "display_name");
+		if (!string_sid_node || !type_node)
+			continue;
+
+		string_sid = xmlNodeGetContent (string_sid_node);
+
+		content = xmlNodeGetContent (type_node);
+		if (!content || !xmlStrcmp (content, (xmlChar *) "user"))
+			type = E2K_SID_TYPE_USER;
+		else if (!xmlStrcmp (content, (xmlChar *) "group"))
+			type = E2K_SID_TYPE_GROUP;
+		else if (!xmlStrcmp (content, (xmlChar *) "well_known_group"))
+			type = E2K_SID_TYPE_WELL_KNOWN_GROUP;
+		else if (!xmlStrcmp (content, (xmlChar *) "alias"))
+			type = E2K_SID_TYPE_ALIAS;
+		else
+			type = E2K_SID_TYPE_INVALID;
+		xmlFree (content);
+
+		if (display_name_node)
+			display_name = xmlNodeGetContent (display_name_node);
+		else
+			display_name = NULL;
+
+		sid = e2k_sid_new_from_string_sid (type, (gchar *) string_sid,
+						   (gchar *) display_name);
+		xmlFree (string_sid);
+		if (display_name)
+			xmlFree (display_name);
+
+		bsid = e2k_sid_get_binary_sid (sid);
+		if (g_hash_table_lookup (sd->priv->sids, bsid)) {
+			g_object_unref (sid);
+			continue;
+		}
+
+		g_hash_table_insert (sd->priv->sids, (gchar *)bsid, sid);
+	}
+}
+
+static gboolean
+parse_sid (E2kSecurityDescriptor *sd, GByteArray *binsd, guint16 *off,
+	   E2kSid **sid)
+{
+	gint sid_len;
+
+	if (binsd->len - *off < E2K_SID_BINARY_SID_MIN_LEN)
+		return FALSE;
+	sid_len = E2K_SID_BINARY_SID_LEN (binsd->data + *off);
+	if (binsd->len - *off < sid_len)
+		return FALSE;
+
+	*sid = g_hash_table_lookup (sd->priv->sids, binsd->data + *off);
+	*off += sid_len;
+
+	return *sid != NULL;
+}
+
+static gboolean
+parse_acl (E2kSecurityDescriptor *sd, GByteArray *binsd, guint16 *off)
+{
+	E2k_ACL aclbuf;
+	E2k_ACE acebuf;
+	gint ace_count, i;
+
+	if (binsd->len - *off < sizeof (E2k_ACL))
+		return FALSE;
+
+	memcpy (&aclbuf, binsd->data + *off, sizeof (aclbuf));
+	if (*off + GUINT16_FROM_LE (aclbuf.AclSize) > binsd->len)
+		return FALSE;
+	if (aclbuf.AclRevision != E2K_ACL_REVISION)
+		return FALSE;
+
+	ace_count = GUINT16_FROM_LE (aclbuf.AceCount);
+
+	*off += sizeof (aclbuf);
+	for (i = 0; i < ace_count; i++) {
+		if (binsd->len - *off < sizeof (E2k_ACE))
+			return FALSE;
+
+		memcpy (&acebuf, binsd->data + *off,
+			sizeof (acebuf.Header) + sizeof (acebuf.Mask));
+		*off += sizeof (acebuf.Header) + sizeof (acebuf.Mask);
+
+		/* If either of OBJECT_INHERIT_ACE or INHERIT_ONLY_ACE
+		 * is set, both must be.
+		 */
+		if (acebuf.Header.AceFlags & E2K_OBJECT_INHERIT_ACE) {
+			if (!(acebuf.Header.AceFlags & E2K_INHERIT_ONLY_ACE))
+				return FALSE;
+		} else {
+			if (acebuf.Header.AceFlags & E2K_INHERIT_ONLY_ACE)
+				return FALSE;
+		}
+
+		if (!parse_sid (sd, binsd, off, &acebuf.Sid))
+			return FALSE;
+
+		if (!g_hash_table_lookup (sd->priv->sid_order, acebuf.Sid)) {
+			gint size = g_hash_table_size (sd->priv->sid_order);
+
+			g_hash_table_insert (sd->priv->sid_order, acebuf.Sid,
+					     GUINT_TO_POINTER (size + 1));
+		}
+
+		g_array_append_val (sd->priv->aces, acebuf);
+	}
+
+	return TRUE;
+}
+
+/**
+ * e2k_security_descriptor_new:
+ * @xml_form: the XML form of the folder's security descriptor
+ * (The "http://schemas.microsoft.com/exchange/security/descriptor";
+ * property, aka %E2K_PR_EXCHANGE_SD_XML)
+ * @binary_form: the binary form of the folder's security descriptor
+ * (The "http://schemas.microsoft.com/exchange/ntsecuritydescriptor";
+ * property, aka %E2K_PR_EXCHANGE_SD_BINARY)
+ *
+ * Constructs an #E2kSecurityDescriptor from the data in @xml_form and
+ * @binary_form.
+ *
+ * Return value: the security descriptor, or %NULL if the data could
+ * not be parsed.
+ **/
+E2kSecurityDescriptor *
+e2k_security_descriptor_new (xmlNodePtr xml_form, GByteArray *binary_form)
+{
+	E2kSecurityDescriptor *sd;
+	E2k_SECURITY_DESCRIPTOR_RELATIVE sdbuf;
+	guint16 off, header_len;
+
+	g_return_val_if_fail (xml_form != NULL, NULL);
+	g_return_val_if_fail (binary_form != NULL, NULL);
+
+	if (binary_form->len < 2)
+		return NULL;
+
+	memcpy (&header_len, binary_form->data, 2);
+	header_len = GUINT16_FROM_LE (header_len);
+	if (header_len + sizeof (sdbuf) > binary_form->len)
+		return NULL;
+
+	memcpy (&sdbuf, binary_form->data + header_len, sizeof (sdbuf));
+	if (sdbuf.Revision != E2K_SECURITY_DESCRIPTOR_REVISION)
+		return NULL;
+	if ((sdbuf.Control & (E2K_SE_DACL_PRESENT | E2K_SE_SACL_PRESENT)) !=
+	    E2K_SE_DACL_PRESENT)
+		return NULL;
+
+	sd = g_object_new (E2K_TYPE_SECURITY_DESCRIPTOR, NULL);
+	sd->priv->header = g_byte_array_new ();
+	g_byte_array_append (sd->priv->header, binary_form->data, header_len);
+	sd->priv->control_flags = sdbuf.Control;
+
+	/* Create a SID for "Default" then extract remaining SIDs from
+	 * the XML form since they have display names associated with
+	 * them.
+	 */
+	sd->priv->default_sid =
+		e2k_sid_new_from_string_sid (E2K_SID_TYPE_WELL_KNOWN_GROUP,
+					     E2K_SID_WKS_EVERYONE, NULL);
+	g_hash_table_insert (sd->priv->sids,
+			     (gchar *)e2k_sid_get_binary_sid (sd->priv->default_sid),
+			     sd->priv->default_sid);
+	extract_sids (sd, xml_form);
+
+	off = GUINT32_FROM_LE (sdbuf.Owner) + sd->priv->header->len;
+	if (!parse_sid (sd, binary_form, &off, &sd->priv->owner))
+		goto lose;
+	off = GUINT32_FROM_LE (sdbuf.Group) + sd->priv->header->len;
+	if (!parse_sid (sd, binary_form, &off, &sd->priv->group))
+		goto lose;
+
+	off = GUINT32_FROM_LE (sdbuf.Dacl) + sd->priv->header->len;
+	if (!parse_acl (sd, binary_form, &off))
+		goto lose;
+
+	return sd;
+
+ lose:
+	g_object_unref (sd);
+	return NULL;
+}
+
+/**
+ * e2k_security_descriptor_to_binary:
+ * @sd: an #E2kSecurityDescriptor
+ *
+ * Converts @sd back to binary (#E2K_PR_EXCHANGE_SD_BINARY) form
+ * so it can be PROPPATCHed back to the server.
+ *
+ * Return value: the binary form of @sd.
+ **/
+GByteArray *
+e2k_security_descriptor_to_binary (E2kSecurityDescriptor *sd)
+{
+	GByteArray *binsd;
+	E2k_SECURITY_DESCRIPTOR_RELATIVE sdbuf;
+	E2k_ACL aclbuf;
+	E2k_ACE *aces;
+	gint off, ace, last_ace = -1, acl_size, ace_count;
+	const guint8 *bsid;
+
+	g_return_val_if_fail (E2K_IS_SECURITY_DESCRIPTOR (sd), NULL);
+
+	aces = (E2k_ACE *)sd->priv->aces->data;
+
+	/* Compute the length of the ACL first */
+	acl_size = sizeof (E2k_ACL);
+	for (ace = ace_count = 0; ace < sd->priv->aces->len; ace++) {
+		if (aces[ace].Mask) {
+			ace_count++;
+			acl_size += GUINT16_FROM_LE (aces[ace].Header.AceSize);
+		}
+	}
+
+	binsd = g_byte_array_new ();
+
+	/* Exchange-specific header */
+	g_byte_array_append (binsd, sd->priv->header->data,
+			     sd->priv->header->len);
+
+	/* SECURITY_DESCRIPTOR header */
+	memset (&sdbuf, 0, sizeof (sdbuf));
+	sdbuf.Revision = E2K_SECURITY_DESCRIPTOR_REVISION;
+	sdbuf.Control = sd->priv->control_flags;
+	off = sizeof (sdbuf);
+	sdbuf.Dacl = GUINT32_TO_LE (off);
+	off += acl_size;
+	sdbuf.Owner = GUINT32_TO_LE (off);
+	bsid = e2k_sid_get_binary_sid (sd->priv->owner);
+	off += E2K_SID_BINARY_SID_LEN (bsid);
+	sdbuf.Group = GUINT32_TO_LE (off);
+	g_byte_array_append (binsd, (gpointer)&sdbuf, sizeof (sdbuf));
+
+	/* ACL header */
+	aclbuf.AclRevision = E2K_ACL_REVISION;
+	aclbuf.Sbz1        = 0;
+	aclbuf.AclSize     = GUINT16_TO_LE (acl_size);
+	aclbuf.AceCount    = GUINT16_TO_LE (ace_count);
+	aclbuf.Sbz2        = 0;
+	g_byte_array_append (binsd, (gpointer)&aclbuf, sizeof (aclbuf));
+
+	/* ACEs */
+	for (ace = 0; ace < sd->priv->aces->len; ace++) {
+		if (!aces[ace].Mask)
+			continue;
+
+		if (last_ace != -1) {
+			if (ace_compar (&aces[last_ace], &aces[ace], sd) != -1) {
+				g_warning ("ACE order mismatch at %d\n", ace);
+				g_byte_array_free (binsd, TRUE);
+				return NULL;
+			}
+		}
+
+		g_byte_array_append (binsd, (gpointer)&aces[ace],
+				     sizeof (aces[ace].Header) +
+				     sizeof (aces[ace].Mask));
+		bsid = e2k_sid_get_binary_sid (aces[ace].Sid);
+		g_byte_array_append (binsd, bsid,
+				     E2K_SID_BINARY_SID_LEN (bsid));
+		last_ace = ace;
+	}
+
+	/* Owner and Group */
+	bsid = e2k_sid_get_binary_sid (sd->priv->owner);
+	g_byte_array_append (binsd, bsid, E2K_SID_BINARY_SID_LEN (bsid));
+	bsid = e2k_sid_get_binary_sid (sd->priv->group);
+	g_byte_array_append (binsd, bsid, E2K_SID_BINARY_SID_LEN (bsid));
+
+	return binsd;
+}
+
+/**
+ * e2k_security_descriptor_get_default:
+ * @sd: a security descriptor
+ *
+ * Returns an #E2kSid corresponding to the default permissions
+ * associated with @sd. You can pass this to
+ * e2k_security_descriptor_get_permissions() and
+ * e2k_security_descriptor_set_permissions().
+ *
+ * Return value: the "Default" SID
+ **/
+E2kSid *
+e2k_security_descriptor_get_default (E2kSecurityDescriptor *sd)
+{
+	return sd->priv->default_sid;
+}
+
+/**
+ * e2k_security_descriptor_get_sids:
+ * @sd: a security descriptor
+ *
+ * Returns a #GList containing the SIDs of each user or group
+ * represented in @sd. You can pass these SIDs to
+ * e2k_security_descriptor_get_permissions(),
+ * e2k_security_descriptor_set_permissions(), and
+ * e2k_security_descriptor_remove_sid().
+ *
+ * Return value: a list of SIDs. The caller must free the list
+ * with g_list_free(), but should not free the contents.
+ **/
+GList *
+e2k_security_descriptor_get_sids (E2kSecurityDescriptor *sd)
+{
+	GList *sids = NULL;
+	GHashTable *added_sids;
+	E2k_ACE *aces;
+	gint ace;
+
+	g_return_val_if_fail (E2K_IS_SECURITY_DESCRIPTOR (sd), NULL);
+
+	added_sids = g_hash_table_new (NULL, NULL);
+	aces = (E2k_ACE *)sd->priv->aces->data;
+	for (ace = 0; ace < sd->priv->aces->len; ace++) {
+		if (!g_hash_table_lookup (added_sids, aces[ace].Sid)) {
+			g_hash_table_insert (added_sids, aces[ace].Sid,
+					     aces[ace].Sid);
+			sids = g_list_prepend (sids, aces[ace].Sid);
+		}
+	}
+	g_hash_table_destroy (added_sids);
+
+	return sids;
+}
+
+/**
+ * e2k_security_descriptor_remove_sid:
+ * @sd: a security descriptor
+ * @sid: a SID
+ *
+ * Removes @sid from @sd. If @sid is a user, this means s/he will now
+ * have only the default permissions on @sd (unless s/he is a member
+ * of a group that is also present in @sd.)
+ **/
+void
+e2k_security_descriptor_remove_sid (E2kSecurityDescriptor *sd,
+				    E2kSid *sid)
+{
+	E2k_ACE *aces;
+	gint ace;
+
+	g_return_if_fail (E2K_IS_SECURITY_DESCRIPTOR (sd));
+	g_return_if_fail (E2K_IS_SID (sid));
+
+	/* Canonicalize the SID */
+	sid = g_hash_table_lookup (sd->priv->sids,
+				   e2k_sid_get_binary_sid (sid));
+	if (!sid)
+		return;
+
+	/* We can't actually remove all trace of the user, because if
+	 * he is removed and then re-added without saving in between,
+	 * then we need to keep the original AceFlags. So we just
+	 * clear out all of the masks, which (assuming the user is
+	 * not re-added) will result in him not being written out
+	 * when sd is saved.
+	 */
+
+	aces = (E2k_ACE *)sd->priv->aces->data;
+	for (ace = 0; ace < sd->priv->aces->len; ace++) {
+		if (aces[ace].Sid == sid)
+			aces[ace].Mask = 0;
+	}
+}
+
+/**
+ * e2k_security_descriptor_get_permissions:
+ * @sd: a security descriptor
+ * @sid: a SID
+ *
+ * Computes the MAPI permissions associated with @sid. (Only the
+ * permissions *directly* associated with @sid, not any acquired via
+ * group memberships or the Default SID.)
+ *
+ * Return value: the MAPI permissions
+ **/
+guint32
+e2k_security_descriptor_get_permissions (E2kSecurityDescriptor *sd,
+					 E2kSid *sid)
+{
+	E2k_ACE *aces;
+	guint32 mapi_perms, checkperm;
+	gint ace, map;
+
+	g_return_val_if_fail (E2K_IS_SECURITY_DESCRIPTOR (sd), 0);
+	g_return_val_if_fail (E2K_IS_SID (sid), 0);
+
+	/* Canonicalize the SID */
+	sid = g_hash_table_lookup (sd->priv->sids,
+				   e2k_sid_get_binary_sid (sid));
+	if (!sid)
+		return 0;
+
+	mapi_perms = 0;
+	aces = (E2k_ACE *)sd->priv->aces->data;
+	for (ace = 0; ace < sd->priv->aces->len; ace++) {
+		if (aces[ace].Sid != sid)
+			continue;
+		if (aces[ace].Header.AceType == E2K_ACCESS_DENIED_ACE_TYPE)
+			continue;
+
+		for (map = 0; map < permissions_map_size; map++) {
+			if (aces[ace].Header.AceFlags & E2K_OBJECT_INHERIT_ACE)
+				checkperm = permissions_map[map].object_allowed;
+			else
+				checkperm = permissions_map[map].container_allowed;
+			if (!checkperm)
+				continue;
+
+			if ((aces[ace].Mask & checkperm) == checkperm)
+				mapi_perms |= permissions_map[map].mapi_permission;
+		}
+	}
+
+	return mapi_perms;
+}
+
+/* Put @ace into @sd. If no ACE corresponding to @ace currently exists,
+ * it will be added in the right place. If it does already exist, its
+ * flags (in particular INHERITED_ACE) will be preserved and only the
+ * mask will be changed.
+ */
+static void
+set_ace (E2kSecurityDescriptor *sd, E2k_ACE *ace)
+{
+	E2k_ACE *aces = (E2k_ACE *)sd->priv->aces->data;
+	gint low, mid = 0, high, cmp = -1;
+
+	low = 0;
+	high = sd->priv->aces->len - 1;
+	while (low <= high) {
+		mid = (low + high) / 2;
+		cmp = ace_compar (ace, &aces[mid], sd);
+		if (cmp == 0) {
+			if (ace->Mask)
+				aces[mid].Mask = ace->Mask;
+			else
+				g_array_remove_index (sd->priv->aces, mid);
+			return;
+		} else if (cmp < 0)
+			high = mid - 1;
+		else
+			low = mid + 1;
+	}
+
+	if (ace->Mask)
+		g_array_insert_vals (sd->priv->aces, cmp < 0 ? mid : mid + 1, ace, 1);
+}
+
+/**
+ * e2k_security_descriptor_set_permissions:
+ * @sd: a security descriptor
+ * @sid: a SID
+ * @perms: the MAPI permissions
+ *
+ * Updates or sets @sid's permissions on @sd.
+ **/
+void
+e2k_security_descriptor_set_permissions (E2kSecurityDescriptor *sd,
+					 E2kSid *sid, guint32 perms)
+{
+	E2k_ACE ace;
+	guint32 object_allowed, object_denied;
+	guint32 container_allowed, container_denied;
+	const guint8 *bsid;
+	E2kSid *sid2;
+	gint map;
+
+	g_return_if_fail (E2K_IS_SECURITY_DESCRIPTOR (sd));
+	g_return_if_fail (E2K_IS_SID (sid));
+
+	bsid = e2k_sid_get_binary_sid (sid);
+	sid2 = g_hash_table_lookup (sd->priv->sids, bsid);
+	if (!sid2) {
+		gint size = g_hash_table_size (sd->priv->sid_order);
+
+		g_hash_table_insert (sd->priv->sids, (gchar *)bsid, sid);
+		g_object_ref (sid);
+
+		g_hash_table_insert (sd->priv->sid_order, sid,
+				     GUINT_TO_POINTER (size + 1));
+	} else
+		sid = sid2;
+
+	object_allowed    = 0;
+	object_denied     = object_permissions_all;
+	container_allowed = 0;
+	container_denied  = container_permissions_all;
+
+	for (map = 0; map < permissions_map_size; map++) {
+		if (!(permissions_map[map].mapi_permission & perms))
+			continue;
+
+		object_allowed    |=  permissions_map[map].object_allowed;
+		object_denied     &= ~permissions_map[map].object_not_denied;
+		container_allowed |=  permissions_map[map].container_allowed;
+		container_denied  &= ~permissions_map[map].container_not_denied;
+	}
+
+	ace.Sid = sid;
+	ace.Header.AceSize = GUINT16_TO_LE (sizeof (ace.Header) +
+					    sizeof (ace.Mask) +
+					    E2K_SID_BINARY_SID_LEN (bsid));
+
+	ace.Header.AceType  = E2K_ACCESS_ALLOWED_ACE_TYPE;
+	ace.Header.AceFlags = E2K_OBJECT_INHERIT_ACE | E2K_INHERIT_ONLY_ACE;
+	ace.Mask = object_allowed;
+	set_ace (sd, &ace);
+	if (sid != sd->priv->default_sid) {
+		ace.Header.AceType  = E2K_ACCESS_DENIED_ACE_TYPE;
+		ace.Header.AceFlags = E2K_OBJECT_INHERIT_ACE | E2K_INHERIT_ONLY_ACE;
+		ace.Mask = object_denied;
+		set_ace (sd, &ace);
+	}
+
+	ace.Header.AceType  = E2K_ACCESS_ALLOWED_ACE_TYPE;
+	ace.Header.AceFlags = E2K_CONTAINER_INHERIT_ACE;
+	ace.Mask = container_allowed;
+	set_ace (sd, &ace);
+	if (sid != sd->priv->default_sid) {
+		ace.Header.AceType  = E2K_ACCESS_DENIED_ACE_TYPE;
+		ace.Header.AceFlags = E2K_CONTAINER_INHERIT_ACE;
+		ace.Mask = container_denied;
+		set_ace (sd, &ace);
+	}
+}
+
+static struct {
+	const gchar *name;
+	guint32 perms;
+} roles[E2K_PERMISSIONS_ROLE_NUM_ROLES] = {
+	/* i18n: These are Outlook's words for the default roles in
+	   the folder permissions dialog. */
+	{ N_("Owner"),             (E2K_PERMISSION_FOLDER_VISIBLE |
+				    E2K_PERMISSION_READ_ANY |
+				    E2K_PERMISSION_CREATE |
+				    E2K_PERMISSION_DELETE_OWNED |
+				    E2K_PERMISSION_EDIT_OWNED |
+				    E2K_PERMISSION_DELETE_ANY |
+				    E2K_PERMISSION_EDIT_ANY |
+				    E2K_PERMISSION_CREATE_SUBFOLDER |
+				    E2K_PERMISSION_CONTACT |
+				    E2K_PERMISSION_OWNER) },
+	{ N_("Publishing Editor"), (E2K_PERMISSION_FOLDER_VISIBLE |
+				    E2K_PERMISSION_READ_ANY |
+				    E2K_PERMISSION_CREATE |
+				    E2K_PERMISSION_DELETE_OWNED |
+				    E2K_PERMISSION_EDIT_OWNED |
+				    E2K_PERMISSION_DELETE_ANY |
+				    E2K_PERMISSION_EDIT_ANY |
+				    E2K_PERMISSION_CREATE_SUBFOLDER) },
+	{ N_("Editor"),            (E2K_PERMISSION_FOLDER_VISIBLE |
+				    E2K_PERMISSION_READ_ANY |
+				    E2K_PERMISSION_CREATE |
+				    E2K_PERMISSION_DELETE_OWNED |
+				    E2K_PERMISSION_EDIT_OWNED |
+				    E2K_PERMISSION_DELETE_ANY |
+				    E2K_PERMISSION_EDIT_ANY) },
+	{ N_("Publishing Author"), (E2K_PERMISSION_FOLDER_VISIBLE |
+				    E2K_PERMISSION_READ_ANY |
+				    E2K_PERMISSION_CREATE |
+				    E2K_PERMISSION_DELETE_OWNED |
+				    E2K_PERMISSION_EDIT_OWNED |
+				    E2K_PERMISSION_CREATE_SUBFOLDER) },
+	{ N_("Author"),            (E2K_PERMISSION_FOLDER_VISIBLE |
+				    E2K_PERMISSION_READ_ANY |
+				    E2K_PERMISSION_CREATE |
+				    E2K_PERMISSION_DELETE_OWNED |
+				    E2K_PERMISSION_EDIT_OWNED) },
+	{ N_("Non-editing Author"),(E2K_PERMISSION_FOLDER_VISIBLE |
+				    E2K_PERMISSION_READ_ANY |
+				    E2K_PERMISSION_CREATE |
+				    E2K_PERMISSION_DELETE_OWNED) },
+	{ N_("Reviewer"),          (E2K_PERMISSION_FOLDER_VISIBLE |
+				    E2K_PERMISSION_READ_ANY) },
+	{ N_("Contributor"),       (E2K_PERMISSION_FOLDER_VISIBLE |
+				    E2K_PERMISSION_CREATE) },
+	{ N_("None"),              (E2K_PERMISSION_FOLDER_VISIBLE) }
+};
+
+/**
+ * e2k_permissions_role_get_name:
+ * @role: a permissions role
+ *
+ * Returns the localized name corresponding to @role
+ *
+ * Return value: the name
+ **/
+const gchar *
+e2k_permissions_role_get_name (E2kPermissionsRole role)
+{
+	if (role == E2K_PERMISSIONS_ROLE_CUSTOM)
+		return _("Custom");
+
+	g_return_val_if_fail (role > E2K_PERMISSIONS_ROLE_CUSTOM &&
+			      role < E2K_PERMISSIONS_ROLE_NUM_ROLES, NULL);
+	return _(roles[role].name);
+}
+
+/**
+ * e2k_permissions_role_get_perms
+ * @role: a permissions role
+ *
+ * Returns the MAPI permissions associated with @role. @role may not
+ * be %E2K_PERMISSIONS_ROLE_CUSTOM.
+ *
+ * Return value: the MAPI permissions
+ **/
+guint32
+e2k_permissions_role_get_perms (E2kPermissionsRole role)
+{
+	g_return_val_if_fail (role >= E2K_PERMISSIONS_ROLE_CUSTOM &&
+			      role < E2K_PERMISSIONS_ROLE_NUM_ROLES, 0);
+	return roles[role].perms;
+}
+
+/**
+ * e2k_permissions_role_find:
+ * @perms: MAPI permissions
+ *
+ * Finds the #E2kPermissionsRole value associated with @perms. If
+ * @perms don't describe any standard role, the return value will be
+ * %E2K_PERMISSIONS_ROLE_CUSTOM
+ *
+ * Return value: the role
+ **/
+E2kPermissionsRole
+e2k_permissions_role_find (guint perms)
+{
+	gint role;
+
+	/* "Folder contact" isn't actually a permission, and is ignored
+	 * for purposes of roles.
+	 */
+	perms &= ~E2K_PERMISSION_CONTACT;
+
+	/* The standard "None" permission includes "Folder visible",
+	 * but 0 counts as "None" too.
+	 */
+	if (perms == 0)
+		return E2K_PERMISSIONS_ROLE_NONE;
+
+	for (role = 0; role < E2K_PERMISSIONS_ROLE_NUM_ROLES; role++) {
+		if ((roles[role].perms & ~E2K_PERMISSION_CONTACT) == perms)
+			return role;
+	}
+
+	return E2K_PERMISSIONS_ROLE_CUSTOM;
+}
diff --git a/server/lib/e2k-security-descriptor.h b/server/lib/e2k-security-descriptor.h
new file mode 100644
index 0000000..4283391
--- /dev/null
+++ b/server/lib/e2k-security-descriptor.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_SECURITY_DESCRIPTOR_H__
+#define __E2K_SECURITY_DESCRIPTOR_H__
+
+#include "e2k-types.h"
+#include <glib-object.h>
+#include <libxml/tree.h>
+
+#define E2K_TYPE_SECURITY_DESCRIPTOR            (e2k_security_descriptor_get_type ())
+#define E2K_SECURITY_DESCRIPTOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E2K_TYPE_SECURITY_DESCRIPTOR, E2kSecurityDescriptor))
+#define E2K_SECURITY_DESCRIPTOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E2K_TYPE_SECURITY_DESCRIPTOR, E2kSecurityDescriptorClass))
+#define E2K_IS_SECURITY_DESCRIPTOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E2K_TYPE_SECURITY_DESCRIPTOR))
+#define E2K_IS_SECURITY_DESCRIPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E2K_TYPE_SECURITY_DESCRIPTOR))
+
+struct _E2kSecurityDescriptor {
+	GObject parent;
+
+	E2kSecurityDescriptorPrivate *priv;
+};
+
+struct _E2kSecurityDescriptorClass {
+	GObjectClass parent_class;
+};
+
+GType                  e2k_security_descriptor_get_type      (void);
+
+E2kSecurityDescriptor *e2k_security_descriptor_new           (xmlNodePtr             xml_form,
+							      GByteArray            *binary_form);
+GByteArray            *e2k_security_descriptor_to_binary     (E2kSecurityDescriptor *sd);
+
+GList                 *e2k_security_descriptor_get_sids      (E2kSecurityDescriptor *sd);
+E2kSid                *e2k_security_descriptor_get_default   (E2kSecurityDescriptor *sd);
+void                   e2k_security_descriptor_remove_sid    (E2kSecurityDescriptor *sd,
+							      E2kSid                *sid);
+
+/* MAPI folder permissions */
+#define E2K_PERMISSION_READ_ANY         0x001
+#define E2K_PERMISSION_CREATE           0x002
+#define E2K_PERMISSION_EDIT_OWNED       0x008
+#define E2K_PERMISSION_DELETE_OWNED     0x010
+#define E2K_PERMISSION_EDIT_ANY         0x020
+#define E2K_PERMISSION_DELETE_ANY       0x040
+#define E2K_PERMISSION_CREATE_SUBFOLDER 0x080
+#define E2K_PERMISSION_OWNER            0x100
+#define E2K_PERMISSION_CONTACT          0x200
+#define E2K_PERMISSION_FOLDER_VISIBLE   0x400
+
+#define E2K_PERMISSION_EDIT_MASK	(E2K_PERMISSION_EDIT_ANY | E2K_PERMISSION_EDIT_OWNED)
+#define E2K_PERMISSION_DELETE_MASK	(E2K_PERMISSION_DELETE_ANY | E2K_PERMISSION_DELETE_OWNED)
+
+guint32 e2k_security_descriptor_get_permissions (E2kSecurityDescriptor *sd,
+						 E2kSid *sid);
+void    e2k_security_descriptor_set_permissions (E2kSecurityDescriptor *sd,
+						 E2kSid *sid,
+						 guint32 perms);
+
+/* Outlook-defined roles */
+typedef enum {
+	E2K_PERMISSIONS_ROLE_OWNER,
+	E2K_PERMISSIONS_ROLE_PUBLISHING_EDITOR,
+	E2K_PERMISSIONS_ROLE_EDITOR,
+	E2K_PERMISSIONS_ROLE_PUBLISHING_AUTHOR,
+	E2K_PERMISSIONS_ROLE_AUTHOR,
+	E2K_PERMISSIONS_ROLE_NON_EDITING_AUTHOR,
+	E2K_PERMISSIONS_ROLE_REVIEWER,
+	E2K_PERMISSIONS_ROLE_CONTRIBUTOR,
+	E2K_PERMISSIONS_ROLE_NONE,
+
+	E2K_PERMISSIONS_ROLE_NUM_ROLES,
+	E2K_PERMISSIONS_ROLE_CUSTOM = -1
+} E2kPermissionsRole;
+
+const gchar        *e2k_permissions_role_get_name  (E2kPermissionsRole role);
+guint32            e2k_permissions_role_get_perms (E2kPermissionsRole role);
+
+E2kPermissionsRole e2k_permissions_role_find      (guint perms);
+
+#endif
diff --git a/server/lib/e2k-sid.c b/server/lib/e2k-sid.c
new file mode 100644
index 0000000..53c8df6
--- /dev/null
+++ b/server/lib/e2k-sid.c
@@ -0,0 +1,317 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-sid.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <libxml/xmlmemory.h>
+
+typedef struct {
+	guint8  Revision;
+	guint8  SubAuthorityCount;
+	guint8  zero_pad[5];
+	guint8  IdentifierAuthority;
+	guint32 SubAuthority[1];
+} E2kSid_SID;
+#define E2K_SID_SID_REVISION 1
+
+struct _E2kSidPrivate {
+	E2kSidType type;
+	E2kSid_SID *binary_sid;
+	gchar *string_sid;
+	gchar *display_name;
+};
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+static void dispose (GObject *object);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	object_class->dispose = dispose;
+}
+
+static void
+init (GObject *object)
+{
+	E2kSid *sid = E2K_SID (object);
+
+	sid->priv = g_new0 (E2kSidPrivate, 1);
+}
+
+static void
+dispose (GObject *object)
+{
+	E2kSid *sid = E2K_SID (object);
+
+	if (sid->priv) {
+		if (sid->priv->string_sid)
+			g_free (sid->priv->string_sid);
+		if (sid->priv->binary_sid)
+			g_free (sid->priv->binary_sid);
+		g_free (sid->priv->display_name);
+
+		g_free (sid->priv);
+		sid->priv = NULL;
+	}
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+E2K_MAKE_TYPE (e2k_sid, E2kSid, class_init, init, PARENT_TYPE)
+
+static E2kSid *
+sid_new_internal (E2kSidType type, const gchar *display_name,
+		  const gchar *string_sid, const guint8 *binary_sid)
+{
+	E2kSid *sid;
+
+	sid = g_object_new (E2K_TYPE_SID, NULL);
+	sid->priv->type = type;
+
+	if (binary_sid)
+		sid->priv->binary_sid = g_memdup (binary_sid, E2K_SID_BINARY_SID_LEN (binary_sid));
+	if (string_sid)
+		sid->priv->string_sid = g_strdup (string_sid);
+	else if (!display_name)
+		e2k_sid_get_string_sid (sid);
+
+	if (!display_name) {
+		if (type == E2K_SID_TYPE_WELL_KNOWN_GROUP) {
+			if (!strcmp (string_sid, E2K_SID_WKS_ANONYMOUS))
+				display_name = _(E2K_SID_WKS_ANONYMOUS_NAME);
+			else if (!strcmp (string_sid, E2K_SID_WKS_EVERYONE))
+				display_name = _(E2K_SID_WKS_EVERYONE_NAME);
+		}
+		if (!display_name)
+			display_name = string_sid;
+	}
+	sid->priv->display_name = g_strdup (display_name);
+
+	return sid;
+}
+
+/**
+ * e2k_sid_new_from_string_sid:
+ * @type: the type of SID that @string_sid is
+ * @string_sid: the string form of a Windows Security Identifier
+ * @display_name: UTF-8 display name of the user/group/etc identified
+ * by @string_sid
+ *
+ * Creates an %E2kSid from the given information
+ *
+ * Return value: the new SID
+ **/
+E2kSid *
+e2k_sid_new_from_string_sid (E2kSidType type, const gchar *string_sid,
+			     const gchar *display_name)
+{
+	g_return_val_if_fail (string_sid != NULL, NULL);
+
+	if (strlen (string_sid) < 6 || strncmp (string_sid, "S-1-", 4) != 0)
+		return NULL;
+
+	return sid_new_internal (type, display_name, string_sid, NULL);
+}
+
+/**
+ * e2k_sid_new_from_binary_sid:
+ * @type: the type of SID that @binary_sid is
+ * @binary_sid: the binary form of a Windows Security Identifier
+ * @display_name: UTF-8 display name of the user/group/etc identified
+ * by @string_sid
+ *
+ * Creates an %E2kSid from the given information
+ *
+ * Return value: the new SID
+ **/
+E2kSid *
+e2k_sid_new_from_binary_sid (E2kSidType    type,
+			     const guint8 *binary_sid,
+			     const gchar   *display_name)
+{
+	g_return_val_if_fail (binary_sid != NULL, NULL);
+
+	return sid_new_internal (type, display_name, NULL, binary_sid);
+}
+
+/**
+ * e2k_sid_get_sid_type:
+ * @sid: a SID
+ *
+ * Returns the type of @sid (user, group, etc)
+ *
+ * Return value: the %E2kSidType
+ **/
+E2kSidType
+e2k_sid_get_sid_type (E2kSid *sid)
+{
+	g_return_val_if_fail (E2K_IS_SID (sid), E2K_SID_TYPE_USER);
+
+	return sid->priv->type;
+}
+
+/**
+ * e2k_sid_get_string_sid:
+ * @sid: a SID
+ *
+ * Returns the string form of @sid
+ *
+ * Return value: the string SID
+ **/
+const gchar *
+e2k_sid_get_string_sid (E2kSid *sid)
+{
+	g_return_val_if_fail (E2K_IS_SID (sid), NULL);
+
+	if (!sid->priv->string_sid) {
+		GString *string;
+		gint sa;
+
+		string = g_string_new (NULL);
+
+		/* Revision and IdentifierAuthority. */
+		g_string_append_printf (string, "S-%u-%u",
+					sid->priv->binary_sid->Revision,
+					sid->priv->binary_sid->IdentifierAuthority);
+
+		/* Subauthorities. */
+		for (sa = 0; sa < sid->priv->binary_sid->SubAuthorityCount; sa++) {
+			g_string_append_printf (string, "-%lu",
+						(gulong) GUINT32_FROM_LE (sid->priv->binary_sid->SubAuthority[sa]));
+		}
+
+		sid->priv->string_sid = string->str;
+		g_string_free (string, FALSE);
+	}
+
+	return sid->priv->string_sid;
+}
+
+/**
+ * e2k_sid_get_binary_sid:
+ * @sid: a SID
+ *
+ * Returns the binary form of @sid. Since the SID data is self-delimiting,
+ * no length value is needed. Use E2K_SID_BINARY_SID_LEN() if you need to
+ * know the size of the binary data.
+ *
+ * Return value: the binary SID
+ **/
+const guint8 *
+e2k_sid_get_binary_sid (E2kSid *sid)
+{
+	g_return_val_if_fail (E2K_IS_SID (sid), NULL);
+
+	if (!sid->priv->binary_sid) {
+		gint sa, subauth_count;
+		guint32 subauthority;
+		gchar *p;
+
+		p = sid->priv->string_sid + 4;
+		subauth_count = 0;
+		while ((p = strchr (p, '-'))) {
+			subauth_count++;
+			p++;
+		}
+
+		sid->priv->binary_sid = g_malloc0 (sizeof (E2kSid_SID) + 4 * (subauth_count - 1));
+		sid->priv->binary_sid->Revision = E2K_SID_SID_REVISION;
+		sid->priv->binary_sid->IdentifierAuthority = strtoul (sid->priv->string_sid + 4, &p, 10);
+		sid->priv->binary_sid->SubAuthorityCount = subauth_count;
+
+		sa = 0;
+		while (*p == '-' && sa < subauth_count) {
+			subauthority = strtoul (p + 1, &p, 10);
+			sid->priv->binary_sid->SubAuthority[sa++] =
+				GUINT32_TO_LE (subauthority);
+		}
+	}
+
+	return (guint8 *)sid->priv->binary_sid;
+}
+
+/**
+ * e2k_sid_get_display_name:
+ * @sid: a SID
+ *
+ * Returns the display name of the entity identified by @sid
+ *
+ * Return value: the UTF-8 display name
+ **/
+const gchar *
+e2k_sid_get_display_name (E2kSid *sid)
+{
+	g_return_val_if_fail (E2K_IS_SID (sid), NULL);
+
+	return sid->priv->display_name;
+}
+
+/**
+ * e2k_sid_binary_sid_equal:
+ * @a: pointer to a binary SID
+ * @b: pointer to another binary SID
+ *
+ * Determines if @a and @b contain the same SID data. For use
+ * with #GHashTable.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gint
+e2k_sid_binary_sid_equal (gconstpointer a, gconstpointer b)
+{
+	const guint8 *bsida = (const guint8 *)a;
+	const guint8 *bsidb = (const guint8 *)b;
+
+	if (E2K_SID_BINARY_SID_LEN (bsida) !=
+	    E2K_SID_BINARY_SID_LEN (bsidb))
+		return FALSE;
+	return memcmp (bsida, bsidb, E2K_SID_BINARY_SID_LEN (bsida)) == 0;
+}
+
+/**
+ * e2k_sid_binary_sid_hash:
+ * @key: pointer to a binary SID
+ *
+ * Hashes @key, a binary SID. For use with #GHashTable.
+ *
+ * Return value: the hash value
+ **/
+guint
+e2k_sid_binary_sid_hash (gconstpointer key)
+{
+	const guint8 *bsid = (const guint8 *)key;
+	guint32 final_sa;
+
+	/* The majority of SIDs will differ only in the last
+	 * subauthority value.
+	 */
+	memcpy (&final_sa, bsid + E2K_SID_BINARY_SID_LEN (bsid) - 4, 4);
+	return final_sa;
+}
diff --git a/server/lib/e2k-sid.h b/server/lib/e2k-sid.h
new file mode 100644
index 0000000..ac17e3c
--- /dev/null
+++ b/server/lib/e2k-sid.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2002-2004 Novell, Inc. */
+
+#ifndef __E2K_SID_H__
+#define __E2K_SID_H__
+
+#include "e2k-types.h"
+
+#include <glib-object.h>
+#include <libxml/tree.h>
+
+#define E2K_TYPE_SID            (e2k_sid_get_type ())
+#define E2K_SID(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E2K_TYPE_SID, E2kSid))
+#define E2K_SID_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E2K_TYPE_SID, E2kSidClass))
+#define E2K_IS_SID(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E2K_TYPE_SID))
+#define E2K_IS_SID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E2K_TYPE_SID))
+
+typedef enum {
+	E2K_SID_TYPE_INVALID,
+	E2K_SID_TYPE_USER,
+	E2K_SID_TYPE_ALIAS,
+	E2K_SID_TYPE_GROUP,
+	E2K_SID_TYPE_WELL_KNOWN_GROUP,
+	E2K_SID_TYPE_DOMAIN,
+	E2K_SID_TYPE_DELETED_ACCOUNT,
+	E2K_SID_TYPE_UNKNOWN,
+	E2K_SID_TYPE_COMPUTER
+} E2kSidType;
+
+struct _E2kSid {
+	GObject parent;
+
+	E2kSidPrivate *priv;
+};
+
+struct _E2kSidClass {
+	GObjectClass parent_class;
+
+};
+
+GType         e2k_sid_get_type            (void);
+
+E2kSid       *e2k_sid_new_from_string_sid (E2kSidType     type,
+					   const gchar    *string_sid,
+					   const gchar    *display_name);
+E2kSid       *e2k_sid_new_from_binary_sid (E2kSidType     type,
+					   const guint8  *binary_sid,
+					   const gchar    *display_name);
+
+E2kSidType    e2k_sid_get_sid_type        (E2kSid        *sid);
+const gchar   *e2k_sid_get_string_sid      (E2kSid        *sid);
+const guint8 *e2k_sid_get_binary_sid      (E2kSid        *sid);
+const gchar   *e2k_sid_get_display_name    (E2kSid        *sid);
+
+#define E2K_SID_BINARY_SID_MIN_LEN   8
+#define E2K_SID_BINARY_SID_LEN(bsid) (8 + ((guint8 *)bsid)[1] * 4)
+guint         e2k_sid_binary_sid_hash     (gconstpointer  key);
+gint          e2k_sid_binary_sid_equal    (gconstpointer  a,
+					   gconstpointer  b);
+
+/* Some well-known SIDs */
+#define E2K_SID_WKS_EVERYONE       "S-1-1-0"
+#define E2K_SID_WKS_EVERYONE_NAME  "Default"
+#define E2K_SID_WKS_ANONYMOUS      "S-1-5-7"
+#define E2K_SID_WKS_ANONYMOUS_NAME "Anonymous"
+
+#endif
+
diff --git a/server/lib/e2k-types.h b/server/lib/e2k-types.h
new file mode 100644
index 0000000..dfa5983
--- /dev/null
+++ b/server/lib/e2k-types.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_TYPES_H__
+#define __E2K_TYPES_H__
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+typedef struct _E2kAction                     E2kAction;
+typedef struct _E2kAddrEntry                  E2kAddrEntry;
+typedef struct _E2kAddrList                   E2kAddrList;
+
+typedef struct _E2kContext                    E2kContext;
+typedef struct _E2kContextPrivate             E2kContextPrivate;
+typedef struct _E2kContextClass               E2kContextClass;
+
+typedef struct _E2kGlobalCatalog              E2kGlobalCatalog;
+typedef struct _E2kGlobalCatalogPrivate       E2kGlobalCatalogPrivate;
+typedef struct _E2kGlobalCatalogClass         E2kGlobalCatalogClass;
+
+typedef struct _E2kOperation                  E2kOperation;
+
+typedef struct _E2kRestriction                E2kRestriction;
+
+typedef struct _E2kSecurityDescriptor         E2kSecurityDescriptor;
+typedef struct _E2kSecurityDescriptorPrivate  E2kSecurityDescriptorPrivate;
+typedef struct _E2kSecurityDescriptorClass    E2kSecurityDescriptorClass;
+
+typedef struct _E2kSid                        E2kSid;
+typedef struct _E2kSidPrivate                 E2kSidPrivate;
+typedef struct _E2kSidClass                   E2kSidClass;
+
+#define E2K_MAKE_TYPE(type_name,TypeName,class_init,init,parent) \
+GType type_name##_get_type(void)			\
+{							\
+	static GType type = 0;				\
+	if (!type){					\
+		static GTypeInfo const object_info = {	\
+			sizeof (TypeName##Class),	\
+							\
+			(GBaseInitFunc) NULL,		\
+			(GBaseFinalizeFunc) NULL,	\
+							\
+			(GClassInitFunc) class_init,	\
+			(GClassFinalizeFunc) NULL,	\
+			NULL,	/* class_data */	\
+							\
+			sizeof (TypeName),		\
+			0,	/* n_preallocs */	\
+			(GInstanceInitFunc) init,	\
+		};					\
+		type = g_type_register_static (parent, #TypeName, &object_info, 0); \
+	}						\
+	return type;					\
+}
+
+#define E2K_MAKE_TYPE_WITH_IFACE(type_name,TypeName,class_init,init,parent,iface_init,iparent) \
+GType type_name##_get_type(void)			\
+{							\
+	static GType type = 0;				\
+	if (!type){					\
+		static GTypeInfo const object_info = {	\
+			sizeof (TypeName##Class),	\
+							\
+			(GBaseInitFunc) NULL,		\
+			(GBaseFinalizeFunc) NULL,	\
+							\
+			(GClassInitFunc) class_init,	\
+			(GClassFinalizeFunc) NULL,	\
+			NULL,	/* class_data */	\
+							\
+			sizeof (TypeName),		\
+			0,	/* n_preallocs */	\
+			(GInstanceInitFunc) init,	\
+		};					\
+		static GInterfaceInfo const iface_info = {	\
+			(GInterfaceInitFunc) iface_init,	\
+			NULL,					\
+			NULL					\
+		};						\
+		type = g_type_register_static (parent, #TypeName, &object_info, 0);	\
+		g_type_add_interface_static (type, iparent, &iface_info);		\
+	}						\
+	return type;					\
+}
+
+/* Put "E2K_KEEP_PRECEDING_COMMENT_OUT_OF_PO_FILES;" on a line to
+ * separate a _() from a comment that doesn't go with it.
+ */
+#define E2K_KEEP_PRECEDING_COMMENT_OUT_OF_PO_FILES
+
+#endif
diff --git a/server/lib/e2k-uri.c b/server/lib/e2k-uri.c
new file mode 100644
index 0000000..93c0f2a
--- /dev/null
+++ b/server/lib/e2k-uri.c
@@ -0,0 +1,418 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 1999-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "e2k-uri.h"
+
+/**
+ * e2k_uri_new:
+ * @uri_string: the URI
+ *
+ * Parses @uri_string.
+ *
+ * Return value: a parsed %E2kUri
+ **/
+E2kUri *
+e2k_uri_new (const gchar *uri_string)
+{
+	E2kUri *uri;
+	const gchar *end, *hash, *colon, *semi, *at, *slash;
+	const gchar *question, *p;
+
+	uri = g_new0 (E2kUri, 1);
+
+	/* Find fragment. */
+	end = hash = strchr (uri_string, '#');
+	if (hash && hash[1]) {
+		uri->fragment = g_strdup (hash + 1);
+		e2k_uri_decode (uri->fragment);
+	} else
+		end = uri_string + strlen (uri_string);
+
+	/* Find protocol: initial [a-z+.-]* substring until ":" */
+	p = uri_string;
+	while (p < end && (isalnum ((guchar)*p) ||
+			   *p == '.' || *p == '+' || *p == '-'))
+		p++;
+
+	if (p > uri_string && *p == ':') {
+		uri->protocol = g_ascii_strdown (uri_string, p - uri_string);
+		uri_string = p + 1;
+	}
+
+	if (!*uri_string)
+		return uri;
+
+	/* Check for authority */
+	if (strncmp (uri_string, "//", 2) == 0) {
+		uri_string += 2;
+
+		slash = uri_string + strcspn (uri_string, "/#");
+		at = strchr (uri_string, '@');
+		if (at && at < slash) {
+			gchar *backslash;
+
+			colon = strchr (uri_string, ':');
+			if (colon && colon < at) {
+				uri->passwd = g_strndup (colon + 1,
+							 at - colon - 1);
+				e2k_uri_decode (uri->passwd);
+			} else {
+				uri->passwd = NULL;
+				colon = at;
+			}
+
+			semi = strchr(uri_string, ';');
+			if (semi && semi < colon &&
+			    !g_ascii_strncasecmp (semi, ";auth=", 6)) {
+				uri->authmech = g_strndup (semi + 6,
+							   colon - semi - 6);
+				e2k_uri_decode (uri->authmech);
+			} else {
+				uri->authmech = NULL;
+				semi = colon;
+			}
+
+			uri->user = g_strndup (uri_string, semi - uri_string);
+			e2k_uri_decode (uri->user);
+			uri_string = at + 1;
+
+			backslash = strchr (uri->user, '\\');
+			if (!backslash)
+				backslash = strchr (uri->user, '/');
+			if (backslash) {
+				uri->domain = uri->user;
+				*backslash = '\0';
+				uri->user = g_strdup (backslash + 1);
+			}
+		} else
+			uri->user = uri->passwd = uri->domain = NULL;
+
+		/* Find host and port. */
+		colon = strchr (uri_string, ':');
+		if (colon && colon < slash) {
+			uri->host = g_strndup (uri_string, colon - uri_string);
+			uri->port = strtoul (colon + 1, NULL, 10);
+		} else {
+			uri->host = g_strndup (uri_string, slash - uri_string);
+			e2k_uri_decode (uri->host);
+			uri->port = 0;
+		}
+
+		uri_string = slash;
+	}
+
+	/* Find query */
+	question = memchr (uri_string, '?', end - uri_string);
+	if (question) {
+		if (question[1]) {
+			uri->query = g_strndup (question + 1,
+						end - (question + 1));
+			e2k_uri_decode (uri->query);
+		}
+		end = question;
+	}
+
+	/* Find parameters */
+	semi = memchr (uri_string, ';', end - uri_string);
+	if (semi) {
+		if (semi[1]) {
+			const gchar *cur, *p, *eq;
+			gchar *name, *value;
+
+			for (cur = semi + 1; cur < end; cur = p + 1) {
+				p = memchr (cur, ';', end - cur);
+				if (!p)
+					p = end;
+				eq = memchr (cur, '=', p - cur);
+				if (eq) {
+					name = g_strndup (cur, eq - cur);
+					value = g_strndup (eq + 1, p - (eq + 1));
+					e2k_uri_decode (value);
+				} else {
+					name = g_strndup (cur, p - cur);
+					value = g_strdup ("");
+				}
+				e2k_uri_decode (name);
+				g_datalist_set_data_full (&uri->params, name,
+							  value, g_free);
+				g_free (name);
+			}
+		}
+		end = semi;
+	}
+
+	if (end != uri_string) {
+		uri->path = g_strndup (uri_string, end - uri_string);
+		e2k_uri_decode (uri->path);
+	}
+
+	return uri;
+}
+
+/**
+ * e2k_uri_free:
+ * @uri: an %E2kUri
+ *
+ * Frees @uri
+ **/
+void
+e2k_uri_free (E2kUri *uri)
+{
+	if (uri) {
+		g_free (uri->protocol);
+		g_free (uri->user);
+		g_free (uri->domain);
+		g_free (uri->authmech);
+		g_free (uri->passwd);
+		g_free (uri->host);
+		g_free (uri->path);
+		g_datalist_clear (&uri->params);
+		g_free (uri->query);
+		g_free (uri->fragment);
+
+		g_free (uri);
+	}
+}
+
+/**
+ * e2k_uri_get_param:
+ * @uri: an %E2kUri
+ * @name: name of the parameter
+ *
+ * Fetches a parameter from @uri
+ *
+ * Return value: the value of @name, or %NULL if it is not set
+ **/
+const gchar *
+e2k_uri_get_param (E2kUri *uri, const gchar *name)
+{
+	return g_datalist_get_data (&uri->params, name);
+}
+
+#define HEXVAL(c) (isdigit (c) ? (c) - '0' : g_ascii_tolower (c) - 'a' + 10)
+
+/**
+ * e2k_uri_decode:
+ * @part: a piece of a URI
+ *
+ * Undoes URI-escaping in @part in-place.
+ **/
+void
+e2k_uri_decode (gchar *part)
+{
+	guchar *s, *d;
+
+	s = d = (guchar *)part;
+	while (*s) {
+		if (*s == '%') {
+			if (isxdigit (s[1]) && isxdigit (s[2])) {
+				*d++ = HEXVAL (s[1]) * 16 + HEXVAL (s[2]);
+				s += 3;
+			} else
+				*d++ = *s++;
+		} else
+			*d++ = *s++;
+	}
+	*d = '\0';
+}
+
+static const gint uri_encoded_char[] = {
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x00 - 0x0f */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x10 - 0x1f */
+	1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2,  /*  ' ' - '/'  */
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2,  /*  '0' - '?'  */
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '@' - 'O'  */
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 0,  /*  'P' - '_'  */
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  '`' - 'o'  */
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1,  /*  'p' - 0x7f */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+/**
+ * e2k_uri_append_encoded:
+ * @str: a %GString containing part of a URI
+ * @in: data to append to @str
+ * @wss_encode: whether or not to use the special Web Storage System
+ * encoding rules
+ * @extra_enc_chars: additional characters beyond the normal URI-reserved
+ * characters to encode when appending to @str
+ *
+ * Appends @in to @str, encoding URI-unsafe characters as needed
+ * (optionally including some Exchange-specific encodings).
+ *
+ * When appending a path, you must append each segment separately;
+ * e2k_uri_append_encoded() will encode any "/"s passed in.
+ **/
+void
+e2k_uri_append_encoded (GString *str, const gchar *in,
+			gboolean wss_encode, const gchar *extra_enc_chars)
+{
+	const guchar *s = (const guchar *)in;
+
+	while (*s) {
+		if (extra_enc_chars && strchr (extra_enc_chars, *s))
+			goto escape;
+		switch (uri_encoded_char[*s]) {
+		case 2:
+			if (!wss_encode)
+				goto escape;
+			switch (*s++) {
+			case '/':
+				g_string_append (str, "_xF8FF_");
+				break;
+			case '?':
+				g_string_append (str, "_x003F_");
+				break;
+			case '\\':
+				g_string_append (str, "_xF8FE_");
+				break;
+			case '~':
+				g_string_append (str, "_x007E_");
+				break;
+			}
+			break;
+		case 1:
+		escape:
+			g_string_append_printf (str, "%%%02x", (gint)*s++);
+			break;
+		default:
+			g_string_append_c (str, *s++);
+			break;
+		}
+	}
+}
+
+/**
+ * e2k_uri_encode:
+ * @in: data to encode
+ * @wss_encode: whether or not to use the special Web Storage System
+ * encoding rules
+ * @extra_enc_chars: additional characters beyond the normal URI-reserved
+ * characters to encode when appending to @str
+ *
+ * Encodes URI-unsafe characters as in e2k_uri_append_encoded()
+ *
+ * Return value: the encoded string
+ **/
+gchar *
+e2k_uri_encode (const gchar *in, gboolean wss_encode,
+		const gchar *extra_enc_chars)
+{
+	GString *string;
+	gchar *out;
+
+	string = g_string_new (NULL);
+	e2k_uri_append_encoded (string, in, wss_encode, extra_enc_chars);
+	out = string->str;
+	g_string_free (string, FALSE);
+
+	return out;
+}
+
+/**
+ * e2k_uri_path:
+ * @uri_string: a well-formed absolute URI
+ *
+ * Returns the path component of @uri_string, including the initial
+ * "/". (The return value is actually a pointer into the passed-in
+ * string, meaning this will only really work if the URI has no
+ * query/fragment/etc.)
+ *
+ * Return value: the path component of @uri_string.
+ **/
+const gchar *
+e2k_uri_path (const gchar *uri_string)
+{
+	const gchar *p;
+
+	p = strchr (uri_string, ':');
+	if (p++) {
+		if (!strncmp (p, "//", 2)) {
+			p = strchr (p + 2, '/');
+			if (p)
+				return p;
+		} else if (*p)
+			return p;
+	}
+	return "";
+}
+
+/**
+ * e2k_uri_concat:
+ * @uri_prefix: an absolute URI
+ * @tail: a relative path
+ *
+ * Constructs a new URI consisting of the concatenation of
+ * @uri_prefix and @tail. If @uri_prefix does not end with a "/",
+ * one will be inserted between @uri_prefix and @tail.
+ *
+ * Return value: the new URI
+ **/
+gchar *
+e2k_uri_concat (const gchar *uri_prefix, const gchar *tail)
+{
+	const gchar *p;
+
+	p = strrchr (uri_prefix, '/');
+	if (p && !p[1])
+		return g_strdup_printf ("%s%s", uri_prefix, tail);
+	else
+		return g_strdup_printf ("%s/%s", uri_prefix, tail);
+}
+
+/**
+ * e2k_uri_relative:
+ * @uri_prefix: an absolute URI
+ * @uri: another URI, presumably a child of @uri_prefix
+ *
+ * Returns a URI describing @uri's relation to @uri_prefix; either a
+ * relative URI consisting of the subpath of @uri underneath
+ * @uri_prefix, or all of @uri if it is not a sub-uri of @uri_prefix.
+ *
+ * Return value: the relative URI
+ **/
+const gchar *
+e2k_uri_relative (const gchar *uri_prefix, const gchar *uri)
+{
+	gint prefix_len = strlen (uri_prefix);
+
+	if (!strncmp (uri_prefix, uri, prefix_len)) {
+		uri += prefix_len;
+		while (*uri == '/')
+			uri++;
+	}
+
+	return uri;
+}
diff --git a/server/lib/e2k-uri.h b/server/lib/e2k-uri.h
new file mode 100644
index 0000000..5df234d
--- /dev/null
+++ b/server/lib/e2k-uri.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef E2K_URI_H
+#define E2K_URI_H
+
+#include <glib.h>
+
+typedef struct {
+	gchar  *protocol;
+	gchar  *user;
+	gchar  *domain;
+	gchar  *authmech;
+	gchar  *passwd;
+	gchar  *host;
+	gint    port;
+	gchar  *path;
+	GData *params;
+	gchar  *query;
+	gchar  *fragment;
+} E2kUri;
+
+E2kUri *    e2k_uri_new       (const gchar *uri_string);
+void        e2k_uri_free      (E2kUri *uri);
+const gchar *e2k_uri_get_param (E2kUri *uri, const gchar *name);
+
+void        e2k_uri_decode         (gchar *part);
+gchar *      e2k_uri_encode         (const gchar *in,
+				    gboolean    wss_encode,
+				    const gchar *extra_enc_chars);
+void        e2k_uri_append_encoded (GString    *str,
+				    const gchar *in,
+				    gboolean    wss_encode,
+				    const gchar *extra_enc_chars);
+
+const gchar *e2k_uri_path      (const gchar *uri_string);
+
+gchar       *e2k_uri_concat    (const gchar *uri_prefix, const gchar *tail);
+const gchar *e2k_uri_relative  (const gchar *uri_prefix, const gchar *uri);
+
+#endif /* E2K_URI_H */
diff --git a/server/lib/e2k-utils.c b/server/lib/e2k-utils.c
new file mode 100644
index 0000000..bab34ce
--- /dev/null
+++ b/server/lib/e2k-utils.c
@@ -0,0 +1,668 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-utils.h"
+#include "e2k-autoconfig.h"
+#include "e2k-propnames.h"
+#include "e2k-rule.h"
+
+#include <libedataserver/e-time-utils.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Do not internationalize */
+const gchar *e2k_rfc822_months [] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+/**
+ * e2k_parse_timestamp:
+ * @timestamp: an ISO8601 timestamp returned by the Exchange server
+ *
+ * Converts @timestamp to a %time_t value. @timestamp must be in one
+ * of the two ISO8601 variants used by Exchange.
+ *
+ * Note that the timestamps used (in most contexts) by Exchange have
+ * millisecond resolution, so converting them to %time_t loses
+ * resolution. Since ISO8601 timestamps can be compared using
+ * strcmp(), it is often best to keep them as strings.
+ *
+ * Return value: the %time_t corresponding to @timestamp, or -1 on
+ * error.
+ **/
+time_t
+e2k_parse_timestamp (const gchar *timestamp)
+{
+	struct tm tm;
+
+	tm.tm_year = strtoul (timestamp, (gchar **)&timestamp, 10) - 1900;
+	if (*timestamp++ != '-')
+		return -1;
+	tm.tm_mon = strtoul (timestamp, (gchar **)&timestamp, 10) - 1;
+	if (*timestamp++ != '-')
+		return -1;
+	tm.tm_mday = strtoul (timestamp, (gchar **)&timestamp, 10);
+	if (*timestamp++ != 'T')
+		return -1;
+	tm.tm_hour = strtoul (timestamp, (gchar **)&timestamp, 10);
+	if (*timestamp++ != ':')
+		return -1;
+	tm.tm_min = strtoul (timestamp, (gchar **)&timestamp, 10);
+	if (*timestamp++ != ':')
+		return -1;
+	tm.tm_sec = strtoul (timestamp, (gchar **)&timestamp, 10);
+	if (*timestamp != '.' && *timestamp != 'Z')
+		return -1;
+
+	return e_mktime_utc (&tm);
+}
+
+/**
+ * e2k_make_timestamp:
+ * @when: the %time_t to convert to an ISO8601 timestamp
+ *
+ * Creates an ISO8601 timestamp (in an format acceptable to Exchange)
+ * corresponding to @when.
+ *
+ * Return value: the timestamp, which the caller must free.
+ **/
+gchar *
+e2k_make_timestamp (time_t when)
+{
+	struct tm *tm;
+
+	tm = gmtime (&when);
+	return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02dZ",
+				tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+				tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+/**
+ * e2k_make_timestamp_rfc822:
+ * @when: the %time_t to convert to an RFC822 timestamp
+ *
+ * Creates an RFC822 Date header value corresponding to @when, in the
+ * locale timezone.
+ *
+ * Return value: the timestamp, which the caller must free.
+ **/
+gchar *
+e2k_make_timestamp_rfc822 (time_t when)
+{
+	struct tm tm;
+	gint offset;
+
+	e_localtime_with_offset (when, &tm, &offset);
+	offset = (offset / 3600) * 100 + (offset / 60) % 60;
+
+	return g_strdup_printf ("%02d %s %04d %02d:%02d:%02d %+05d",
+				tm.tm_mday, e2k_rfc822_months[tm.tm_mon],
+				tm.tm_year + 1900,
+				tm.tm_hour, tm.tm_min, tm.tm_sec,
+				offset);
+}
+
+/* SYSTIME_OFFSET is the number of minutes between the Windows epoch
+ * (1601-01-01T00:00:00Z) and the time_t epoch (1970-01-01T00:00:00Z):
+ * 369 years, 89 of which are leap years.
+ */
+#define SYSTIME_OFFSET 194074560UL
+
+/**
+ * e2k_systime_to_time_t:
+ * @systime: a MAPI PT_SYSTIME value (minutes since Windows epoch)
+ *
+ * Converts the MAPI PT_SYSTIME value @systime to a corresponding
+ * %time_t value (assuming it is within the valid range of a %time_t).
+ *
+ * Return value: a %time_t corresponding to @systime.
+ **/
+time_t
+e2k_systime_to_time_t (guint32 systime)
+{
+	return (systime - SYSTIME_OFFSET) * 60;
+}
+
+/**
+ * e2k_systime_from_time_t:
+ * @tt: a %time_t value
+ *
+ * Converts the %time_t value @tt to a corresponding MAPI PT_SYSTIME
+ * value, losing some precision if @tt does not fall on a minute
+ * boundary.
+ *
+ * Return value: the Windows systime value corresponding to @tt
+ **/
+guint32
+e2k_systime_from_time_t (time_t tt)
+{
+	return (tt / 60) + SYSTIME_OFFSET;
+}
+
+/**
+ * e2k_filetime_to_time_t:
+ * @filetime: a Windows FILETIME value (100ns intervals since
+ * Windows epoch)
+ *
+ * Converts the Windows FILETIME value @filetime to a corresponding
+ * %time_t value (assuming it is within the valid range of a %time_t),
+ * truncating to a second boundary.
+ *
+ * Return value: a %time_t corresponding to @filetime.
+ **/
+time_t
+e2k_filetime_to_time_t (guint64 filetime)
+{
+	return (time_t)(filetime / 10000000 - SYSTIME_OFFSET * 60);
+}
+
+/**
+ * e2k_filetime_from_time_t:
+ * @tt: a %time_t value
+ *
+ * Converts the %time_t value @tt to a corresponding Windows FILETIME
+ * value.
+ *
+ * Return value: the Windows FILETIME value corresponding to @tt
+ **/
+guint64
+e2k_filetime_from_time_t (time_t tt)
+{
+	return (((guint64)tt) + ((guint64)SYSTIME_OFFSET) * 60) * 10000000;
+}
+
+/**
+ * e2k_lf_to_crlf:
+ * @in: input text in UNIX ("\n") format
+ *
+ * Creates a copy of @in with all LFs converted to CRLFs.
+ *
+ * Return value: the converted text, which the caller must free.
+ **/
+gchar *
+e2k_lf_to_crlf (const gchar *in)
+{
+	gint len;
+	const gchar *s;
+	gchar *out, *d;
+
+	g_return_val_if_fail (in != NULL, NULL);
+
+	len = strlen (in);
+	for (s = strchr (in, '\n'); s; s = strchr (s + 1, '\n'))
+		len++;
+
+	out = g_malloc (len + 1);
+	for (s = in, d = out; *s; s++) {
+		if (*s == '\n')
+			*d++ = '\r';
+		*d++ = *s;
+	}
+	*d = '\0';
+
+	return out;
+}
+
+/**
+ * e2k_crlf_to_lf:
+ * @in: input text in network ("\r\n") format
+ *
+ * Creates a copy of @in with all CRLFs converted to LFs. (Actually,
+ * it just strips CRs, so any raw CRs will be removed.)
+ *
+ * Return value: the converted text, which the caller must free.
+ **/
+gchar *
+e2k_crlf_to_lf (const gchar *in)
+{
+	gint len;
+	const gchar *s;
+	gchar *out;
+	GString *str;
+
+	g_return_val_if_fail (in != NULL, NULL);
+
+	str = g_string_new ("");
+
+	len = strlen (in);
+	for (s = in; *s; s++) {
+		if (*s != '\r')
+			str = g_string_append_c (str, *s);
+	}
+
+	out = str->str;
+	g_string_free (str, FALSE);
+
+	return out;
+}
+
+/**
+ * e2k_strdup_with_trailing_slash:
+ * @path: a URI or path
+ *
+ * Copies @path, appending a "/" to it if and only if it did not
+ * already end in "/".
+ *
+ * Return value: the path, which the caller must free
+ **/
+gchar *
+e2k_strdup_with_trailing_slash (const gchar *path)
+{
+	gchar *p;
+
+	if (!path || !*path)
+		return NULL;
+
+	p = strrchr (path, '/');
+	if (p && !p[1])
+		return g_strdup (path);
+	else
+		return g_strdup_printf ("%s/", path);
+}
+
+/**
+ * e2k_entryid_to_dn:
+ * @entryid: an Exchange entryid
+ *
+ * Finds an Exchange 5.5 DN inside a binary entryid property (such as
+ * #PR_STORE_ENTRYID or an element of #PR_DELEGATES_ENTRYIDS).
+ *
+ * Return value: the entryid, which is a pointer into @entryid's data.
+ **/
+const gchar *
+e2k_entryid_to_dn (GByteArray *entryid)
+{
+	gchar *p;
+
+	p = ((gchar *)entryid->data) + entryid->len - 1;
+	if (*p == 0) {
+		while (*(p - 1) && p > (gchar *)entryid->data)
+			p--;
+		if (*p == '/')
+			return p;
+	}
+	return NULL;
+}
+
+static void
+append_permanenturl_section (GString *url, guint8 *entryid)
+{
+	gint i = 0;
+
+	/* First part */
+	while (i < 16)
+		g_string_append_printf (url, "%02x", entryid[i++]);
+
+	/* Replace 0s with a single '-' */
+	g_string_append_c (url, '-');
+	while (i < 22 && entryid[i] == 0)
+		i++;
+
+	/* Last part; note that if the first non-0 byte can be
+	 * expressed in a single hex digit, we do so. (ie, the 0
+	 * in the 16's place was also accumulated into the
+	 * preceding '-'.)
+	 */
+	if (i < 22 && entryid[i] < 0x10)
+		g_string_append_printf (url, "%01x", entryid[i++]);
+	while (i < 22)
+		g_string_append_printf (url, "%02x", entryid[i++]);
+}
+
+#define E2K_PERMANENTURL_INFIX "-FlatUrlSpace-"
+#define E2K_PERMANENTURL_INFIX_LEN (sizeof (E2K_PERMANENTURL_INFIX) - 1)
+
+/**
+ * e2k_entryid_to_permanenturl:
+ * @entryid: an ENTRYID (specifically, a PR_SOURCE_KEY)
+ * @base_uri: base URI of the store containing @entryid
+ *
+ * Creates a permanenturl based on @entryid and @base_uri.
+ *
+ * Return value: the permanenturl, which the caller must free.
+ **/
+gchar *
+e2k_entryid_to_permanenturl (GByteArray *entryid, const gchar *base_uri)
+{
+	GString *url;
+	gchar *ret;
+
+	g_return_val_if_fail (entryid->len == 22 || entryid->len == 44, NULL);
+
+	url = g_string_new (base_uri);
+	if (url->str[url->len - 1] != '/')
+		g_string_append_c (url, '/');
+	g_string_append (url, E2K_PERMANENTURL_INFIX);
+	g_string_append_c (url, '/');
+
+	append_permanenturl_section (url, entryid->data);
+	if (entryid->len > 22) {
+		g_string_append_c (url, '/');
+		append_permanenturl_section (url, entryid->data + 22);
+	}
+
+	ret = url->str;
+	g_string_free (url, FALSE);
+	return ret;
+}
+
+#define HEXVAL(c) (isdigit (c) ? (c) - '0' : g_ascii_tolower (c) - 'a' + 10)
+
+static gboolean
+append_entryid_section (GByteArray *entryid, const gchar **permanenturl)
+{
+	const gchar *p;
+	guint8 buf[44], byte;
+	gint endlen;
+
+	p = *permanenturl;
+	if (strspn (p, "0123456789abcdefABCDEF") != 32)
+		return FALSE;
+	if (p[32] != '-')
+		return FALSE;
+	endlen = strspn (p + 33, "0123456789abcdefABCDEF");
+	if (endlen > 6)
+		return FALSE;
+
+	/* Expand to the full form by replacing the "-" with "0"s */
+	memcpy (buf, p, 32);
+	memset (buf + 32, '0', sizeof (buf) - 32 - endlen);
+	memcpy (buf + sizeof (buf) - endlen, p + 33, endlen);
+
+	p = (gchar *) buf;
+	while (p < (gchar *) buf + sizeof (buf)) {
+		byte = (HEXVAL (*p) << 4) + HEXVAL (*(p + 1));
+		g_byte_array_append (entryid, &byte, 1);
+		p += 2;
+	}
+
+	*permanenturl += 33 + endlen;
+	return TRUE;
+}
+
+/**
+ * e2k_permanenturl_to_entryid:
+ * @permanenturl: an Exchange permanenturl
+ *
+ * Creates an ENTRYID (specifically, a PR_SOURCE_KEY) based on
+ * @permanenturl
+ *
+ * Return value: the entryid
+ **/
+GByteArray *
+e2k_permanenturl_to_entryid (const gchar *permanenturl)
+{
+	GByteArray *entryid;
+
+	permanenturl = strstr (permanenturl, E2K_PERMANENTURL_INFIX);
+	if (!permanenturl)
+		return NULL;
+	permanenturl += E2K_PERMANENTURL_INFIX_LEN;
+
+	entryid = g_byte_array_new ();
+	while (*permanenturl++ == '/') {
+		if (!append_entryid_section (entryid, &permanenturl)) {
+			g_byte_array_free (entryid, TRUE);
+			return NULL;
+		}
+	}
+
+	return entryid;
+}
+
+/**
+ * e2k_ascii_strcase_equal
+ * @v: a string
+ * @v2: another string
+ *
+ * ASCII-case-insensitive comparison function for use with #GHashTable.
+ *
+ * Return value: %TRUE if @v and @v2 are ASCII-case-insensitively
+ * equal, %FALSE if not.
+ **/
+gint
+e2k_ascii_strcase_equal (gconstpointer v, gconstpointer v2)
+{
+	return !g_ascii_strcasecmp (v, v2);
+}
+
+/**
+ * e2k_ascii_strcase_hash
+ * @v: a string
+ *
+ * ASCII-case-insensitive hash function for use with #GHashTable.
+ *
+ * Return value: An ASCII-case-insensitive hashing of @v.
+ **/
+guint
+e2k_ascii_strcase_hash (gconstpointer v)
+{
+	/* case-insensitive g_str_hash */
+
+	const guchar *p = v;
+	guint h = g_ascii_tolower (*p);
+
+	if (h) {
+		for (p += 1; *p != '\0'; p++)
+			h = (h << 5) - h + g_ascii_tolower (*p);
+	}
+
+	return h;
+}
+
+/**
+ * e2k_restriction_folders_only:
+ * @rn: a restriction
+ *
+ * Examines @rn, and determines if it can only return folders
+ *
+ * Return value: %TRUE if @rn will cause only folders to be returned
+ **/
+gboolean
+e2k_restriction_folders_only (E2kRestriction *rn)
+{
+	gint i;
+
+	if (!rn)
+		return FALSE;
+
+	switch (rn->type) {
+	case E2K_RESTRICTION_PROPERTY:
+		if (strcmp (rn->res.property.pv.prop.name,
+			    E2K_PR_DAV_IS_COLLECTION) != 0)
+			return FALSE;
+
+		/* return TRUE if it's "= TRUE" or "!= FALSE" */
+		return (rn->res.property.relop == E2K_RELOP_EQ) ==
+			(rn->res.property.pv.value != NULL);
+
+	case E2K_RESTRICTION_AND:
+		for (i = 0; i < rn->res.and.nrns; i++) {
+			if (e2k_restriction_folders_only (rn->res.and.rns[i]))
+				return TRUE;
+		}
+		return FALSE;
+
+	case E2K_RESTRICTION_OR:
+		for (i = 0; i < rn->res.or.nrns; i++) {
+			if (!e2k_restriction_folders_only (rn->res.or.rns[i]))
+				return FALSE;
+		}
+		return TRUE;
+
+	case E2K_RESTRICTION_NOT:
+		return !e2k_restriction_folders_only (rn->res.not.rn);
+
+	case E2K_RESTRICTION_COMMENT:
+		return e2k_restriction_folders_only (rn->res.comment.rn);
+
+	default:
+		return FALSE;
+	}
+}
+
+/* From MAPIDEFS.H */
+static const gchar MAPI_ONE_OFF_UID[] = {
+	0x81, 0x2b, 0x1f, 0xa4, 0xbe, 0xa3, 0x10, 0x19,
+	0x9d, 0x6e, 0x00, 0xdd, 0x01, 0x0f, 0x54, 0x02
+};
+#define MAPI_ONE_OFF_UNICODE	  0x8000
+#define MAPI_ONE_OFF_NO_RICH_INFO 0x0001
+#define MAPI_ONE_OFF_MYSTERY_FLAG 0x1000
+
+/**
+ * e2k_entryid_generate_oneoff:
+ * @display_name: the display name of the user
+ * @email: the email address
+ * @unicode: %TRUE to generate a Unicode ENTRYID (in which case
+ * @display_name should be UTF-8), %FALSE for an ASCII ENTRYID.
+ *
+ * Constructs a "one-off" ENTRYID value that can be used as a MAPI
+ * recipient (eg, for a message forwarding server-side rule),
+ * corresponding to @display_name and @email.
+ *
+ * Return value: the recipient ENTRYID
+ **/
+GByteArray *
+e2k_entryid_generate_oneoff (const gchar *display_name, const gchar *email, gboolean unicode)
+{
+	GByteArray *entryid;
+
+	entryid = g_byte_array_new ();
+
+	e2k_rule_append_uint32 (entryid, 0);
+	g_byte_array_append (entryid, (guint8 *) MAPI_ONE_OFF_UID, sizeof (MAPI_ONE_OFF_UID));
+	e2k_rule_append_uint16 (entryid, 0);
+	e2k_rule_append_uint16 (entryid,
+				MAPI_ONE_OFF_NO_RICH_INFO |
+				MAPI_ONE_OFF_MYSTERY_FLAG |
+				(unicode ? MAPI_ONE_OFF_UNICODE : 0));
+
+	if (unicode) {
+		e2k_rule_append_unicode (entryid, display_name);
+		e2k_rule_append_unicode (entryid, "SMTP");
+		e2k_rule_append_unicode (entryid, email);
+	} else {
+		e2k_rule_append_string (entryid, display_name);
+		e2k_rule_append_string (entryid, "SMTP");
+		e2k_rule_append_string (entryid, email);
+	}
+
+	return entryid;
+}
+
+static const gchar MAPI_LOCAL_UID[] = {
+	0xdc, 0xa7, 0x40, 0xc8, 0xc0, 0x42, 0x10, 0x1a,
+	0xb4, 0xb9, 0x08, 0x00, 0x2b, 0x2f, 0xe1, 0x82
+};
+
+/**
+ * e2k_entryid_generate_local:
+ * @exchange_dn: the Exchange 5.5-style DN of the local user
+ *
+ * Constructs an ENTRYID value that can be used as a MAPI
+ * recipient (eg, for a message forwarding server-side rule),
+ * corresponding to the local user identified by @exchange_dn.
+ *
+ * Return value: the recipient ENTRYID
+ **/
+GByteArray *
+e2k_entryid_generate_local (const gchar *exchange_dn)
+{
+	GByteArray *entryid;
+
+	entryid = g_byte_array_new ();
+
+	e2k_rule_append_uint32 (entryid, 0);
+	g_byte_array_append (entryid, (guint8 *) MAPI_LOCAL_UID, sizeof (MAPI_LOCAL_UID));
+	e2k_rule_append_uint16 (entryid, 1);
+	e2k_rule_append_uint16 (entryid, 0);
+	e2k_rule_append_string (entryid, exchange_dn);
+
+	return entryid;
+}
+
+static const gchar MAPI_CONTACT_UID[] = {
+	0xfe, 0x42, 0xaa, 0x0a, 0x18, 0xc7, 0x1a, 0x10,
+	0xe8, 0x85, 0x0b, 0x65, 0x1c, 0x24, 0x00, 0x00
+};
+
+/**
+ * e2k_entryid_generate_contact:
+ * @contact_entryid: the #PR_ENTRYID of an item in the user's Contacts
+ * folder.
+ * @nth_address: which of the contact's email addresses to use.
+ *
+ * Constructs an ENTRYID value that can be used as a MAPI recipient
+ * (eg, for a message forwarding server-side rule), corresponding to
+ * the Contacts folder entry identified by @contact_entryid.
+ *
+ * Return value: the recipient ENTRYID
+ **/
+GByteArray *
+e2k_entryid_generate_contact (GByteArray *contact_entryid, gint nth_address)
+{
+	GByteArray *entryid;
+
+	entryid = g_byte_array_new ();
+
+	e2k_rule_append_uint32 (entryid, 0);
+	g_byte_array_append (entryid, (guint8 *) MAPI_CONTACT_UID, sizeof (MAPI_CONTACT_UID));
+	e2k_rule_append_uint32 (entryid, 3);
+	e2k_rule_append_uint32 (entryid, 4);
+	e2k_rule_append_uint32 (entryid, nth_address);
+	e2k_rule_append_uint32 (entryid, contact_entryid->len);
+	g_byte_array_append (entryid, contact_entryid->data, contact_entryid->len);
+
+	return entryid;
+}
+
+/**
+ * e2k_search_key_generate:
+ * @addrtype: the type of @address (usually "SMTP" or "EX")
+ * @address: the address data
+ *
+ * Constructs a PR_SEARCH_KEY value for @address
+ *
+ * Return value: the search key
+ **/
+GByteArray *
+e2k_search_key_generate (const gchar *addrtype, const gchar *address)
+{
+	GByteArray *search_key;
+	guint8 *p;
+
+	search_key = g_byte_array_new ();
+	g_byte_array_append (search_key, (guint8 *) addrtype, strlen (addrtype));
+	g_byte_array_append (search_key, (guint8 *) ":", 1);
+	g_byte_array_append (search_key, (guint8 *) address, strlen (address));
+	g_byte_array_append (search_key, (guint8 *) "", 1);
+
+	for (p = search_key->data; *p; p++)
+		*p = g_ascii_toupper (*p);
+
+	return search_key;
+}
diff --git a/server/lib/e2k-utils.h b/server/lib/e2k-utils.h
new file mode 100644
index 0000000..cc71398
--- /dev/null
+++ b/server/lib/e2k-utils.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef E2K_UTILS_H
+#define E2K_UTILS_H
+
+#include <time.h>
+#include "e2k-types.h"
+
+time_t  e2k_parse_timestamp       (const gchar *timestamp);
+gchar   *e2k_make_timestamp        (time_t when);
+gchar   *e2k_make_timestamp_rfc822 (time_t when);
+
+time_t  e2k_systime_to_time_t     (guint32 systime);
+guint32 e2k_systime_from_time_t   (time_t tt);
+
+time_t  e2k_filetime_to_time_t    (guint64 filetime);
+guint64 e2k_filetime_from_time_t  (time_t tt);
+
+gchar *e2k_lf_to_crlf (const gchar *in);
+gchar *e2k_crlf_to_lf (const gchar *in);
+
+gchar *e2k_strdup_with_trailing_slash (const gchar *path);
+
+const gchar *e2k_entryid_to_dn           (GByteArray *entryid);
+
+gchar       *e2k_entryid_to_permanenturl (GByteArray *entryid,
+					 const gchar *base_uri);
+GByteArray *e2k_permanenturl_to_entryid (const gchar *permanenturl);
+
+gint  e2k_ascii_strcase_equal (gconstpointer v,
+			       gconstpointer v2);
+guint e2k_ascii_strcase_hash  (gconstpointer v);
+
+gboolean e2k_restriction_folders_only (E2kRestriction *rn);
+
+GByteArray *e2k_entryid_generate_oneoff  (const gchar *display_name,
+					  const gchar *email,
+					  gboolean    unicode);
+GByteArray *e2k_entryid_generate_local   (const gchar *exchange_dn);
+GByteArray *e2k_entryid_generate_contact (GByteArray *contact_entryid,
+					  gint         nth_address);
+GByteArray *e2k_search_key_generate      (const gchar *addrtype,
+					  const gchar *address);
+
+#endif /* E2K_UTILS_H */
diff --git a/server/lib/e2k-validate.h b/server/lib/e2k-validate.h
new file mode 100644
index 0000000..e0b5c7d
--- /dev/null
+++ b/server/lib/e2k-validate.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2003, 2004 Novell, Inc. */
+
+#ifndef __E2K_VALIDATE_H_
+#define __E2K_VALIDATE_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E2K_AUTOCONFIG_USE_GAL_DEFAULT, /* try ntlm if available and then basic if not or failed */
+
+	E2K_AUTOCONFIG_USE_GAL_BASIC,
+	/*E2K_AUTOCONFIG_USE_GAL_SASL,*/
+	E2K_AUTOCONFIG_USE_GAL_NTLM
+} E2kAutoconfigGalAuthPref;
+
+typedef struct {
+        gchar *host;
+        gchar *ad_server;
+	E2kAutoconfigGalAuthPref ad_auth;
+        gchar *mailbox;
+        gchar *owa_path;
+	gboolean is_ntlm;
+}ExchangeParams;
+
+typedef enum {
+	E2K_AUTOCONFIG_OK,
+	E2K_AUTOCONFIG_REDIRECT,
+	E2K_AUTOCONFIG_TRY_SSL,
+	E2K_AUTOCONFIG_AUTH_ERROR,
+	E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN,
+	E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC,
+	E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM,
+	E2K_AUTOCONFIG_EXCHANGE_5_5,
+	E2K_AUTOCONFIG_NOT_EXCHANGE,
+	E2K_AUTOCONFIG_NO_OWA,
+	E2K_AUTOCONFIG_NO_MAILBOX,
+	E2K_AUTOCONFIG_CANT_BPROPFIND,
+	E2K_AUTOCONFIG_CANT_RESOLVE,
+	E2K_AUTOCONFIG_CANT_CONNECT,
+	E2K_AUTOCONFIG_CANCELLED,
+	E2K_AUTOCONFIG_FAILED
+} E2kAutoconfigResult;
+
+gboolean e2k_validate_user (const gchar *owa_url, gchar *key, gchar **user,
+			    ExchangeParams *exchange_params,
+			    gboolean *remember_password,
+			    E2kAutoconfigResult *result,
+			    GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_VALIDATE_H_ */
diff --git a/server/lib/e2k-xml-utils.c b/server/lib/e2k-xml-utils.c
new file mode 100644
index 0000000..adfb86e
--- /dev/null
+++ b/server/lib/e2k-xml-utils.c
@@ -0,0 +1,255 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-xml-utils.h"
+#include <stdlib.h>
+#include <libxml/HTMLparser.h>
+#include <libxml/parserInternals.h>
+#include <libxml/xmlmemory.h>
+
+static void
+my_xml_parser_error_handler (gpointer ctx, const gchar *msg, ...)
+{
+	;
+}
+
+/**
+ * e2k_parse_xml:
+ * @buf: the data to parse
+ * @len: the length of the buffer, or -1 if it is '\0'-terminated
+ *
+ * Parses the XML document in @buf.
+ *
+ * Return value: a pointer to an #xmlDoc
+ **/
+xmlDoc *
+e2k_parse_xml (const gchar *buf, gint len)
+{
+	static xmlSAXHandler *sax;
+	xmlParserCtxtPtr ctxt;
+	xmlDoc *doc;
+
+	g_return_val_if_fail (buf != NULL, NULL);
+
+	if (!sax) {
+		xmlInitParser();
+		sax = xmlMalloc (sizeof (xmlSAXHandler));
+#if LIBXML_VERSION > 20600
+		xmlSAXVersion (sax, 2);
+#else
+		memcpy (sax, &xmlDefaultSAXHandler, sizeof (xmlSAXHandler));
+#endif
+		sax->warning = my_xml_parser_error_handler;
+		sax->error = my_xml_parser_error_handler;
+	}
+
+	if (len == -1)
+		len = strlen (buf);
+	ctxt = xmlCreateMemoryParserCtxt (buf, len);
+	if (!ctxt)
+		return NULL;
+
+	xmlFree (ctxt->sax);
+        ctxt->sax = sax;
+#if LIBXML_VERSION > 20600
+	ctxt->sax2 = 1;
+	ctxt->str_xml = xmlDictLookup (ctxt->dict, BAD_CAST "xml", 3);
+	ctxt->str_xmlns = xmlDictLookup (ctxt->dict, BAD_CAST "xmlns", 5);
+	ctxt->str_xml_ns = xmlDictLookup (ctxt->dict, XML_XML_NAMESPACE, 36);
+#endif
+
+	/* We set recover to TRUE because Exchange will let you
+	 * put control-characters into data, which will make the
+	 * XML be not well-formed.
+	 */
+	ctxt->recovery = TRUE;
+	ctxt->vctxt.error = my_xml_parser_error_handler;
+	ctxt->vctxt.warning = my_xml_parser_error_handler;
+
+	xmlParseDocument (ctxt);
+
+	doc = ctxt->myDoc;
+	ctxt->sax = NULL;
+	xmlFreeParserCtxt (ctxt);
+
+	return doc;
+}
+
+/**
+ * e2k_parse_html:
+ * @buf: the data to parse
+ * @len: the length of the buffer, or -1 if it is '\0'-terminated
+ *
+ * Parses the HTML document in @buf.
+ *
+ * Return value: a pointer to an #xmlDoc
+ **/
+xmlDoc *
+e2k_parse_html (const gchar *buf, gint len)
+{
+	xmlDoc *doc;
+#if LIBXML_VERSION > 20600
+	static xmlSAXHandler *sax;
+	htmlParserCtxtPtr ctxt;
+
+	g_return_val_if_fail (buf != NULL, NULL);
+
+	if (!sax) {
+		xmlInitParser();
+		sax = xmlMalloc (sizeof (htmlSAXHandler));
+		memcpy (sax, &htmlDefaultSAXHandler, sizeof (xmlSAXHandlerV1));
+		sax->warning = my_xml_parser_error_handler;
+		sax->error = my_xml_parser_error_handler;
+	}
+
+	if (len == -1)
+		len = strlen (buf);
+	ctxt = htmlCreateMemoryParserCtxt (buf, len);
+	if (!ctxt)
+		return NULL;
+
+        xmlFree (ctxt->sax);
+        ctxt->sax = sax;
+	ctxt->vctxt.error = my_xml_parser_error_handler;
+	ctxt->vctxt.warning = my_xml_parser_error_handler;
+
+	htmlParseDocument (ctxt);
+	doc = ctxt->myDoc;
+
+	ctxt->sax = NULL;
+	htmlFreeParserCtxt (ctxt);
+
+#else /* LIBXML_VERSION <= 20600 */
+	gchar *buf_copy = g_strndup (buf, len);
+
+	doc = htmlParseDoc (buf_copy, NULL);
+	g_free (buf_copy);
+#endif
+
+	return doc;
+}
+
+/**
+ * e2k_g_string_append_xml_escaped:
+ * @string: a %GString containing XML data
+ * @value: data to append to @string
+ *
+ * Appends @value to @string, escaping any characters that can't appear
+ * unencoded in XML text (eg, "<").
+ **/
+void
+e2k_g_string_append_xml_escaped (GString *string, const gchar *value)
+{
+	while (*value) {
+		switch (*value) {
+		case '<':
+			g_string_append (string, "&lt;");
+			break;
+		case '>':
+			g_string_append (string, "&gt;");
+			break;
+		case '&':
+			g_string_append (string, "&amp;");
+			break;
+		case '"':
+			g_string_append (string, "&quot;");
+			break;
+
+		default:
+			g_string_append_c (string, *value);
+			break;
+		}
+		value++;
+	}
+}
+
+/**
+ * e2k_xml_find:
+ * @node: a node of an xml document
+ * @name: the name of the element to find
+ *
+ * Starts or continues a pre-order depth-first search of an xml
+ * document for an element named @name. @node is used as the starting
+ * point of the search, but is not examined itself.
+ *
+ * To search the complete document, pass the root node of the document
+ * as @node on the first call, and then on each successive call,
+ * pass the previous match as @node.
+ *
+ * Return value: the first matching element after @node, or %NULL when
+ * there are no more matches.
+ **/
+xmlNode *
+e2k_xml_find (xmlNode *node, const gchar *name)
+{
+	return e2k_xml_find_in (node, NULL, name);
+}
+
+/**
+ * e2k_xml_find_in:
+ * @node: a node of an xml document
+ * @top: top of the search space
+ * @name: the name of the element to find
+ *
+ * Starts or continues a pre-order depth-first search of a subset of
+ * an xml document for an element named @name. @node is used as the
+ * starting point of the search, but is not examined itself. @top is
+ * the upper-most node that will be examined.
+ *
+ * To search the complete tree under a given node, pass that node as
+ * both @node and @top on the first call, and then on each successive
+ * call, pass the previous match as @node (with the original node
+ * still as @top).
+ *
+ * Return value: the first matching element after @node, or %NULL when
+ * there are no more matches.
+ **/
+xmlNode *
+e2k_xml_find_in (xmlNode *node, xmlNode *top, const gchar *name)
+{
+	g_return_val_if_fail (name != NULL, NULL);
+
+	while (node) {
+		/* If the current node has children, then the first
+		 * child is the next node to examine. If it doesn't
+		 * have children but does have a younger sibling, then
+		 * that sibling is next up. Otherwise, climb back up
+		 * the tree until we find a node that does have a
+		 * younger sibling.
+		 */
+		if (node->children)
+			node = node->children;
+		else {
+			while (node && !node->next && node != top)
+				node = node->parent;
+			if (!node || node == top)
+				return NULL;
+			node = node->next;
+		}
+
+		if (node->name && !strcmp ((gchar *) node->name, name))
+			return node;
+	}
+
+	return NULL;
+}
diff --git a/server/lib/e2k-xml-utils.h b/server/lib/e2k-xml-utils.h
new file mode 100644
index 0000000..854b439
--- /dev/null
+++ b/server/lib/e2k-xml-utils.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_XML_UTILS_H__
+#define __E2K_XML_UTILS_H__
+
+#include <string.h>
+
+#include "e2k-types.h"
+#include <libxml/parser.h>
+
+#define E2K_XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+
+xmlDoc *e2k_parse_xml  (const gchar *buf, gint len);
+xmlDoc *e2k_parse_html (const gchar *buf, gint len);
+
+#define E2K_IS_NODE(node, nspace, nname) \
+	(!xmlStrcmp ((node)->name, (xmlChar *) (nname)) && \
+	(node)->ns && !xmlStrcmp ((node)->ns->href, (xmlChar *) (nspace)))
+
+void  e2k_g_string_append_xml_escaped (GString *string, const gchar *value);
+
+xmlNode *e2k_xml_find    (xmlNode *node, const gchar *name);
+xmlNode *e2k_xml_find_in (xmlNode *node, xmlNode *top, const gchar *name);
+
+#endif
diff --git a/server/lib/ebrowse.c b/server/lib/ebrowse.c
new file mode 100644
index 0000000..7f14028
--- /dev/null
+++ b/server/lib/ebrowse.c
@@ -0,0 +1,713 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This  program is free  software; you  can redistribute  it and/or
+ * modify it under the terms of version 2  of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* WebDAV test program / utility */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <libsoup/soup-misc.h>
+
+#include "e2k-context.h"
+#include "e2k-restriction.h"
+#include "e2k-security-descriptor.h"
+#include "e2k-sid.h"
+#include "e2k-xml-utils.h"
+
+#include "e2k-propnames.h"
+#include "e2k-propnames.c"
+
+#include "test-utils.h"
+
+static E2kContext *ctx;
+static E2kOperation op;
+
+static const gchar *folder_tree_props[] = {
+	E2K_PR_DAV_DISPLAY_NAME,
+	E2K_PR_EXCHANGE_FOLDER_CLASS
+};
+static const gint n_folder_tree_props = sizeof (folder_tree_props) / sizeof (folder_tree_props[0]);
+
+static void
+display_folder_tree (E2kContext *ctx, gchar *top)
+{
+	E2kRestriction *rn;
+	E2kResultIter *iter;
+	E2kResult *result;
+	gint status;
+	const gchar *name, *class;
+
+	e2k_operation_init (&op);
+	rn = e2k_restriction_prop_bool (E2K_PR_DAV_IS_COLLECTION,
+					E2K_RELOP_EQ, TRUE);
+	iter = e2k_context_search_start (ctx, &op, top,
+					 folder_tree_props,
+					 n_folder_tree_props,
+					 rn, NULL, TRUE);
+	e2k_restriction_unref (rn);
+
+	while ((result = e2k_result_iter_next (iter))) {
+		name = e2k_properties_get_prop (result->props,
+						E2K_PR_DAV_DISPLAY_NAME);
+		class = e2k_properties_get_prop (result->props,
+						 E2K_PR_EXCHANGE_FOLDER_CLASS);
+
+		printf ("%s:\n    %s, %s\n", result->href,
+			name, class ? class : "(No Outlook folder class)");
+	}
+	status = e2k_result_iter_free (iter);
+	e2k_operation_free (&op);
+
+	test_abort_if_http_error (status);
+	test_quit ();
+}
+
+static void
+list_contents (E2kContext *ctx, gchar *top, gboolean reverse)
+{
+	E2kRestriction *rn;
+	E2kResultIter *iter;
+	E2kResult *result;
+	const gchar *prop;
+	gint status;
+
+	e2k_operation_init (&op);
+	prop = E2K_PR_DAV_DISPLAY_NAME;
+	rn = e2k_restriction_prop_bool (E2K_PR_DAV_IS_COLLECTION,
+					E2K_RELOP_EQ, FALSE);
+	iter = e2k_context_search_start (ctx, &op, top, &prop, 1,
+					 rn, NULL, !reverse);
+	e2k_restriction_unref (rn);
+
+	while ((result = e2k_result_iter_next (iter))) {
+		printf ("%3d %s (%s)\n", e2k_result_iter_get_index (iter),
+			result->href,
+			(gchar *)e2k_properties_get_prop (result->props,
+							 E2K_PR_DAV_DISPLAY_NAME));
+	}
+	status = e2k_result_iter_free (iter);
+	e2k_operation_free (&op);
+
+	test_abort_if_http_error (status);
+	test_quit ();
+}
+
+static gint
+mp_compar (gconstpointer k, gconstpointer m)
+{
+	const gchar *key = k;
+	struct mapi_proptag *mp = (gpointer)m;
+
+	return strncmp (key, mp->proptag, 5);
+}
+
+static void
+print_propname (const gchar *propname)
+{
+	struct mapi_proptag *mp;
+
+	printf ("  %s", propname);
+
+	if (!strncmp (propname, E2K_NS_MAPI_PROPTAG, sizeof (E2K_NS_MAPI_PROPTAG) - 1)) {
+		mp = bsearch (propname + 42, mapi_proptags, nmapi_proptags,
+			      sizeof (struct mapi_proptag), mp_compar);
+		if (mp)
+			printf (" (%s)", mp->name);
+	}
+
+	printf (":\n");
+}
+
+static void
+print_binary (GByteArray *data)
+{
+	guchar *start, *end, *p;
+
+	end = data->data + data->len;
+	for (start = data->data; start < end; start += 16) {
+		printf ("    ");
+		for (p = start; p < end && p < start + 16; p++)
+			printf ("%02x ", *p);
+		while (p++ < start + 16)
+			printf ("   ");
+		printf ("   ");
+		for (p = start; p < end && p < start + 16; p++)
+			printf ("%c", isprint (*p) ? *p : '.');
+		printf ("\n");
+	}
+}
+
+typedef struct {
+	const gchar *propname;
+	E2kPropType type;
+	gpointer value;
+} EBrowseProp;
+
+static gint
+prop_compar (gconstpointer a, gconstpointer b)
+{
+	EBrowseProp **pa = (gpointer)a;
+	EBrowseProp **pb = (gpointer)b;
+
+	return strcmp ((*pa)->propname, (*pb)->propname);
+}
+
+static void
+print_prop (EBrowseProp *prop)
+{
+	print_propname (prop->propname);
+
+	switch (prop->type) {
+	case E2K_PROP_TYPE_BINARY:
+		print_binary (prop->value);
+		break;
+
+	case E2K_PROP_TYPE_STRING_ARRAY:
+	case E2K_PROP_TYPE_INT_ARRAY:
+	{
+		GPtrArray *array = prop->value;
+		gint i;
+
+		for (i = 0; i < array->len; i++)
+			printf ("    %s\n", (gchar *)array->pdata[i]);
+		break;
+	}
+
+	case E2K_PROP_TYPE_BINARY_ARRAY:
+	{
+		GPtrArray *array = prop->value;
+		gint i;
+
+		for (i = 0; i < array->len; i++) {
+			print_binary (array->pdata[i]);
+			printf ("\n");
+		}
+		break;
+	}
+
+	case E2K_PROP_TYPE_XML:
+		printf ("    (xml)\n");
+		break;
+
+	case E2K_PROP_TYPE_STRING:
+	default:
+		printf ("    %s\n", (gchar *)prop->value);
+		break;
+	}
+}
+
+static void
+add_prop (const gchar *propname, E2kPropType type, gpointer value, gpointer props)
+{
+	EBrowseProp *prop;
+
+	prop = g_new0 (EBrowseProp, 1);
+	prop->propname = propname;
+	prop->type = type;
+	prop->value = value;
+	g_ptr_array_add (props, prop);
+}
+
+static void
+print_properties (E2kResult *results, gint nresults)
+{
+	GPtrArray *props;
+	gint i;
+
+	if (nresults != 1) {
+		printf ("Got %d results?\n", nresults);
+		test_quit ();
+		return;
+	}
+
+	printf ("%s\n", results[0].href);
+	props = g_ptr_array_new ();
+	e2k_properties_foreach (results[0].props, add_prop, props);
+	qsort (props->pdata, props->len, sizeof (gpointer), prop_compar);
+
+	for (i = 0; i < props->len; i++)
+		print_prop (props->pdata[i]);
+
+	test_quit ();
+}
+
+static void
+got_all_properties (SoupMessage *msg, gpointer ctx)
+{
+	E2kResult *results;
+	gint nresults;
+
+	test_abort_if_http_error (msg->status_code);
+
+	e2k_results_from_multistatus (msg, &results, &nresults);
+	test_abort_if_http_error (msg->status_code);
+	print_properties (results, nresults);
+	e2k_results_free (results, nresults);
+}
+
+#define ALL_PROPS \
+"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" \
+"<propfind xmlns=\"DAV:\" xmlns:e=\"http://schemas.microsoft.com/exchange/\";>" \
+"  <allprop>" \
+"    <e:allprop/>" \
+"  </allprop>" \
+"</propfind>"
+
+static void
+get_all_properties (E2kContext *ctx, gchar *uri)
+{
+	SoupMessage *msg;
+
+	msg = e2k_soup_message_new_full (ctx, uri, "PROPFIND",
+					 "text/xml", SOUP_BUFFER_USER_OWNED,
+					 ALL_PROPS, strlen (ALL_PROPS));
+	soup_message_add_header (msg->request_headers, "Brief", "t");
+	soup_message_add_header (msg->request_headers, "Depth", "0");
+
+	e2k_context_queue_message (ctx, msg, got_all_properties, ctx);
+}
+
+static void
+get_property (E2kContext *ctx, gchar *uri, gchar *prop)
+{
+	E2kHTTPStatus status;
+	E2kResult *results;
+	gint nresults, i;
+
+	if (!strncmp (prop, "PR_", 3)) {
+		for (i = 0; i < nmapi_proptags; i++)
+			if (!strcmp (mapi_proptags[i].name, prop)) {
+				prop = g_strconcat (E2K_NS_MAPI_PROPTAG,
+						    mapi_proptags[i].proptag,
+						    NULL);
+				break;
+			}
+	}
+
+	e2k_operation_init (&op);
+	status = e2k_context_propfind (ctx, &op, uri,
+				       (const gchar **)&prop, 1,
+				       &results, &nresults);
+	e2k_operation_free (&op);
+	test_abort_if_http_error (status);
+	print_properties (results, nresults);
+	e2k_results_free (results, nresults);
+}
+
+static void
+get_fav_properties(E2kContext *ctx, gchar *uri)
+{
+	E2kRestriction *rn;
+	E2kResultIter *iter;
+	E2kResult *result;
+	const gchar *prop;
+	gint status;
+	gchar *eml_str, *top = uri, fav_uri[1024];
+
+	/* list the contents and search for the favorite properties */
+	e2k_operation_init (&op);
+	prop = E2K_PR_DAV_DISPLAY_NAME;
+	rn = e2k_restriction_prop_bool (E2K_PR_DAV_IS_COLLECTION,
+					E2K_RELOP_EQ, FALSE);
+	iter = e2k_context_search_start (ctx, &op, top, &prop, 1,
+                                         rn, NULL, FALSE);
+	e2k_restriction_unref (rn);
+
+	while ((result = e2k_result_iter_next (iter))) {
+		strcpy(fav_uri, uri);
+		eml_str = strstr(result->href, "Shortcuts");
+		eml_str = eml_str + strlen("Shortcuts");
+
+		strcat(fav_uri, eml_str);
+
+		printf("\nNAME:\n");
+		get_property (ctx, fav_uri, PR_FAV_DISPLAY_NAME);
+		printf("\nALIAS:\n");
+		get_property (ctx, fav_uri, PR_FAV_DISPLAY_ALIAS);
+		printf("\nPUBLIC SOURCE KEY:\n");
+		get_property (ctx, fav_uri, PR_FAV_PUBLIC_SOURCE_KEY);
+		printf("\nPARENT SOURCE KEY:\n");
+		get_property (ctx, fav_uri, PR_FAV_PARENT_SOURCE_KEY);
+		printf("\nAUTO SUBFOLDERS:\n");
+		get_property (ctx, fav_uri, PR_FAV_AUTOSUBFOLDERS);
+		printf("\nLEVEL MASK:\n");
+		get_property (ctx, fav_uri, PR_FAV_LEVEL_MASK);
+		printf("\nINHERIT AUTO:\n");
+		get_property (ctx, fav_uri, PR_FAV_INHERIT_AUTO);
+		printf("\nDEL SUBS:\n");
+		get_property (ctx, fav_uri, PR_FAV_DEL_SUBS);
+		printf("\n\t\t=================================================\n");
+
+		memset(fav_uri, 0, 1024);
+	}
+	status = e2k_result_iter_free (iter);
+	e2k_operation_free (&op);
+
+	test_abort_if_http_error (status);
+	test_quit ();
+}
+
+static void
+get_sd (E2kContext *ctx, gchar *uri)
+{
+	const gchar *props[] = {
+		E2K_PR_EXCHANGE_SD_BINARY,
+		E2K_PR_EXCHANGE_SD_XML,
+	};
+	E2kHTTPStatus status;
+	E2kResult *results;
+	gint nresults;
+	xmlNodePtr xml_form;
+	GByteArray *binary_form;
+	E2kSecurityDescriptor *sd;
+	E2kPermissionsRole role;
+	guint32 perms;
+	GList *sids, *s;
+	E2kSid *sid;
+
+	e2k_operation_init (&op);
+	status = e2k_context_propfind (ctx, &op, uri, props, 2,
+				       &results, &nresults);
+	e2k_operation_free (&op);
+	test_abort_if_http_error (status);
+
+	if (nresults == 0)
+		goto done;
+
+	xml_form = e2k_properties_get_prop (results[0].props,
+					    E2K_PR_EXCHANGE_SD_XML);
+	binary_form = e2k_properties_get_prop (results[0].props,
+					       E2K_PR_EXCHANGE_SD_BINARY);
+	if (!xml_form || !binary_form)
+		goto done;
+
+	xmlElemDump (stdout, NULL, xml_form);
+	printf ("\n");
+
+	print_binary (binary_form);
+	printf ("\n");
+
+	sd = e2k_security_descriptor_new (xml_form, binary_form);
+	if (!sd) {
+		printf ("(Could not parse)\n");
+		goto done;
+	}
+
+	sids = e2k_security_descriptor_get_sids (sd);
+	for (s = sids; s; s = s->next) {
+		sid = s->data;
+		perms = e2k_security_descriptor_get_permissions (sd, sid);
+		role = e2k_permissions_role_find (perms);
+		printf ("%s: %s (0x%lx)\n",
+			e2k_sid_get_display_name (sid),
+			e2k_permissions_role_get_name (role),
+			(gulong)perms);
+	}
+	g_list_free (sids);
+
+	if (!e2k_security_descriptor_to_binary (sd))
+		printf ("\nSD is malformed.\n");
+	g_object_unref (sd);
+
+ done:
+	test_quit ();
+}
+
+static void
+get_body (E2kContext *ctx, gchar *uri)
+{
+	E2kHTTPStatus status;
+	gchar *body;
+	gint len;
+
+	e2k_operation_init (&op);
+	status = e2k_context_get (ctx, &op, uri, NULL, &body, &len);
+	e2k_operation_free (&op);
+	test_abort_if_http_error (status);
+
+	fwrite (body, 1, len, stdout);
+	test_quit ();
+}
+
+static void
+delete (E2kContext *ctx, gchar *uri)
+{
+	E2kHTTPStatus status;
+
+	e2k_operation_init (&op);
+	status = e2k_context_delete (ctx, &op, uri);
+	e2k_operation_free (&op);
+	test_abort_if_http_error (status);
+	test_quit ();
+}
+
+static void
+notify (E2kContext *ctx, const gchar *uri,
+	E2kContextChangeType type, gpointer user_data)
+{
+	switch (type) {
+	case E2K_CONTEXT_OBJECT_CHANGED:
+		printf ("Changed\n");
+		break;
+	case E2K_CONTEXT_OBJECT_ADDED:
+		printf ("Added\n");
+		break;
+	case E2K_CONTEXT_OBJECT_REMOVED:
+		printf ("Removed\n");
+		break;
+	case E2K_CONTEXT_OBJECT_MOVED:
+		printf ("Moved\n");
+		break;
+	}
+}
+
+static void
+subscribe (E2kContext *ctx, gchar *uri)
+{
+	e2k_context_subscribe (ctx, uri,
+			       E2K_CONTEXT_OBJECT_CHANGED, 0,
+			       notify, NULL);
+	e2k_context_subscribe (ctx, uri,
+			       E2K_CONTEXT_OBJECT_ADDED, 0,
+			       notify, NULL);
+	e2k_context_subscribe (ctx, uri,
+			       E2K_CONTEXT_OBJECT_REMOVED, 0,
+			       notify, NULL);
+	e2k_context_subscribe (ctx, uri,
+			       E2K_CONTEXT_OBJECT_MOVED, 0,
+			       notify, NULL);
+}
+
+static void
+move (E2kContext *ctx, gchar *from, gchar *to, gboolean delete)
+{
+	GPtrArray *source_hrefs;
+	E2kResultIter *iter;
+	E2kResult *result;
+	E2kHTTPStatus status;
+
+	source_hrefs = g_ptr_array_new ();
+	g_ptr_array_add (source_hrefs, "");
+
+	e2k_operation_init (&op);
+	iter = e2k_context_transfer_start (ctx, &op, from, to,
+					   source_hrefs, delete);
+	g_ptr_array_free (source_hrefs, TRUE);
+
+	result = e2k_result_iter_next (iter);
+	if (result) {
+		if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (result->status))
+			printf ("Failed: %d\n", result->status);
+		else {
+			printf ("moved to %s\n",
+				(gchar *)e2k_properties_get_prop (result->props,
+								 E2K_PR_DAV_LOCATION));
+		}
+	}
+	status = e2k_result_iter_free (iter);
+	e2k_operation_free (&op);
+
+	test_abort_if_http_error (status);
+	test_quit ();
+}
+
+static void
+name (E2kContext *ctx, gchar *alias, gchar *uri_prefix)
+{
+	E2kHTTPStatus status;
+	gchar *uri, *body;
+	gint len;
+	xmlDoc *doc;
+	xmlNode *item, *node;
+	gchar *data;
+
+	uri = g_strdup_printf ("%s?Cmd=galfind&AN=%s", uri_prefix, alias);
+	e2k_operation_init (&op);
+	status = e2k_context_get_owa (ctx, &op, uri, TRUE, &body, &len);
+	e2k_operation_free (&op);
+	test_abort_if_http_error (status);
+
+	doc = e2k_parse_xml (body, len);
+
+	if ((node = e2k_xml_find (doc->children, "error")))
+		printf ("Error: %s\n", xmlNodeGetContent (node));
+	else {
+		item = doc->children;
+		while ((item = e2k_xml_find (item, "item"))) {
+			for (node = item->children; node; node = node->next) {
+				if (node->type == XML_ELEMENT_NODE) {
+					data = xmlNodeGetContent (node);
+					if (data && *data)
+						printf ("%s: %s\n", node->name, data);
+					xmlFree (data);
+				}
+			}
+		}
+	}
+
+	xmlFreeDoc (doc);
+	test_quit ();
+}
+
+static void
+put (E2kContext *ctx, const gchar *file, const gchar *uri)
+{
+	struct stat st;
+	gchar *buf;
+	gint fd;
+	E2kHTTPStatus status;
+
+	fd = open (file, O_RDONLY);
+	if (fd == -1 || fstat (fd, &st) == -1) {
+		fprintf (stderr, "%s\n", g_strerror (errno));
+		exit (1);
+	}
+	buf = g_malloc (st.st_size);
+	read (fd, buf, st.st_size);
+	close (fd);
+
+	e2k_operation_init (&op);
+	status = e2k_context_put (ctx, &op, uri,
+				  "message/rfc822", buf, st.st_size,
+				  NULL);
+	e2k_operation_free (&op);
+	test_abort_if_http_error (status);
+	test_quit ();
+}
+
+static gpointer
+cancel (gpointer op)
+{
+	e2k_operation_cancel (op);
+	return NULL;
+}
+
+static void
+quit (gint sig)
+{
+	static pthread_t cancel_thread;
+
+	/* Can't cancel from here because we might be
+	 * inside a malloc.
+	 */
+	if (!cancel_thread) {
+		pthread_create (&cancel_thread, NULL, cancel, &op);
+	} else
+		exit (0);
+}
+
+static void
+usage (void)
+{
+	printf ("usage: ebrowse -t URI                       (shallow folder tree)\n");
+	printf ("       ebrowse [-l | -L ] URI               (contents listing [back/forward])\n");
+	printf ("       ebrowse [ -p | -P prop ] URI         (look up all/one prop)\n");
+	printf ("       ebrowse -S URI                       (look up security descriptor)\n");
+	printf ("       ebrowse -b URI                       (fetch body)\n");
+	printf ("       ebrowse -q FILE URI                  (put body)\n");
+	printf ("       ebrowse -d URI                       (delete)\n");
+	printf ("       ebrowse -s URI                       (subscribe and listen)\n");
+	printf ("       ebrowse [ -m | -c ] SRC DEST         (move/copy)\n");
+	printf ("       ebrowse -n ALIAS URI                 (lookup name)\n");
+	printf ("       ebrowse -f URI			     (lookup favorite folder props)\n");
+	exit (1);
+}
+
+const gchar *test_program_name = "ebrowse";
+
+void
+test_main (gint argc, gchar **argv)
+{
+	gchar *uri;
+
+	signal (SIGINT, quit);
+
+	uri = argv[argc - 1];
+	ctx = test_get_context (uri);
+
+	switch (argv[1][1]) {
+	case 't':
+		display_folder_tree (ctx, uri);
+		break;
+
+	case 'l':
+		list_contents (ctx, uri, FALSE);
+		break;
+
+	case 'L':
+		list_contents (ctx, uri, TRUE);
+		break;
+
+	case 'b':
+		get_body (ctx, uri);
+		break;
+
+	case 'd':
+		delete (ctx, uri);
+		break;
+
+	case 'p':
+		get_all_properties (ctx, uri);
+		break;
+
+	case 'P':
+		get_property (ctx, uri, argv[2]);
+		break;
+
+	case 'S':
+		get_sd (ctx, uri);
+		break;
+
+	case 's':
+		subscribe (ctx, uri);
+		break;
+
+	case 'm':
+	case 'c':
+		move (ctx, argv[2], uri, argv[1][1] == 'm');
+		break;
+
+	case 'n':
+		name (ctx, argv[2], uri);
+		break;
+
+	case 'q':
+		put (ctx, argv[2], uri);
+		break;
+
+	case 'f':
+		get_fav_properties(ctx, uri);
+		break;
+
+	default:
+		usage ();
+	}
+}
diff --git a/server/lib/fbtest.c b/server/lib/fbtest.c
new file mode 100644
index 0000000..1adcebb
--- /dev/null
+++ b/server/lib/fbtest.c
@@ -0,0 +1,146 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This  program is free  software; you  can redistribute  it and/or
+ * modify it under the terms of version 2  of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* Free/Busy test program. Note though that this uses the code in
+ * e2k-freebusy.c, which is not currently used by Connector itself.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "e2k-freebusy.h"
+#include "e2k-global-catalog.h"
+#include "test-utils.h"
+
+const gchar *test_program_name = "fbtest";
+
+void
+test_main (gint argc, gchar **argv)
+{
+	E2kGlobalCatalog *gc;
+	E2kGlobalCatalogStatus status;
+	E2kGlobalCatalogEntry *entry;
+	const gchar *server, *email;
+	E2kContext *ctx;
+	E2kFreebusy *fb;
+	E2kFreebusyEvent event;
+	gint ti, bi, oi;
+	gchar *public_uri;
+	struct tm tm;
+	time_t t;
+
+	if (argc != 3) {
+		fprintf (stderr, "Usage: %s server email-addr\n", argv[0]);
+		exit (1);
+	}
+
+	server = argv[1];
+	email = argv[2];
+
+	gc = test_get_gc (server);
+
+	status = e2k_global_catalog_lookup (
+		gc, NULL, E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL,
+		email, E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN,
+		&entry);
+
+	if (status != E2K_GLOBAL_CATALOG_OK) {
+		fprintf (stderr, "Lookup failed: %d\n", status);
+		test_quit ();
+		return;
+	}
+
+	public_uri = g_strdup_printf ("http://%s/public";, server);
+	ctx = test_get_context (public_uri);
+	fb = e2k_freebusy_new (ctx, public_uri, entry->legacy_exchange_dn);
+	g_free (public_uri);
+	g_object_unref (ctx);
+
+	if (!fb) {
+		fprintf (stderr, "Could not get fb props\n");
+		test_quit ();
+		return;
+	}
+
+	if (!fb->events[E2K_BUSYSTATUS_ALL]->len) {
+		printf ("No data\n");
+		test_quit ();
+		return;
+	}
+
+	printf ("                         6am      9am      noon     3pm      6pm\n");
+
+	ti = bi = oi = 0;
+	for (t = fb->start; t < fb->end; t += 30 * 60) {
+		if ((t - fb->start) % (24 * 60 * 60) == 0) {
+			tm = *localtime (&t);
+			printf ("\n%02d-%02d: ", tm.tm_mon + 1, tm.tm_mday);
+		}
+
+		for (; oi < fb->events[E2K_BUSYSTATUS_OOF]->len; oi++) {
+			event = g_array_index (fb->events[E2K_BUSYSTATUS_OOF],
+					       E2kFreebusyEvent, oi);
+			if (event.end <= t)
+				continue;
+			if (event.start < t + (30 * 60)) {
+				printf ("O");
+				goto next;
+			}
+			if (event.start > t)
+				break;
+		}
+		for (; bi < fb->events[E2K_BUSYSTATUS_BUSY]->len; bi++) {
+			event = g_array_index (fb->events[E2K_BUSYSTATUS_BUSY],
+					       E2kFreebusyEvent, bi);
+			if (event.end <= t)
+				continue;
+			if (event.start < t + (30 * 60)) {
+				printf ("X");
+				goto next;
+			}
+			if (event.start > t)
+				break;
+		}
+		for (; ti < fb->events[E2K_BUSYSTATUS_TENTATIVE]->len; ti++) {
+			event = g_array_index (fb->events[E2K_BUSYSTATUS_TENTATIVE],
+					       E2kFreebusyEvent, ti);
+			if (event.end <= t)
+				continue;
+			if (event.start < t + (30 * 60)) {
+				printf ("t");
+				goto next;
+			}
+			if (event.start > t)
+				break;
+		}
+		printf (".");
+
+	next:
+		if ((t - fb->start) % (60 * 60))
+			printf (" ");
+	}
+	printf ("\n");
+
+	test_quit ();
+}
diff --git a/server/lib/gctest.c b/server/lib/gctest.c
new file mode 100644
index 0000000..c3c80df
--- /dev/null
+++ b/server/lib/gctest.c
@@ -0,0 +1,250 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This  program is free  software; you  can redistribute  it and/or
+ * modify it under the terms of version 2  of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* Global Catalog test program */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "e2k-global-catalog.h"
+#include "e2k-sid.h"
+#include "test-utils.h"
+
+E2kGlobalCatalog *gc;
+E2kOperation op;
+
+static void
+do_lookup (E2kGlobalCatalog *gc, const gchar *user)
+{
+	E2kGlobalCatalogStatus status;
+	E2kGlobalCatalogEntry *entry;
+	E2kGlobalCatalogLookupType type;
+	guint32 flags;
+	gint i, pwd_exp_days;
+	gdouble maxAge;
+
+	if (*user == '/')
+		type = E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN;
+	else if (strchr (user, '@'))
+		type = E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL;
+	else
+		type = E2K_GLOBAL_CATALOG_LOOKUP_BY_DN;
+
+	flags =	E2K_GLOBAL_CATALOG_LOOKUP_SID |
+		E2K_GLOBAL_CATALOG_LOOKUP_EMAIL |
+		E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX |
+		E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN |
+		E2K_GLOBAL_CATALOG_LOOKUP_DELEGATES |
+		E2K_GLOBAL_CATALOG_LOOKUP_DELEGATORS |
+		E2K_GLOBAL_CATALOG_LOOKUP_QUOTA |
+		E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL;
+
+	e2k_operation_init (&op);
+	status = e2k_global_catalog_lookup (gc, &op, type, user, flags, &entry);
+	// e2k_operation_free (&op);
+
+	switch (status) {
+	case E2K_GLOBAL_CATALOG_OK:
+		break;
+	case E2K_GLOBAL_CATALOG_NO_SUCH_USER:
+		printf ("No entry for %s\n", user);
+		test_quit ();
+		return;
+	case E2K_GLOBAL_CATALOG_NO_DATA:
+		printf ("Entry for %s contains no data\n", user);
+		test_quit ();
+		return;
+	case E2K_GLOBAL_CATALOG_AUTH_FAILED:
+		printf ("Authentication failed (try DOMAIN\\username)\n");
+		test_quit ();
+		return;
+	default:
+		printf ("Error looking up user\n");
+		test_quit ();
+		return;
+	}
+
+	printf ("%s (%s)\n", entry->display_name, entry->dn);
+	if (entry->email)
+		printf ("  email: %s\n", entry->email);
+	if (entry->mailbox)
+		printf ("  mailbox: %s on %s\n", entry->mailbox, entry->exchange_server);
+	if (entry->legacy_exchange_dn)
+		printf ("  Exchange 5.5 DN: %s\n", entry->legacy_exchange_dn);
+	if (entry->sid)
+		printf ("  sid: %s\n", e2k_sid_get_string_sid (entry->sid));
+	if (entry->delegates) {
+		printf ("  delegates:\n");
+		for (i = 0; i < entry->delegates->len; i++)
+			printf ("    %s\n", (gchar *)entry->delegates->pdata[i]);
+	}
+	if (entry->delegators) {
+		printf ("  delegators:\n");
+		for (i = 0; i < entry->delegators->len; i++)
+			printf ("    %s\n", (gchar *)entry->delegators->pdata[i]);
+	}
+
+	if (entry->quota_warn || entry->quota_nosend || entry->quota_norecv )
+		printf ("  Mail Quota Info:\n");
+	if (entry->quota_warn)
+		printf ("    Issue Quota warning at : %d\n", entry->quota_warn);
+	if (entry->quota_nosend)
+		printf ("    Stop sending mails at  : %d\n", entry->quota_nosend);
+	if (entry->quota_norecv)
+		printf ("    Stop sending and recieving mails at : %d\n", entry->quota_norecv);
+	if (entry->user_account_control)
+		printf ("    user_account_control : %d\n", entry->user_account_control);
+
+	maxAge = lookup_passwd_max_age (gc, &op);
+	printf("Password max age is %f \n", maxAge);
+	pwd_exp_days = (maxAge * 0.000000100)/86400;
+	printf("Password expiery period is %d \n", pwd_exp_days);
+
+	e2k_operation_free (&op);
+
+	e2k_global_catalog_entry_free (gc, entry);
+	test_quit ();
+}
+
+static gchar *
+lookup_dn (E2kGlobalCatalog *gc, const gchar *id)
+{
+	E2kGlobalCatalogEntry *entry;
+	E2kGlobalCatalogLookupType type;
+	E2kGlobalCatalogStatus status;
+	gchar *dn;
+
+	if (id[0] == '/')
+		type = E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN;
+	else if (strchr (id, '@'))
+		type = E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL;
+	else
+		return g_strdup (id);
+
+	e2k_operation_init (&op);
+	status = e2k_global_catalog_lookup (gc, &op, type, id, 0, &entry);
+	e2k_operation_free (&op);
+
+	switch (status) {
+	case E2K_GLOBAL_CATALOG_OK:
+		break;
+	case E2K_GLOBAL_CATALOG_NO_SUCH_USER:
+		printf ("No entry for %s\n", id);
+		exit (1);
+		break;
+	default:
+		printf ("Error looking up user %s\n", id);
+		exit (1);
+		break;
+	}
+
+	dn = g_strdup (entry->dn);
+	e2k_global_catalog_entry_free (gc, entry);
+
+	return dn;
+}
+
+static void
+do_modify (E2kGlobalCatalog *gc, const gchar *user,
+	   gint deleg_op, const gchar *delegate)
+{
+	gchar *self_dn, *deleg_dn;
+	E2kGlobalCatalogStatus status;
+
+	self_dn = lookup_dn (gc, user);
+	deleg_dn = lookup_dn (gc, delegate);
+
+	e2k_operation_init (&op);
+	if (deleg_op == '+')
+		status = e2k_global_catalog_add_delegate (gc, &op, self_dn, deleg_dn);
+	else
+		status = e2k_global_catalog_remove_delegate (gc, &op, self_dn, deleg_dn);
+	e2k_operation_free (&op);
+
+	switch (status) {
+	case E2K_GLOBAL_CATALOG_OK:
+		printf ("Done\n");
+		break;
+	case E2K_GLOBAL_CATALOG_BAD_DATA:
+		printf ("Invalid delegate DN\n");
+		break;
+	case E2K_GLOBAL_CATALOG_NO_DATA:
+		printf ("No such delegate to remove\n");
+		break;
+	case E2K_GLOBAL_CATALOG_EXISTS:
+		printf ("That user is already a delegate\n");
+		break;
+	default:
+		printf ("Failed\n");
+		break;
+	}
+
+	test_quit ();
+}
+
+static gpointer
+cancel (gpointer data)
+{
+	e2k_operation_cancel (&op);
+	return NULL;
+}
+
+static void
+quit (gint sig)
+{
+	static pthread_t cancel_thread;
+
+	if (!cancel_thread) {
+		pthread_create (&cancel_thread, NULL, cancel, NULL);
+	} else
+		exit (0);
+}
+
+const gchar *test_program_name = "gctest";
+
+void
+test_main (gint argc, gchar **argv)
+{
+	const gchar *server;
+
+	if (argc < 3 || argc > 4 ||
+	    (argc == 4 && argv[3][0] != '+' && argv[3][0] != '-')) {
+		fprintf (stderr, "Usage: %s server email-or-dn [[+|-]delegate]\n", argv[0]);
+		exit (1);
+	}
+
+	signal (SIGINT, quit);
+
+	server = argv[1];
+	gc = test_get_gc (server);
+
+	if (argc == 3)
+		do_lookup (gc, argv[2]);
+	else
+		do_modify (gc, argv[2], argv[3][0], argv[3] + 1);
+
+	g_object_unref (gc);
+}
diff --git a/server/lib/mapi-properties b/server/lib/mapi-properties
new file mode 100644
index 0000000..45a8cf9
--- /dev/null
+++ b/server/lib/mapi-properties
@@ -0,0 +1,1549 @@
+http://schemas.microsoft.com/mapi/proptag/
+
+The second half of each property name indicates the type:
+0002  PT_SHORT         signed 16-bit integer
+0003  PT_LONG          signed 32-bit integer
+000a  PT_ERROR         invalid type (?)
+000b  PT_BOOLEAN       boolean
+000d  PT_OBJECT        embedded object
+0014  PT_LONGLONG      signed 64-bit integer
+001e  PT_STRING8       Locale-encoded string
+001f  PT_UNICODE       UTF8-encoded string
+0040  PT_SYSTIME       64bit time value
+0048  PT_CLSID         GUID
+00fd  PT_SRESTRICTION  rule condition
+00fe  PT_ACTIONS       rule actions
+0102  PT_BINARY        binary blob
+1xxx  PT_MV_xxx        multivalued
+
+In general, you can request either the 001e or 001f variant of a
+string.
+
+Not all properties are available via WebDAV. (In particular, none with
+type 000d are.)
+
+(The part below this line is preprocessed into lib/mapi-properties.[ch])
+
+x00010003  PR_ACKNOWLEDGEMENT_MODE
+x0002000b  PR_ALTERNATE_RECIPIENT_ALLOWED
+x00030102  PR_AUTHORIZING_USERS
+x0004001f  PR_AUTO_FORWARD_COMMENT
+x0005000b  PR_AUTO_FORWARDED
+x00060102  PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID
+x00070102  PR_CONTENT_CORRELATOR
+x0008001f  PR_CONTENT_IDENTIFIER
+x00090003  PR_CONTENT_LENGTH
+x000a000b  PR_CONTENT_RETURN_REQUESTED
+x000b0102  PR_CONVERSATION_KEY
+x000c0102  PR_CONVERSION_EITS
+x000d000b  PR_CONVERSION_WITH_LOSS_PROHIBITED
+x000e0102  PR_CONVERTED_EITS
+x000f0040  PR_DEFERRED_DELIVERY_TIME
+x00100040  PR_DELIVER_TIME
+x00110003  PR_DISCARD_REASON
+x0012000b  PR_DISCLOSURE_OF_RECIPIENTS
+x00130102  PR_DL_EXPANSION_HISTORY
+x0014000b  PR_DL_EXPANSION_PROHIBITED
+x00150040  PR_EXPIRY_TIME
+x0016000b  PR_IMPLICIT_CONVERSION_PROHIBITED
+x00170003  PR_IMPORTANCE
+x00180102  PR_IPM_ID
+x00190040  PR_LATEST_DELIVERY_TIME
+x001a001f  PR_MESSAGE_CLASS
+x001b0102  PR_MESSAGE_DELIVERY_ID
+x001e0102  PR_MESSAGE_SECURITY_LABEL
+x001f0102  PR_OBSOLETED_IPMS
+x00200102  PR_ORIGINALLY_INTENDED_RECIPIENT_NAME
+x00210102  PR_ORIGINAL_EITS
+x00220102  PR_ORIGINATOR_CERTIFICATE
+x0023000b  PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED
+x00240102  PR_ORIGINATOR_RETURN_ADDRESS
+x00250102  PR_PARENT_KEY
+x00260003  PR_PRIORITY
+x00270102  PR_ORIGIN_CHECK
+x0028000b  PR_PROOF_OF_SUBMISSION_REQUESTED
+x0029000b  PR_READ_RECEIPT_REQUESTED
+x002a0040  PR_RECEIPT_TIME
+x002b000b  PR_RECIPIENT_REASSIGNMENT_PROHIBITED
+x002c0102  PR_REDIRECTION_HISTORY
+x002d0102  PR_RELATED_IPMS
+x002e0003  PR_ORIGINAL_SENSITIVITY
+x002f001f  PR_LANGUAGES
+x00300040  PR_REPLY_TIME
+x00310102  PR_REPORT_TAG
+x00320040  PR_REPORT_TIME
+x0033000b  PR_RETURNED_IPM
+x00340003  PR_SECURITY
+x0035000b  PR_INCOMPLETE_COPY
+x00360003  PR_SENSITIVITY
+x0037001f  PR_SUBJECT
+x00380102  PR_SUBJECT_IPM
+x00390040  PR_CLIENT_SUBMIT_TIME
+x003a001f  PR_REPORT_NAME
+x003b0102  PR_SENT_REPRESENTING_SEARCH_KEY
+x003c0102  PR_X400_CONTENT_TYPE
+x003d001f  PR_SUBJECT_PREFIX
+x003e0003  PR_NON_RECEIPT_REASON
+x003f0102  PR_RECEIVED_BY_ENTRYID
+x0040001f  PR_RECEIVED_BY_NAME
+x00410102  PR_SENT_REPRESENTING_ENTRYID
+x0042001f  PR_SENT_REPRESENTING_NAME
+x00430102  PR_RCVD_REPRESENTING_ENTRYID
+x0044001f  PR_RCVD_REPRESENTING_NAME
+x00450102  PR_REPORT_ENTRYID
+x00460102  PR_READ_RECEIPT_ENTRYID
+x00470102  PR_MESSAGE_SUBMISSION_ID
+x00470102  PR_MTS_ID
+x00470102  PR_MTS_REPORT_ID
+x00480040  PR_PROVIDER_SUBMIT_TIME
+x0049001f  PR_ORIGINAL_SUBJECT
+x004a000b  PR_DISC_VAL
+x004b001f  PR_ORIG_MESSAGE_CLASS
+x004c0102  PR_ORIGINAL_AUTHOR_ENTRYID
+x004d001f  PR_ORIGINAL_AUTHOR_NAME
+x004e0040  PR_ORIGINAL_SUBMIT_TIME
+x004f0102  PR_REPLY_RECIPIENT_ENTRIES
+x0050001f  PR_REPLY_RECIPIENT_NAMES
+x00510102  PR_RECEIVED_BY_SEARCH_KEY
+x00520102  PR_RCVD_REPRESENTING_SEARCH_KEY
+x00530102  PR_READ_RECEIPT_SEARCH_KEY
+x00540102  PR_REPORT_SEARCH_KEY
+x00550040  PR_ORIGINAL_DELIVERY_TIME
+x00560102  PR_ORIGINAL_AUTHOR_SEARCH_KEY
+x0057000b  PR_MESSAGE_TO_ME
+x0058000b  PR_MESSAGE_CC_ME
+x0059000b  PR_MESSAGE_RECIP_ME
+x005a001f  PR_ORIGINAL_SENDER_NAME
+x005b0102  PR_ORIGINAL_SENDER_ENTRYID
+x005c0102  PR_ORIGINAL_SENDER_SEARCH_KEY
+x005d001f  PR_ORIGINAL_SENT_REPRESENTING_NAME
+x005e0102  PR_ORIGINAL_SENT_REPRESENTING_ENTRYID
+x005f0102  PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY
+x00600040  PR_START_DATE
+x00610040  PR_END_DATE
+x00620003  PR_OWNER_APPT_ID
+x0063000b  PR_RESPONSE_REQUESTED
+x0064001f  PR_SENT_REPRESENTING_ADDRTYPE
+x0065001f  PR_SENT_REPRESENTING_EMAIL_ADDRESS
+x0066001f  PR_ORIGINAL_SENDER_ADDRTYPE
+x0067001f  PR_ORIGINAL_SENDER_EMAIL_ADDRESS
+x0068001f  PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE
+x0069001f  PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS
+x0070001f  PR_CONVERSATION_TOPIC
+x00710102  PR_CONVERSATION_INDEX
+x0072001f  PR_ORIGINAL_DISPLAY_BCC
+x0073001f  PR_ORIGINAL_DISPLAY_CC
+x0074001f  PR_ORIGINAL_DISPLAY_TO
+x0075001f  PR_RECEIVED_BY_ADDRTYPE
+x0076001f  PR_RECEIVED_BY_EMAIL_ADDRESS
+x0077001f  PR_RCVD_REPRESENTING_ADDRTYPE
+x0078001f  PR_RCVD_REPRESENTING_EMAIL_ADDRESS
+x0079001f  PR_ORIGINAL_AUTHOR_ADDRTYPE
+x007a001f  PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS
+x007b001f  PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE
+x007c001f  PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS
+x007d001f  PR_TRANSPORT_MESSAGE_HEADERS
+x007e0102  PR_DELEGATION
+x007f0102  PR_TNEF_CORRELATION_KEY
+x0c000102  PR_CONTENT_INTEGRITY_CHECK
+x0c010003  PR_EXPLICIT_CONVERSION
+x0c02000b  PR_IPM_RETURN_REQUESTED
+x0c030102  PR_MESSAGE_TOKEN
+x0c040003  PR_NDR_REASON_CODE
+x0c050003  PR_NDR_DIAG_CODE
+x0c06000b  PR_NON_RECEIPT_NOTIFICATION_REQUESTED
+x0c070003  PR_DELIVERY_POINT
+x0c08000b  PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED
+x0c090102  PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT
+x0c0a000b  PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY
+x0c0b0003  PR_PHYSICAL_DELIVERY_MODE
+x0c0c0003  PR_PHYSICAL_DELIVERY_REPORT_REQUEST
+x0c0d0102  PR_PHYSICAL_FORWARDING_ADDRESS
+x0c0e000b  PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED
+x0c0f000b  PR_PHYSICAL_FORWARDING_PROHIBITED
+x0c100102  PR_PHYSICAL_RENDITION_ATTRIBUTES
+x0c110102  PR_PROOF_OF_DELIVERY
+x0c12000b  PR_PROOF_OF_DELIVERY_REQUESTED
+x0c130102  PR_RECIPIENT_CERTIFICATE
+x0c14001f  PR_RECIPIENT_NUMBER_FOR_ADVICE
+x0c150003  PR_RECIPIENT_TYPE
+x0c160003  PR_REGISTERED_MAIL_TYPE
+x0c17000b  PR_REPLY_REQUESTED
+x0c180003  PR_REQUESTED_DELIVERY_METHOD
+x0c190102  PR_SENDER_ENTRYID
+x0c1a001f  PR_SENDER_NAME
+x0c1b001f  PR_SUPPLEMENTARY_INFO
+x0c1c0003  PR_TYPE_OF_MTS_USER
+x0c1d0102  PR_SENDER_SEARCH_KEY
+x0c1e001f  PR_SENDER_ADDRTYPE
+x0c1f001f  PR_SENDER_EMAIL_ADDRESS
+x0e000014  PR_CURRENT_VERSION
+x0e01000b  PR_DELETE_AFTER_SUBMIT
+x0e02001f  PR_DISPLAY_BCC
+x0e03001f  PR_DISPLAY_CC
+x0e04001f  PR_DISPLAY_TO
+x0e05001f  PR_PARENT_DISPLAY
+x0e060040  PR_MESSAGE_DELIVERY_TIME
+x0e070003  PR_MESSAGE_FLAGS
+x0e080003  PR_MESSAGE_SIZE
+x0e080014  PR_MESSAGE_SIZE_EXTENDED
+x0e090102  PR_PARENT_ENTRYID
+x0e0a0102  PR_SENTMAIL_ENTRYID
+x0e0c000b  PR_CORRELATE
+x0e0d0102  PR_CORRELATE_MTSID
+x0e0e000b  PR_DISCRETE_VALUES
+x0e0f000b  PR_RESPONSIBILITY
+x0e100003  PR_SPOOLER_STATUS
+x0e110003  PR_TRANSPORT_STATUS
+x0e12000d  PR_MESSAGE_RECIPIENTS
+x0e13000d  PR_MESSAGE_ATTACHMENTS
+x0e140003  PR_SUBMIT_FLAGS
+x0e150003  PR_RECIPIENT_STATUS
+x0e160003  PR_TRANSPORT_KEY
+x0e170003  PR_MSG_STATUS
+x0e180003  PR_MESSAGE_DOWNLOAD_TIME
+x0e190014  PR_CREATION_VERSION
+x0e1a0014  PR_MODIFY_VERSION
+x0e1b000b  PR_HASATTACH
+x0e1c0003  PR_BODY_CRC
+x0e1d001f  PR_NORMALIZED_SUBJECT
+x0e1f000b  PR_RTF_IN_SYNC
+x0e200003  PR_ATTACH_SIZE
+x0e210003  PR_ATTACH_NUM
+x0e22000b  PR_PREPROCESS
+x0e230003  PR_INTERNET_ARTICLE_NUMBER
+x0e24001f  PR_NEWSGROUP_NAME
+x0e250102  PR_ORIGINATING_MTA_CERTIFICATE
+x0e260102  PR_PROOF_OF_SUBMISSION
+x0e270102  PR_NT_SECURITY_DESCRIPTOR
+x0e580102  PR_CREATOR_SID
+x0e590102  PR_LAST_MODIFIER_SID
+x0e5e0048  PR_MIME_HANDLER_CLASSIDS
+x0e610003  PR_URL_COMP_NAME_POSTFIX
+x0e62000b  PR_URL_COMP_NAME_SET
+x0e630003  PR_SUBFOLDER_CT
+x0e640003  PR_DELETED_SUBFOLDER_CT
+x0e660040  PR_DELETE_TIME
+x0e670102  PR_AGE_LIMIT
+x0e790003  PR_TRUST_SENDER
+x0e960102  PR_ATTACH_VIRUS_SCAN_INFO
+x0ff40003  PR_ACCESS
+x0ff50003  PR_ROW_TYPE
+x0ff60102  PR_INSTANCE_KEY
+x0ff70003  PR_ACCESS_LEVEL
+x0ff80102  PR_MAPPING_SIGNATURE
+x0ff90102  PR_RECORD_KEY
+x0ffa0102  PR_STORE_RECORD_KEY
+x0ffb0102  PR_STORE_ENTRYID
+x0ffc0102  PR_MINI_ICON
+x0ffd0102  PR_ICON
+x0ffe0003  PR_OBJECT_TYPE
+x0fff0102  PR_ENTRYID
+x0fff0102  PR_MEMBER_ENTRYID
+x1000001f  PR_BODY
+x1001001f  PR_REPORT_TEXT
+x10020102  PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY
+x10030102  PR_REPORTING_DL_NAME
+x10040102  PR_REPORTING_MTA_CERTIFICATE
+x10060003  PR_RTF_SYNC_BODY_CRC
+x10070003  PR_RTF_SYNC_BODY_COUNT
+x1008001f  PR_RTF_SYNC_BODY_TAG
+x10090102  PR_RTF_COMPRESSED
+x10100003  PR_RTF_SYNC_PREFIX_COUNT
+x10110003  PR_RTF_SYNC_TRAILING_COUNT
+x10120102  PR_ORIGINALLY_INTENDED_RECIP_ENTRYID
+x10130102  PR_HTML
+x1030001f  PR_INTERNET_APPROVED
+x1031001f  PR_INTERNET_CONTROL
+x1032001f  PR_INTERNET_DISTRIBUTION
+x1033001f  PR_INTERNET_FOLLOWUP_TO
+x10340003  PR_INTERNET_LINES
+x1035001f  PR_INTERNET_MESSAGE_ID
+x1036001f  PR_INTERNET_NEWSGROUPS
+x1037001f  PR_INTERNET_ORGANIZATION
+x1038001f  PR_INTERNET_NNTP_PATH
+x1039001f  PR_INTERNET_REFERENCES
+x103a001f  PR_SUPERSEDES
+x103b0102  PR_POST_FOLDER_ENTRIES
+x103c001f  PR_POST_FOLDER_NAMES
+x103d0102  PR_POST_REPLY_FOLDER_ENTRIES
+x103e001f  PR_POST_REPLY_FOLDER_NAMES
+x103f0102  PR_POST_REPLY_DENIED
+x1040001f  PR_NNTP_XREF
+x1041001f  PR_INTERNET_PRECEDENCE
+x1042001f  PR_IN_REPLY_TO_ID
+x1043001f  PR_LIST_HELP
+x1044001f  PR_LIST_SUBSCRIBE
+x1045001f  PR_LIST_UNSUBSCRIBE
+x10800003  PR_ACTION
+x10810003  PR_ACTION_FLAG
+x10820040  PR_ACTION_DATE
+x10900003  PR_FLAG_STATUS
+x10910040  PR_FLAG_COMPLETE
+x10c00102  PR_SMTP_TEMP_TBL_DATA
+x10c10003  PR_SMTP_TEMP_TBL_DATA_2
+x10c20102  PR_SMTP_TEMP_TBL_DATA_3
+x10c30040  PR_CAL_START_TIME
+x10c40040  PR_CAL_END_TIME
+x10c50040  PR_CAL_RECURRING_ID
+x10c6001f  PR_DAV_SUBMIT_DATA
+x10c70003  PR_CDO_EXPANSION_INDEX
+x10c80102  PR_IFS_INTERNAL_DATA
+x10ca0040  PR_CAL_REMINDER_NEXT_TIME
+x10f1001f  PR_OWA_URL
+x10f2000b  PR_DISABLE_FULL_FIDELITY
+x10f3001f  PR_URL_COMP_NAME
+x10f4000b  PR_ATTR_HIDDEN
+x10f5000b  PR_ATTR_SYSTEM
+x10f6000b  PR_ATTR_READONLY
+x11000102  PR_P1_CONTENT
+x11010102  PR_P1_CONTENT_TYPE
+x30000003  PR_ROWID
+x3001001f  PR_DISPLAY_NAME
+x3002001f  PR_ADDRTYPE
+x3003001f  PR_EMAIL_ADDRESS
+x3004001f  PR_COMMENT
+x30050003  PR_DEPTH
+x3006001f  PR_PROVIDER_DISPLAY
+x30070040  PR_CREATION_TIME
+x30080040  PR_LAST_MODIFICATION_TIME
+x30090003  PR_RESOURCE_FLAGS
+x300a001f  PR_PROVIDER_DLL_NAME
+x300b0102  PR_SEARCH_KEY
+x300c0102  PR_PROVIDER_UID
+x300d0003  PR_PROVIDER_ORDINAL
+x3301001f  PR_FORM_VERSION
+x33020048  PR_FORM_CLSID
+x3303001f  PR_FORM_CONTACT_NAME
+x3304001f  PR_FORM_CATEGORY
+x3305001f  PR_FORM_CATEGORY_SUB
+x33061003  PR_FORM_HOST_MAP
+x3307000b  PR_FORM_HIDDEN
+x3308001f  PR_FORM_DESIGNER_NAME
+x33090048  PR_FORM_DESIGNER_GUID
+x330a0003  PR_FORM_MESSAGE_BEHAVIOR
+x3400000b  PR_DEFAULT_STORE
+x340d0003  PR_STORE_SUPPORT_MASK
+x340e0003  PR_STORE_STATE
+x34100102  PR_IPM_SUBTREE_SEARCH_KEY
+x34110102  PR_IPM_OUTBOX_SEARCH_KEY
+x34120102  PR_IPM_WASTEBASKET_SEARCH_KEY
+x34130102  PR_IPM_SENTMAIL_SEARCH_KEY
+x34140102  PR_MDB_PROVIDER
+x3415000d  PR_RECEIVE_FOLDER_SETTINGS
+x35df0003  PR_VALID_FOLDER_MASK
+x35e00102  PR_IPM_SUBTREE_ENTRYID
+x35e20102  PR_IPM_OUTBOX_ENTRYID
+x35e30102  PR_IPM_WASTEBASKET_ENTRYID
+x35e40102  PR_IPM_SENTMAIL_ENTRYID
+x35e50102  PR_VIEWS_ENTRYID
+x35e60102  PR_COMMON_VIEWS_ENTRYID
+x35e70102  PR_FINDER_ENTRYID
+x36000003  PR_CONTAINER_FLAGS
+x36010003  PR_FOLDER_TYPE
+x36020003  PR_CONTENT_COUNT
+x36030003  PR_CONTENT_UNREAD
+x3604000d  PR_CREATE_TEMPLATES
+x3605000d  PR_DETAILS_TABLE
+x3607000d  PR_SEARCH
+x3609000b  PR_SELECTABLE
+x360a000b  PR_SUBFOLDERS
+x360b0003  PR_STATUS
+x360c001f  PR_ANR
+x360d1003  PR_CONTENTS_SORT_ORDER
+x360e000d  PR_CONTAINER_HIERARCHY
+x360f000d  PR_CONTAINER_CONTENTS
+x3610000d  PR_FOLDER_ASSOCIATED_CONTENTS
+x36110102  PR_DEF_CREATE_DL
+x36120102  PR_DEF_CREATE_MAILUSER
+x3613001f  PR_CONTAINER_CLASS
+x36140014  PR_CONTAINER_MODIFY_VERSION
+x36150102  PR_AB_PROVIDER_ID
+x36160102  PR_DEFAULT_VIEW_ENTRYID
+x36170003  PR_ASSOC_CONTENT_COUNT
+x361c0102  PR_PACKED_NAME_PROPS
+x36d00102  PR_IPM_APPOINTMENT_ENTRYID
+x36d10102  PR_IPM_CONTACT_ENTRYID
+x36d20102  PR_IPM_JOURNAL_ENTRYID
+x36d30102  PR_IPM_NOTE_ENTRYID
+x36d40102  PR_IPM_TASK_ENTRYID
+x36d50102  PR_REMINDERS_ONLINE_ENTRYID
+x36d60102  PR_REMINDERS_OFFLINE_ENTRYID
+x36d70102  PR_IPM_DRAFTS_ENTRYID
+x36d81102  PR_OUTLOOK_2003_ENTRYIDS
+x36df0102  PR_FOLDER_WEBVIEWINFO
+x36e00102  PR_FOLDER_XVIEWINFO_E
+x36e10003  PR_FOLDER_VIEWS_ONLY
+x36e41102  PR_FREEBUSY_ENTRYIDS
+x36e5001f  PR_DEF_MSG_CLASS
+x36e6001f  PR_DEF_FORM_NAME
+x36e9000b  PR_GENERATE_EXCHANGE_VIEWS
+x36ec0003  PR_AGING_PERIOD
+x36ee0003  PR_AGING_GRANULARITY
+x37000102  PR_ATTACHMENT_X400_PARAMETERS
+x3701000d  PR_ATTACH_DATA_OBJ
+x37010102  PR_ATTACH_DATA_BIN
+x37020102  PR_ATTACH_ENCODING
+x3703001f  PR_ATTACH_EXTENSION
+x3704001f  PR_ATTACH_FILENAME
+x37050003  PR_ATTACH_METHOD
+x3707001f  PR_ATTACH_LONG_FILENAME
+x3708001f  PR_ATTACH_PATHNAME
+x37090102  PR_ATTACH_RENDERING
+x370a0102  PR_ATTACH_TAG
+x370b0003  PR_RENDERING_POSITION
+x370c001f  PR_ATTACH_TRANSPORT_NAME
+x370d001f  PR_ATTACH_LONG_PATHNAME
+x370e001f  PR_ATTACH_MIME_TAG
+x370f0102  PR_ATTACH_ADDITIONAL_INFO
+x3712001f  PR_ATTACH_CONTENT_ID
+x3713001f  PR_ATTACH_CONTENT_LOCATION
+x37140003  PR_ATTACH_FLAGS
+x3716001f  PR_ATTACH_CONTENT_DISPOSITION
+x38800102  PR_SYNCEVENT_SUPPRESS_GUID
+x39000003  PR_DISPLAY_TYPE
+x39020102  PR_TEMPLATEID
+x39040102  PR_PRIMARY_CAPABILITY
+x39fe001f  PR_SMTP_ADDRESS
+x39ff001f  PR_7BIT_DISPLAY_NAME
+x39ff001f  PR_EMS_AB_DISPLAY_NAME_PRINTABLE
+x3a00001f  PR_ACCOUNT
+x3a010102  PR_ALTERNATE_RECIPIENT
+x3a02001f  PR_CALLBACK_TELEPHONE_NUMBER
+x3a03000b  PR_CONVERSION_PROHIBITED
+x3a04000b  PR_DISCLOSE_RECIPIENTS
+x3a05001f  PR_GENERATION
+x3a06001f  PR_GIVEN_NAME
+x3a07001f  PR_GOVERNMENT_ID_NUMBER
+x3a08001f  PR_BUSINESS_TELEPHONE_NUMBER
+x3a08001f  PR_OFFICE_TELEPHONE_NUMBER
+x3a09001f  PR_HOME_TELEPHONE_NUMBER
+x3a0a001f  PR_INITIALS
+x3a0b001f  PR_KEYWORD
+x3a0c001f  PR_LANGUAGE
+x3a0d001f  PR_LOCATION
+x3a0e000b  PR_MAIL_PERMISSION
+x3a0f001f  PR_MHS_COMMON_NAME
+x3a10001f  PR_ORGANIZATIONAL_ID_NUMBER
+x3a11001f  PR_SURNAME
+x3a120102  PR_ORIGINAL_ENTRYID
+x3a13001f  PR_ORIGINAL_DISPLAY_NAME
+x3a140102  PR_ORIGINAL_SEARCH_KEY
+x3a15001f  PR_POSTAL_ADDRESS
+x3a16001f  PR_COMPANY_NAME
+x3a17001f  PR_TITLE
+x3a18001f  PR_DEPARTMENT_NAME
+x3a19001f  PR_OFFICE_LOCATION
+x3a1a001f  PR_PRIMARY_TELEPHONE_NUMBER
+x3a1b001f  PR_BUSINESS2_TELEPHONE_NUMBER
+x3a1b001f  PR_OFFICE2_TELEPHONE_NUMBER
+x3a1c001f  PR_CELLULAR_TELEPHONE_NUMBER
+x3a1c001f  PR_MOBILE_TELEPHONE_NUMBER
+x3a1d001f  PR_RADIO_TELEPHONE_NUMBER
+x3a1e001f  PR_CAR_TELEPHONE_NUMBER
+x3a1f001f  PR_OTHER_TELEPHONE_NUMBER
+x3a20001f  PR_TRANSMITTABLE_DISPLAY_NAME
+x3a21001f  PR_BEEPER_TELEPHONE_NUMBER
+x3a21001f  PR_PAGER_TELEPHONE_NUMBER
+x3a220102  PR_USER_CERTIFICATE
+x3a23001f  PR_PRIMARY_FAX_NUMBER
+x3a24001f  PR_BUSINESS_FAX_NUMBER
+x3a25001f  PR_HOME_FAX_NUMBER
+x3a26001f  PR_BUSINESS_ADDRESS_COUNTRY
+x3a26001f  PR_COUNTRY
+x3a27001f  PR_BUSINESS_ADDRESS_CITY
+x3a27001f  PR_LOCALITY
+x3a28001f  PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE
+x3a28001f  PR_STATE_OR_PROVINCE
+x3a29001f  PR_BUSINESS_ADDRESS_STREET
+x3a29001f  PR_STREET_ADDRESS
+x3a2a001f  PR_BUSINESS_ADDRESS_POSTAL_CODE
+x3a2a001f  PR_POSTAL_CODE
+x3a2b001f  PR_BUSINESS_ADDRESS_POST_OFFICE_BOX
+x3a2b001f  PR_POST_OFFICE_BOX
+x3a2c001f  PR_TELEX_NUMBER
+x3a2d001f  PR_ISDN_NUMBER
+x3a2e001f  PR_ASSISTANT_TELEPHONE_NUMBER
+x3a2f001f  PR_HOME2_TELEPHONE_NUMBER
+x3a30001f  PR_ASSISTANT
+x3a40000b  PR_SEND_RICH_INFO
+x3a410040  PR_WEDDING_ANNIVERSARY
+x3a420040  PR_BIRTHDAY
+x3a43001f  PR_HOBBIES
+x3a44001f  PR_MIDDLE_NAME
+x3a45001f  PR_DISPLAY_NAME_PREFIX
+x3a46001f  PR_PROFESSION
+x3a47001f  PR_PREFERRED_BY_NAME
+x3a47001f  PR_REFERRED_BY_NAME
+x3a48001f  PR_SPOUSE_NAME
+x3a49001f  PR_COMPUTER_NETWORK_NAME
+x3a4a001f  PR_CUSTOMER_ID
+x3a4b001f  PR_TTYTDD_PHONE_NUMBER
+x3a4c001f  PR_FTP_SITE
+x3a4d0002  PR_GENDER
+x3a4e001f  PR_MANAGER_NAME
+x3a4f001f  PR_NICKNAME
+x3a50001f  PR_PERSONAL_HOME_PAGE
+x3a51001f  PR_BUSINESS_HOME_PAGE
+x3a520048  PR_CONTACT_VERSION
+x3a531102  PR_CONTACT_ENTRYIDS
+x3a54101f  PR_CONTACT_ADDRTYPES
+x3a550003  PR_CONTACT_DEFAULT_ADDRESS_INDEX
+x3a56101f  PR_CONTACT_EMAIL_ADDRESSES
+x3a57001f  PR_COMPANY_MAIN_PHONE_NUMBER
+x3a58101f  PR_CHILDRENS_NAMES
+x3a59001f  PR_HOME_ADDRESS_CITY
+x3a5a001f  PR_HOME_ADDRESS_COUNTRY
+x3a5b001f  PR_HOME_ADDRESS_POSTAL_CODE
+x3a5c001f  PR_HOME_ADDRESS_STATE_OR_PROVINCE
+x3a5d001f  PR_HOME_ADDRESS_STREET
+x3a5e001f  PR_HOME_ADDRESS_POST_OFFICE_BOX
+x3a5f001f  PR_OTHER_ADDRESS_CITY
+x3a60001f  PR_OTHER_ADDRESS_COUNTRY
+x3a61001f  PR_OTHER_ADDRESS_POSTAL_CODE
+x3a62001f  PR_OTHER_ADDRESS_STATE_OR_PROVINCE
+x3a63001f  PR_OTHER_ADDRESS_STREET
+x3a64001f  PR_OTHER_ADDRESS_POST_OFFICE_BOX
+x3a701102  PR_USER_X509_CERTIFICATE
+x3a710003  PR_SEND_INTERNET_ENCODING
+x3d000102  PR_STORE_PROVIDERS
+x3d010102  PR_AB_PROVIDERS
+x3d020102  PR_TRANSPORT_PROVIDERS
+x3d04000b  PR_DEFAULT_PROFILE
+x3d051102  PR_AB_SEARCH_PATH
+x3d060102  PR_AB_DEFAULT_DIR
+x3d070102  PR_AB_DEFAULT_PAB
+x3d080102  PR_FILTERING_HOOKS
+x3d09001f  PR_SERVICE_NAME
+x3d0a001f  PR_SERVICE_DLL_NAME
+x3d0b001f  PR_SERVICE_ENTRY_NAME
+x3d0c0102  PR_SERVICE_UID
+x3d0d0102  PR_SERVICE_EXTRA_UIDS
+x3d0e0102  PR_SERVICES
+x3d0f101f  PR_SERVICE_SUPPORT_FILES
+x3d10101f  PR_SERVICE_DELETE_FILES
+x3d110102  PR_AB_SEARCH_PATH_UPDATE
+x3d12001f  PR_PROFILE_NAME
+x3d210102  PR_ADMIN_SECURITY_DESCRIPTOR
+x3e00001f  PR_IDENTITY_DISPLAY
+x3e010102  PR_IDENTITY_ENTRYID
+x3e020003  PR_RESOURCE_METHODS
+x3e030003  PR_RESOURCE_TYPE
+x3e040003  PR_STATUS_CODE
+x3e050102  PR_IDENTITY_SEARCH_KEY
+x3e060102  PR_OWN_STORE_ENTRYID
+x3e07001f  PR_RESOURCE_PATH
+x3e08001f  PR_STATUS_STRING
+x3e09000b  PR_X400_DEFERRED_DELIVERY_CANCEL
+x3e0a0102  PR_HEADER_FOLDER_ENTRYID
+x3e0b0003  PR_REMOTE_PROGRESS
+x3e0c001f  PR_REMOTE_PROGRESS_TEXT
+x3e0d000b  PR_REMOTE_VALIDATE_OK
+x3f000003  PR_CONTROL_FLAGS
+x3f010102  PR_CONTROL_STRUCTURE
+x3f020003  PR_CONTROL_TYPE
+x3f030003  PR_DELTAX
+x3f040003  PR_DELTAY
+x3f050003  PR_XPOS
+x3f060003  PR_YPOS
+x3f070102  PR_CONTROL_ID
+x3f080003  PR_INITIAL_DETAILS_PANE
+x3f800014  PR_DID
+x3f810014  PR_SEQID
+x3f820014  PR_DRAFTID
+x3f830040  PR_CHECK_IN_TIME
+x3f84001f  PR_CHECK_IN_COMMENT
+x3f850003  PR_VERSION_OP_CODE
+x3f860102  PR_VERSION_OP_DATA
+x3f870003  PR_VERSION_SEQUENCE_NUMBER
+x3f880014  PR_ATTACH_ID
+x3f8d001f  PR_PKM_DOC_STATUS
+x3f8e101f  PR_MV_PKM_OPERATION_REQ
+x3f8f001f  PR_PKM_DOC_INTERNAL_STATE
+x3f900002  PR_VERSIONING_FLAGS
+x3f910102  PR_PKM_LAST_UNAPPROVED_VID
+x3f92101f  PR_MV_PKM_VERSION_LABELS
+x3f93101f  PR_MV_PKM_VERSION_STATUS
+x3f940102  PR_PKM_INTERNAL_DATA
+x3fc90102  PR_LAST_CONFLICT
+x3fca0102  PR_CONFLICT_MSG_KEY
+x3fd00102  PR_REPL_HEADER
+x3fd10102  PR_REPL_STATUS
+x3fd20102  PR_REPL_CHANGES
+x3fd30102  PR_REPL_RGM
+x3fd40102  PR_RMI
+x3fd50102  PR_INTERNAL_POST_REPLY
+x3fd60040  PR_NTSD_MODIFICATION_TIME
+x3fd8001f  PR_PREVIEW_UNREAD
+x3fd9001f  PR_PREVIEW
+x3fda001f  PR_ABSTRACT
+x3fdb0003  PR_DL_REPORT_FLAGS
+x3fdc0102  PR_BILATERAL_INFO
+x3fdd0003  PR_MSG_BODY_ID
+x3fde0003  PR_INTERNET_CPID
+x3fdf0003  PR_AUTO_RESPONSE_SUPPRESS
+x3fe0000d  PR_ACL_TABLE
+x3fe00102  PR_ACL_DATA
+x3fe1000d  PR_RULES_TABLE
+x3fe10102  PR_RULES_DATA
+x3fe20003  PR_FOLDER_DESIGN_FLAGS
+x3fe3000b  PR_DELEGATED_BY_RULE
+x3fe4000b  PR_DESIGN_IN_PROGRESS
+x3fe5000b  PR_SECURE_ORIGINATION
+x3fe6000b  PR_PUBLISH_IN_ADDRESS_BOOK
+x3fe70003  PR_RESOLVE_METHOD
+x3fe8001f  PR_ADDRESS_BOOK_DISPLAY_NAME
+x3fe90003  PR_EFORMS_LOCALE_ID
+x3fea000b  PR_HAS_DAMS
+x3feb0003  PR_DEFERRED_SEND_NUMBER
+x3fec0003  PR_DEFERRED_SEND_UNITS
+x3fed0003  PR_EXPIRY_NUMBER
+x3fee0003  PR_EXPIRY_UNITS
+x3fef0040  PR_DEFERRED_SEND_TIME
+x3ff00102  PR_CONFLICT_ENTRYID
+x3ff10003  PR_MESSAGE_LOCALE_ID
+x3ff20102  PR_RULE_TRIGGER_HISTORY
+x3ff30102  PR_MOVE_TO_STORE_ENTRYID
+x3ff40102  PR_MOVE_TO_FOLDER_ENTRYID
+x3ff50003  PR_STORAGE_QUOTA_LIMIT
+x3ff60003  PR_EXCESS_STORAGE_USED
+x3ff7001f  PR_SVR_GENERATING_QUOTA_MSG
+x3ff8001f  PR_CREATOR_NAME
+x3ff90102  PR_CREATOR_ENTRYID
+x3ffa001f  PR_LAST_MODIFIER_NAME
+x3ffb0102  PR_LAST_MODIFIER_ENTRYID
+x3ffc001f  PR_REPLY_RECIPIENT_SMTP_PROXIES
+x3ffd0003  PR_MESSAGE_CODEPAGE
+x3ffe0102  PR_EXTENDED_ACL_DATA
+x3fff000b  PR_FROM_I_HAVE
+x40000003  PR_NEW_ATTACH
+x40010003  PR_START_EMBED
+x40020003  PR_END_EMBED
+x40030003  PR_START_RECIP
+x40040003  PR_END_RECIP
+x40050003  PR_END_CC_RECIP
+x40060003  PR_END_BCC_RECIP
+x40070003  PR_END_P1_RECIP
+x40090003  PR_START_TOP_FLD
+x400a0003  PR_START_SUB_FLD
+x400b0003  PR_END_FOLDER
+x400c0003  PR_START_MESSAGE
+x400d0003  PR_END_MESSAGE
+x400e0003  PR_END_ATTACH
+x400f0003  PR_EC_WARNING
+x40100003  PR_START_FAI_MSG
+x40110102  PR_NEW_FX_FOLDER
+x40120003  PR_INCR_SYNC_CHG
+x40130003  PR_INCR_SYNC_DEL
+x40140003  PR_INCR_SYNC_END
+x40150003  PR_INCR_SYNC_MSG
+x40160003  PR_FX_DEL_PROP
+x40170003  PR_IDSET_GIVEN
+x40190003  PR_SENDER_FLAGS
+x401a0003  PR_SENT_REPRESENTING_FLAGS
+x401b0003  PR_RCVD_BY_FLAGS
+x401c0003  PR_RCVD_REPRESENTING_FLAGS
+x401d0003  PR_ORIGINAL_SENDER_FLAGS
+x401e0003  PR_ORIGINAL_SENT_REPRESENTING_FLAGS
+x401f0003  PR_REPORT_FLAGS
+x40200003  PR_READ_RECEIPT_FLAGS
+x4021000b  PR_SOFT_DELETES
+x402c0102  PR_MESSAGE_SUBMISSION_ID_FROM_CLIENT
+x4030001f  PR_SENDER_SIMPLE_DISP_NAME
+x4031001f  PR_SENT_REPRESENTING_SIMPLE_DISP_NAME
+x4038001f  PR_CREATOR_SIMPLE_DISP_NAME
+x403d001f  PR_ORG_ADDR_TYPE
+x403e001f  PR_ORG_EMAIL_ADDR
+x40590003  PR_CREATOR_FLAGS
+x405a0003  PR_MODIFIER_FLAGS
+x405b0003  PR_ORIGINATOR_FLAGS
+x405c0003  PR_REPORT_DESTINATION_FLAGS
+x405d0003  PR_ORIGINAL_AUTHOR_FLAGS
+x40610102  PR_ORIGINATOR_SEARCH_KEY
+x40640102  PR_REPORT_DESTINATION_SEARCH_KEY
+x40650003  PR_ER_FLAG
+x40680102  PR_INTERNET_SUBJECT
+x40690102  PR_INTERNET_SENT_REPRESENTING_NAME
+x59020003  PR_INET_MAIL_OVERRIDE_FORMAT
+x59090003  PR_MSG_EDITOR_FORMAT
+x60010003  PR_DOTSTUFF_STATE
+x65a00014  PR_RULE_SERVER_RULE_ID
+x65c20102  PR_REPLY_TEMPLATE_ID
+x65e00102  PR_SOURCE_KEY
+x65e10102  PR_PARENT_SOURCE_KEY
+x65e20102  PR_CHANGE_KEY
+x65e30102  PR_PREDECESSOR_CHANGE_LIST
+x65e40003  PR_SYNCHRONIZE_FLAGS
+x65e5000b  PR_AUTO_ADD_NEW_SUBS
+x65e6000b  PR_NEW_SUBS_GET_AUTO_ADD
+x65e7001f  PR_MESSAGE_SITE_NAME
+x65e8000b  PR_MESSAGE_PROCESSED
+x65e90003  PR_RULE_MSG_STATE
+x65ea0003  PR_RULE_MSG_USER_FLAGS
+x65eb001f  PR_RULE_MSG_PROVIDER
+x65ec001f  PR_RULE_MSG_NAME
+x65ed0003  PR_RULE_MSG_LEVEL
+x65ee0102  PR_RULE_MSG_PROVIDER_DATA
+x65ef0102  PR_RULE_MSG_ACTIONS
+x65f00102  PR_RULE_MSG_CONDITION
+x65f10003  PR_RULE_MSG_CONDITION_LCID
+x65f20002  PR_RULE_MSG_VERSION
+x65f30003  PR_RULE_MSG_SEQUENCE
+x65f4000b  PR_PREVENT_MSG_CREATE
+x65f50040  PR_IMAP_INTERNAL_DATE
+x66000003  PR_PROFILE_VERSION
+x66010003  PR_PROFILE_CONFIG_FLAGS
+x6602001f  PR_PROFILE_HOME_SERVER
+x6603001f  PR_PROFILE_USER
+x66040003  PR_PROFILE_CONNECT_FLAGS
+x66050003  PR_PROFILE_TRANSPORT_FLAGS
+x66060003  PR_PROFILE_UI_STATE
+x6607001f  PR_PROFILE_UNRESOLVED_NAME
+x6608001f  PR_PROFILE_UNRESOLVED_SERVER
+x66090003  PR_PROFILE_OPEN_FLAGS
+x6609001f  PR_PROFILE_BINDING_ORDER
+x660a0003  PR_PROFILE_TYPE
+x660b001f  PR_PROFILE_MAILBOX
+x660c001f  PR_PROFILE_SERVER
+x660d0003  PR_PROFILE_MAX_RESTRICT
+x660e001f  PR_PROFILE_AB_FILES_PATH
+x660f001f  PR_PROFILE_FAVFLD_DISPLAY_NAME
+x6610001f  PR_PROFILE_OFFLINE_STORE_PATH
+x66110102  PR_PROFILE_OFFLINE_INFO
+x6612001f  PR_PROFILE_HOME_SERVER_DN
+x6613101f  PR_PROFILE_HOME_SERVER_ADDRS
+x6614001f  PR_PROFILE_SERVER_DN
+x6615001f  PR_PROFILE_FAVFLD_COMMENT
+x6616001f  PR_PROFILE_ALLPUB_DISPLAY_NAME
+x6617001f  PR_PROFILE_ALLPUB_COMMENT
+x66180003  PR_DISABLE_WINSOCK
+x6618000b  PR_IN_TRANSIT
+x66190003  PR_PROFILE_AUTH_PACKAGE
+x66190102  PR_USER_ENTRYID
+x661a001f  PR_USER_NAME
+x661b0102  PR_MAILBOX_OWNER_ENTRYID
+x661c001f  PR_MAILBOX_OWNER_NAME
+x661d000b  PR_OOF_STATE
+x661e0102  PR_SCHEDULE_FOLDER_ENTRYID
+x661f0102  PR_IPM_DAF_ENTRYID
+x66200102  PR_NON_IPM_SUBTREE_ENTRYID
+x66210102  PR_EFORMS_REGISTRY_ENTRYID
+x66220102  PR_SPLUS_FREE_BUSY_ENTRYID
+x6623001f  PR_HIERARCHY_SERVER
+x66230102  PR_OFFLINE_ADDRBOOK_ENTRYID
+x66240102  PR_EFORMS_FOR_LOCALE_ENTRYID
+x66250102  PR_FREE_BUSY_FOR_LOCAL_SITE_ENTRYID
+x66260102  PR_ADDRBOOK_FOR_LOCAL_SITE_ENTRYID
+x66270102  PR_OFFLINE_MESSAGE_ENTRYID
+x66280102  PR_GW_MTSIN_ENTRYID
+x66290102  PR_GW_MTSOUT_ENTRYID
+x662a000b  PR_TRANSFER_ENABLED
+x662b0102  PR_TEST_LINE_SPEED
+x662c000d  PR_HIERARCHY_SYNCHRONIZER
+x662d000d  PR_CONTENTS_SYNCHRONIZER
+x662e000d  PR_COLLECTOR
+x662f000d  PR_FAST_TRANSFER
+x66300102  PR_IPM_FAVORITES_ENTRYID
+x66310102  PR_IPM_PUBLIC_FOLDERS_ENTRYID
+x6632000b  PR_STORE_OFFLINE
+x6634000d  PR_CHANGE_ADVISOR
+x6635001f  PR_FAVORITES_DEFAULT_NAME
+x66360102  PR_SYS_CONFIG_FOLDER_ENTRYID
+x66370048  PR_CHANGE_NOTIFICATION_GUID
+x66380003  PR_FOLDER_CHILD_COUNT
+x66390003  PR_RIGHTS
+x663a000b  PR_HAS_RULES
+x663b0102  PR_ADDRESS_BOOK_ENTRYID
+x663c0102  PR_PUBLIC_FOLDER_ENTRYID
+x663d0003  PR_OFFLINE_FLAGS
+x663e0003  PR_HIERARCHY_CHANGE_NUM
+x663f000b  PR_HAS_MODERATOR_RULES
+x66400003  PR_DELETED_MSG_COUNT
+x66410003  PR_DELETED_FOLDER_COUNT
+x66420040  PR_OLDEST_DELETED_ON
+x66430003  PR_DELETED_ASSOC_MSG_COUNT
+x6644001f  PR_REPLICA_SERVER
+x66450102  PR_CLIENT_ACTIONS
+x66460102  PR_DAM_ORIGINAL_ENTRYID
+x6647000b  PR_DAM_BACK_PATCHED
+x66480003  PR_RULE_ERROR
+x66490003  PR_RULE_ACTION_TYPE
+x664a000b  PR_HAS_NAMED_PROPERTIES
+x664b0014  PR_REPLICA_VERSION
+x66500003  PR_RULE_ACTION_NUMBER
+x66510102  PR_RULE_FOLDER_ENTRYID
+x66520102  PR_ACTIVE_USER_ENTRYID
+x66530003  PR_X400_ENVELOPE_TYPE
+x66540040  PR_MSG_FOLD_TIME
+x66550102  PR_ICS_CHANGE_KEY
+x66580003  PR_GW_ADMIN_OPERATIONS
+x66590102  PR_INTERNET_CONTENT
+x665a000b  PR_HAS_ATTACH_FROM_IMAIL
+x665b001f  PR_ORIGINATOR_NAME
+x665c001f  PR_ORIGINATOR_ADDR
+x665d001f  PR_ORIGINATOR_ADDRTYPE
+x665e0102  PR_ORIGINATOR_ENTRYID
+x665f0040  PR_ARRIVAL_TIME
+x66600102  PR_TRACE_INFO
+x66610102  PR_SUBJECT_TRACE_INFO
+x66620003  PR_RECIPIENT_NUMBER
+x66630102  PR_MTS_SUBJECT_ID
+x6664001f  PR_REPORT_DESTINATION_NAME
+x66650102  PR_REPORT_DESTINATION_ENTRYID
+x66660102  PR_CONTENT_SEARCH_KEY
+x66670102  PR_FOREIGN_ID
+x66680102  PR_FOREIGN_REPORT_ID
+x66690102  PR_FOREIGN_SUBJECT_ID
+x666a0102  PR_INTERNAL_TRACE_INFO
+x666a0102  PR_PROMOTE_PROP_ID_LIST
+x666c000b  PR_IN_CONFLICT
+x66700102  PR_LONGTERM_ENTRYID_FROM_TABLE
+x66710014  PR_MEMBER_ID
+x6672001f  PR_MEMBER_NAME
+x66730003  PR_MEMBER_RIGHTS
+x66740014  PR_RULE_ID
+x66750102  PR_RULE_IDS
+x66760003  PR_RULE_SEQUENCE
+x66770003  PR_RULE_STATE
+x66780003  PR_RULE_USER_FLAGS
+x667900fd  PR_RULE_CONDITION
+x667b001f  PR_PROFILE_MOAB
+x667c001f  PR_PROFILE_MOAB_GUID
+x667d0003  PR_PROFILE_MOAB_SEQ
+x667f1102  PR_IMPLIED_RESTRICTIONS
+x668000fe  PR_RULE_ACTIONS
+x6681001f  PR_RULE_PROVIDER
+x6682001f  PR_RULE_NAME
+x66830003  PR_RULE_LEVEL
+x66840102  PR_RULE_PROVIDER_DATA
+x66850040  PR_LAST_FULL_BACKUP
+x66870102  PR_PROFILE_ADDR_INFO
+x66890102  PR_PROFILE_OPTIONS_DATA
+x668a0102  PR_EVENTS_ROOT_FOLDER_ENTRYID
+x668a0102  PR_NNTP_ARTICLE_FOLDER_ENTRYID
+x668b0102  PR_NNTP_CONTROL_FOLDER_ENTRYID
+x668c0102  PR_NEWSGROUP_ROOT_FOLDER_ENTRYID
+x668d001f  PR_INBOUND_NEWSFEED_DN
+x668e001f  PR_OUTBOUND_NEWSFEED_DN
+x668f0040  PR_DELETED_ON
+x66900003  PR_REPLICATION_STYLE
+x66910102  PR_REPLICATION_SCHEDULE
+x66920003  PR_REPLICATION_MESSAGE_PRIORITY
+x66930003  PR_OVERALL_MSG_AGE_LIMIT
+x66940003  PR_REPLICATION_ALWAYS_INTERVAL
+x66950003  PR_REPLICATION_MSG_SIZE
+x6696000b  PR_IS_NEWSGROUP_ANCHOR
+x6697000b  PR_IS_NEWSGROUP
+x66980102  PR_REPLICA_LIST
+x66990003  PR_OVERALL_AGE_LIMIT
+x669a001f  PR_INTERNET_CHARSET
+x669b0014  PR_DELETED_MESSAGE_SIZE_EXTENDED
+x669c0014  PR_DELETED_NORMAL_MESSAGE_SIZE_EXTENDED
+x669d0014  PR_DELETED_ASSOC_MESSAGE_SIZE_EXTENDED
+x669e000b  PR_SECURE_IN_SITE
+x66a0001f  PR_NT_USER_NAME
+x66a10003  PR_LOCALE_ID
+x66a20040  PR_LAST_LOGON_TIME
+x66a30040  PR_LAST_LOGOFF_TIME
+x66a40003  PR_STORAGE_LIMIT_INFORMATION
+x66a5001f  PR_NEWSGROUP_COMPONENT
+x66a60102  PR_NEWSFEED_INFO
+x66a7001f  PR_INTERNET_NEWSGROUP_NAME
+x66a80003  PR_FOLDER_FLAGS
+x66a90040  PR_LAST_ACCESS_TIME
+x66aa0003  PR_RESTRICTION_COUNT
+x66ab0003  PR_CATEG_COUNT
+x66ac0003  PR_CACHED_COLUMN_COUNT
+x66ad0003  PR_NORMAL_MSG_W_ATTACH_COUNT
+x66ae0003  PR_ASSOC_MSG_W_ATTACH_COUNT
+x66af0003  PR_RECIPIENT_ON_NORMAL_MSG_COUNT
+x66b00003  PR_RECIPIENT_ON_ASSOC_MSG_COUNT
+x66b10003  PR_ATTACH_ON_NORMAL_MSG_COUNT
+x66b20003  PR_ATTACH_ON_ASSOC_MSG_COUNT
+x66b30003  PR_NORMAL_MESSAGE_SIZE
+x66b30014  PR_NORMAL_MESSAGE_SIZE_EXTENDED
+x66b40003  PR_ASSOC_MESSAGE_SIZE
+x66b40014  PR_ASSOC_MESSAGE_SIZE_EXTENDED
+x66b5001f  PR_FOLDER_PATHNAME
+x66b60003  PR_OWNER_COUNT
+x66b70003  PR_CONTACT_COUNT
+x66c30003  PR_CODE_PAGE_ID
+x66c40003  PR_RETENTION_AGE_LIMIT
+x66c5000b  PR_DISABLE_PERUSER_READ
+x66c60102  PR_INTERNET_PARSE_STATE
+x66c70102  PR_INTERNET_MESSAGE_INFO
+x6700001f  PR_PST_PATH
+x6701000b  PR_PST_REMEMBER_PW
+x67020003  PR_OST_ENCRYPTION
+x67020003  PR_PST_ENCRYPTION
+x6703001f  PR_PST_PW_SZ_OLD
+x6704001f  PR_PST_PW_SZ_NEW
+x67050003  PR_SORT_LOCALE_ID
+x6707001f  PR_URL_NAME
+x67090040  PR_LOCAL_COMMIT_TIME
+x670a0040  PR_LOCAL_COMMIT_TIME_MAX
+x670b0003  PR_DELETED_COUNT_TOTAL
+x670c0048  PR_AUTO_RESET
+x67100003  PR_URL_COMP_NAME_HASH
+x67110003  PR_MSG_FOLDER_TEMPLATE_RES_2
+x67120003  PR_RANK
+x6713000b  PR_MSG_FOLDER_TEMPLATE_RES_4
+x6714000b  PR_MSG_FOLDER_TEMPLATE_RES_5
+x6715000b  PR_MSG_FOLDER_TEMPLATE_RES_6
+x67160102  PR_MSG_FOLDER_TEMPLATE_RES_7
+x67170102  PR_MSG_FOLDER_TEMPLATE_RES_8
+x67180102  PR_MSG_FOLDER_TEMPLATE_RES_9
+x6719001f  PR_MSG_FOLDER_TEMPLATE_RES_10
+x671a001f  PR_MSG_FOLDER_TEMPLATE_RES_11
+x671b001f  PR_MSG_FOLDER_TEMPLATE_RES_12
+x671e000b  PR_PF_PLATINUM_HOME_MDB
+x671f000b  PR_PF_PROXY_REQUIRED
+x67200102  PR_INTERNET_FREE_DOC_INFO
+x67210003  PR_PF_OVER_HARD_QUOTA_LIMIT
+x67220003  PR_PF_MSG_SIZE_LIMIT
+x67430003  PR_CONNECTION_MODULUS
+x6744001f  PR_DELIVER_TO_DN
+x67460003  PR_MIME_SIZE
+x67470014  PR_FILE_SIZE_EXTENDED
+x67480014  PR_FID
+x67490014  PR_PARENT_FID
+x674a0014  PR_MID
+x674b0014  PR_CATEG_ID
+x674c0014  PR_PARENT_CATEG_ID
+x674d0014  PR_INST_ID
+x674e0003  PR_INSTANCE_NUM
+x674f0014  PR_ADDRBOOK_MID
+x67500003  PR_ICS_NOTIF
+x67510003  PR_ARTICLE_NUM_NEXT
+x67520003  PR_IMAP_LAST_ARTICLE_ID
+x6753000b  PR_NOT_822_RENDERABLE
+x67580102  PR_LTID
+x67590102  PR_CN_EXPORT
+x675a0102  PR_PCL_EXPORT
+x675b1102  PR_CN_MV_EXPORT
+x67790003  PR_PF_QUOTA_STYLE
+x677b0003  PR_PF_STORAGE_QUOTA
+x67830003  PR_SEARCH_FLAGS
+x67aa000b  PR_ASSOCIATED
+x67f00102  PR_PROFILE_SECURE_MAILBOX
+x6800001f  PR_MAILBEAT_BOUNCE_SERVER
+x68010040  PR_MAILBEAT_REQUEST_SENT
+x6802001f  PR_USENET_SITE_NAME
+x68030040  PR_MAILBEAT_REQUEST_RECEIVED
+x68040040  PR_MAILBEAT_REQUEST_PROCESSED
+x68060040  PR_MAILBEAT_REPLY_SENT
+x68070040  PR_MAILBEAT_REPLY_SUBMIT
+x68080040  PR_MAILBEAT_REPLY_RECEIVED
+x68090040  PR_MAILBEAT_REPLY_PROCESSED
+x6844101f  PR_DELEGATES_DISPLAY_NAMES
+x68451102  PR_DELEGATES_ENTRYIDS
+x68470003  PR_FREEBUSY_START_RANGE
+x68480003  PR_FREEBUSY_END_RANGE
+x6849001f  PR_FREEBUSY_EMAIL_ADDRESS
+x684f1003  PR_FREEBUSY_ALL_MONTHS
+x68501102  PR_FREEBUSY_ALL_EVENTS
+x68511003  PR_FREEBUSY_TENTATIVE_MONTHS
+x68521102  PR_FREEBUSY_TENTATIVE_EVENTS
+x68531003  PR_FREEBUSY_BUSY_MONTHS
+x68541102  PR_FREEBUSY_BUSY_EVENTS
+x68551003  PR_FREEBUSY_OOF_MONTHS
+x68561102  PR_FREEBUSY_OOF_EVENTS
+x68680040  PR_FREEBUSY_LAST_MODIFIED
+x68690003  PR_FREEBUSY_NUM_MONTHS
+x686b1003  PR_DELEGATES_SEE_PRIVATE
+x686c0102  PR_PERSONAL_FREEBUSY
+x686d000b  PR_PROCESS_MEETING_REQUESTS
+x686e000b  PR_DECLINE_RECURRING_MEETING_REQUESTS
+x686f000b  PR_DECLINE_CONFLICTING_MEETING_REQUESTS
+x70010102  PR_VD_BINARY
+x7002001f  PR_VD_STRINGS
+x70030003  PR_VD_FLAGS
+x70040102  PR_VD_LINK_TO
+x70050102  PR_VD_VIEW_FOLDER
+x7006001f  PR_VD_NAME
+x70070003  PR_VD_VERSION
+x7c00001f  PR_FAV_DISPLAY_NAME
+x7c00001f  PR_FAV_DISPLAY_ALIAS
+x7c020102  PR_FAV_PUBLIC_SOURCE_KEY
+x7c040102  PR_OST_OSTID
+x7c0a000b  PR_STORE_SLOWLINK
+x7d010003  PR_FAV_AUTOSUBFOLDERS
+x7d020102  PR_FAV_PARENT_SOURCE_KEY
+x7d030003  PR_FAV_LEVEL_MASK
+x7d070003  PR_FAV_INHERIT_AUTO
+x7d080102  PR_FAV_DEL_SUBS
+x7ffa0003  PR_ATTACHMENT_LINKID
+x7ffb0040  PR_EXCEPTION_STARTTIME
+x7ffc0040  PR_EXCEPTION_ENDTIME
+x7ffd0003  PR_ATTACHMENT_FLAGS
+x7ffe000b  PR_ATTACHMENT_HIDDEN
+x8001000b  PR_EMS_AB_DISPLAY_NAME_OVERRIDE
+x80031102  PR_EMS_AB_CA_CERTIFICATE
+x8004001f  PR_EMS_AB_FOLDER_PATHNAME
+x8005000d  PR_EMS_AB_MANAGER
+x8005001f  PR_EMS_AB_MANAGER_T
+x8006000d  PR_EMS_AB_HOME_MDB_O
+x8006001f  PR_EMS_AB_HOME_MDB
+x8007000d  PR_EMS_AB_HOME_MTA_O
+x8007001f  PR_EMS_AB_HOME_MTA
+x8008000d  PR_EMS_AB_IS_MEMBER_OF_DL
+x8008001f  PR_EMS_AB_IS_MEMBER_OF_DL_T
+x8009000d  PR_EMS_AB_MEMBER
+x8009001f  PR_EMS_AB_MEMBER_T
+x800a001f  PR_EMS_AB_AUTOREPLY_MESSAGE
+x800b000b  PR_EMS_AB_AUTOREPLY
+x800c000d  PR_EMS_AB_OWNER_O
+x800c001f  PR_EMS_AB_OWNER
+x800d000d  PR_EMS_AB_KM_SERVER_O
+x800d001f  PR_EMS_AB_KM_SERVER
+x800e000d  PR_EMS_AB_REPORTS
+x800e000d  PR_EMS_AB_REPORTS_T
+x800f101f  PR_EMS_AB_PROXY_ADDRESSES
+x80100102  PR_EMS_AB_HELP_DATA32
+x8011001f  PR_EMS_AB_TARGET_ADDRESS
+x8012101f  PR_EMS_AB_TELEPHONE_NUMBER
+x80130102  PR_EMS_AB_NT_SECURITY_DESCRIPTOR
+x8014000d  PR_EMS_AB_HOME_MDB_BL_O
+x8014101f  PR_EMS_AB_HOME_MDB_BL
+x8015000d  PR_EMS_AB_PUBLIC_DELEGATES
+x8015001f  PR_EMS_AB_PUBLIC_DELEGATES_T
+x80160102  PR_EMS_AB_CERTIFICATE_REVOCATION_LIST
+x80170102  PR_EMS_AB_ADDRESS_ENTRY_DISPLAY_TABLE
+x80180102  PR_EMS_AB_ADDRESS_SYNTAX
+x80230102  PR_EMS_AB_BUSINESS_ROLES
+x8024000d  PR_EMS_AB_OWNER_BL_O
+x8024101f  PR_EMS_AB_OWNER_BL
+x80251102  PR_EMS_AB_CROSS_CERTIFICATE_PAIR
+x80261102  PR_EMS_AB_AUTHORITY_REVOCATION_LIST
+x80270102  PR_EMS_AB_ASSOC_NT_ACCOUNT
+x80280040  PR_EMS_AB_EXPIRATION_TIME
+x80290003  PR_EMS_AB_USN_CHANGED
+x802d001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_1
+x802e001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_2
+x802f001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_3
+x8030001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_4
+x8031001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_5
+x8032001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_6
+x8033001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_7
+x8034001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_8
+x8035001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_9
+x8036001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_10
+x80371102  PR_EMS_AB_SECURITY_PROTOCOL
+x8038000d  PR_EMS_AB_PF_CONTACTS_O
+x8038101f  PR_EMS_AB_PF_CONTACTS
+x803a0102  PR_EMS_AB_HELP_DATA16
+x803b001f  PR_EMS_AB_HELP_FILE_NAME
+x803c000d  PR_EMS_AB_OBJ_DIST_NAME_O
+x803c001f  PR_EMS_AB_OBJ_DIST_NAME
+x803d001f  PR_EMS_AB_ENCRYPT_ALG_SELECTED_OTHER
+x803e001f  PR_EMS_AB_AUTOREPLY_SUBJECT
+x803f000d  PR_EMS_AB_HOME_PUBLIC_SERVER_O
+x803f001f  PR_EMS_AB_HOME_PUBLIC_SERVER
+x8040101f  PR_EMS_AB_ENCRYPT_ALG_LIST_NA
+x8041101f  PR_EMS_AB_ENCRYPT_ALG_LIST_OTHER
+x8042001f  PR_EMS_AB_IMPORTED_FROM
+x8043001f  PR_EMS_AB_ENCRYPT_ALG_SELECTED_NA
+x80440003  PR_EMS_AB_ACCESS_CATEGORY
+x80450102  PR_EMS_AB_ACTIVATION_SCHEDULE
+x80460003  PR_EMS_AB_ACTIVATION_STYLE
+x80470102  PR_EMS_AB_ADDRESS_ENTRY_DISPLAY_TABLE_MSDOS
+x8048001f  PR_EMS_AB_ADDRESS_TYPE
+x8049001f  PR_EMS_AB_ADMD
+x804a001f  PR_EMS_AB_ADMIN_DESCRIPTION
+x804b001f  PR_EMS_AB_ADMIN_DISPLAY_NAME
+x804c001f  PR_EMS_AB_ADMIN_EXTENSION_DLL
+x804d000d  PR_EMS_AB_ALIASED_OBJECT_NAME_O
+x804d001f  PR_EMS_AB_ALIASED_OBJECT_NAME
+x804e000d  PR_EMS_AB_ALT_RECIPIENT_O
+x804e001f  PR_EMS_AB_ALT_RECIPIENT
+x804f000d  PR_EMS_AB_ALT_RECIPIENT_BL_O
+x804f101f  PR_EMS_AB_ALT_RECIPIENT_BL
+x80500102  PR_EMS_AB_ANCESTOR_ID
+x8051000d  PR_EMS_AB_ASSOC_REMOTE_DXA_O
+x8051101f  PR_EMS_AB_ASSOC_REMOTE_DXA
+x80520003  PR_EMS_AB_ASSOCIATION_LIFETIME
+x8053000d  PR_EMS_AB_AUTH_ORIG_BL_O
+x8053101f  PR_EMS_AB_AUTH_ORIG_BL
+x8054001f  PR_EMS_AB_AUTHORIZED_DOMAIN
+x80550102  PR_EMS_AB_AUTHORIZED_PASSWORD
+x8056001f  PR_EMS_AB_AUTHORIZED_USER
+x8057101f  PR_EMS_AB_BUSINESS_CATEGORY
+x8058000d  PR_EMS_AB_CAN_CREATE_PF_O
+x8058101f  PR_EMS_AB_CAN_CREATE_PF
+x8059000d  PR_EMS_AB_CAN_CREATE_PF_BL_O
+x8059101f  PR_EMS_AB_CAN_CREATE_PF_BL
+x805a000d  PR_EMS_AB_CAN_CREATE_PF_DL_O
+x805a101f  PR_EMS_AB_CAN_CREATE_PF_DL
+x805b000d  PR_EMS_AB_CAN_CREATE_PF_DL_BL_O
+x805b101f  PR_EMS_AB_CAN_CREATE_PF_DL_BL
+x805c000d  PR_EMS_AB_CAN_NOT_CREATE_PF_O
+x805c101f  PR_EMS_AB_CAN_NOT_CREATE_PF
+x805d000d  PR_EMS_AB_CAN_NOT_CREATE_PF_BL_O
+x805d101f  PR_EMS_AB_CAN_NOT_CREATE_PF_BL
+x805e000d  PR_EMS_AB_CAN_NOT_CREATE_PF_DL_O
+x805e101f  PR_EMS_AB_CAN_NOT_CREATE_PF_DL
+x805f000d  PR_EMS_AB_CAN_NOT_CREATE_PF_DL_BL_O
+x805f101f  PR_EMS_AB_CAN_NOT_CREATE_PF_DL_BL
+x8060000b  PR_EMS_AB_CAN_PRESERVE_DNS
+x80610003  PR_EMS_AB_CLOCK_ALERT_OFFSET
+x8062000b  PR_EMS_AB_CLOCK_ALERT_REPAIR
+x80630003  PR_EMS_AB_CLOCK_WARNING_OFFSET
+x8064000b  PR_EMS_AB_CLOCK_WARNING_REPAIR
+x8065001f  PR_EMS_AB_COMPUTER_NAME
+x8066101f  PR_EMS_AB_CONNECTED_DOMAINS
+x80670003  PR_EMS_AB_CONTAINER_INFO
+x80680003  PR_EMS_AB_COST
+x8069001f  PR_EMS_AB_COUNTRY_NAME
+x806a0003  PR_EMS_AB_DELIV_CONT_LENGTH
+x806b1102  PR_EMS_AB_DELIV_EITS
+x806c1102  PR_EMS_AB_DELIV_EXT_CONT_TYPES
+x806d000b  PR_EMS_AB_DELIVER_AND_REDIRECT
+x806e0003  PR_EMS_AB_DELIVERY_MECHANISM
+x806f101f  PR_EMS_AB_DESCRIPTION
+x8070101f  PR_EMS_AB_DESTINATION_INDICATOR
+x8071001f  PR_EMS_AB_DIAGNOSTIC_REG_KEY
+x8072000d  PR_EMS_AB_DL_MEM_REJECT_PERMS_BL_O
+x8072101f  PR_EMS_AB_DL_MEM_REJECT_PERMS_BL
+x8073000d  PR_EMS_AB_DL_MEM_SUBMIT_PERMS_BL_O
+x8073101f  PR_EMS_AB_DL_MEM_SUBMIT_PERMS_BL
+x80741102  PR_EMS_AB_DL_MEMBER_RULE
+x8075000d  PR_EMS_AB_DOMAIN_DEF_ALT_RECIP_O
+x8075001f  PR_EMS_AB_DOMAIN_DEF_ALT_RECIP
+x8076001f  PR_EMS_AB_DOMAIN_NAME
+x80770102  PR_EMS_AB_DSA_SIGNATURE
+x8078000b  PR_EMS_AB_DXA_ADMIN_COPY
+x8079000b  PR_EMS_AB_DXA_ADMIN_FORWARD
+x807a0003  PR_EMS_AB_DXA_ADMIN_UPDATE
+x807b000b  PR_EMS_AB_DXA_APPEND_REQCN
+x807c000d  PR_EMS_AB_DXA_CONF_CONTAINER_LIST_O
+x807c101f  PR_EMS_AB_DXA_CONF_CONTAINER_LIST
+x807d0040  PR_EMS_AB_DXA_CONF_REQ_TIME
+x807e001f  PR_EMS_AB_DXA_CONF_SEQ
+x807f0003  PR_EMS_AB_DXA_CONF_SEQ_USN
+x80800003  PR_EMS_AB_DXA_EXCHANGE_OPTIONS
+x8081000b  PR_EMS_AB_DXA_EXPORT_NOW
+x80820003  PR_EMS_AB_DXA_FLAGS
+x8083001f  PR_EMS_AB_DXA_IMP_SEQ
+x80840040  PR_EMS_AB_DXA_IMP_SEQ_TIME
+x80850003  PR_EMS_AB_DXA_IMP_SEQ_USN
+x8086000b  PR_EMS_AB_DXA_IMPORT_NOW
+x8087101f  PR_EMS_AB_DXA_IN_TEMPLATE_MAP
+x8088000d  PR_EMS_AB_DXA_LOCAL_ADMIN_O
+x8088001f  PR_EMS_AB_DXA_LOCAL_ADMIN
+x80890003  PR_EMS_AB_DXA_LOGGING_LEVEL
+x808a001f  PR_EMS_AB_DXA_NATIVE_ADDRESS_TYPE
+x808b101f  PR_EMS_AB_DXA_OUT_TEMPLATE_MAP
+x808c001f  PR_EMS_AB_DXA_PASSWORD
+x808d0003  PR_EMS_AB_DXA_PREV_EXCHANGE_OPTIONS
+x808e000b  PR_EMS_AB_DXA_PREV_EXPORT_NATIVE_ONLY
+x808f0003  PR_EMS_AB_DXA_PREV_IN_EXCHANGE_SENSITIVITY
+x8090000d  PR_EMS_AB_DXA_PREV_REMOTE_ENTRIES_O
+x8090001f  PR_EMS_AB_DXA_PREV_REMOTE_ENTRIES
+x80910003  PR_EMS_AB_DXA_PREV_REPLICATION_SENSITIVITY
+x80920003  PR_EMS_AB_DXA_PREV_TEMPLATE_OPTIONS
+x80930003  PR_EMS_AB_DXA_PREV_TYPES
+x8094001f  PR_EMS_AB_DXA_RECIPIENT_CP
+x8095000d  PR_EMS_AB_DXA_REMOTE_CLIENT_O
+x8095001f  PR_EMS_AB_DXA_REMOTE_CLIENT
+x8096001f  PR_EMS_AB_DXA_REQ_SEQ
+x80970040  PR_EMS_AB_DXA_REQ_SEQ_TIME
+x80980003  PR_EMS_AB_DXA_REQ_SEQ_USN
+x8099001f  PR_EMS_AB_DXA_REQNAME
+x809a001f  PR_EMS_AB_DXA_SVR_SEQ
+x809b0040  PR_EMS_AB_DXA_SVR_SEQ_TIME
+x809c0003  PR_EMS_AB_DXA_SVR_SEQ_USN
+x809d0003  PR_EMS_AB_DXA_TASK
+x809e0003  PR_EMS_AB_DXA_TEMPLATE_OPTIONS
+x809f0040  PR_EMS_AB_DXA_TEMPLATE_TIMESTAMP
+x80a00003  PR_EMS_AB_DXA_TYPES
+x80a1000d  PR_EMS_AB_DXA_UNCONF_CONTAINER_LIST_O
+x80a1101f  PR_EMS_AB_DXA_UNCONF_CONTAINER_LIST
+x80a20003  PR_EMS_AB_ENCAPSULATION_METHOD
+x80a3000b  PR_EMS_AB_ENCRYPT
+x80a4000b  PR_EMS_AB_EXPAND_DLS_LOCALLY
+x80a5000d  PR_EMS_AB_EXPORT_CONTAINERS_O
+x80a5101f  PR_EMS_AB_EXPORT_CONTAINERS
+x80a6000b  PR_EMS_AB_EXPORT_CUSTOM_RECIPIENTS
+x80a7000b  PR_EMS_AB_EXTENDED_CHARS_ALLOWED
+x80a81102  PR_EMS_AB_EXTENSION_DATA
+x80a9101f  PR_EMS_AB_EXTENSION_NAME
+x80aa101f  PR_EMS_AB_EXTENSION_NAME_INHERITED
+x80ab1102  PR_EMS_AB_FACSIMILE_TELEPHONE_NUMBER
+x80ac0102  PR_EMS_AB_FILE_VERSION
+x80ad000b  PR_EMS_AB_FILTER_LOCAL_ADDRESSES
+x80ae000d  PR_EMS_AB_FOLDERS_CONTAINER_O
+x80ae001f  PR_EMS_AB_FOLDERS_CONTAINER
+x80af0003  PR_EMS_AB_GARBAGE_COLL_PERIOD
+x80b0001f  PR_EMS_AB_GATEWAY_LOCAL_CRED
+x80b1001f  PR_EMS_AB_GATEWAY_LOCAL_DESIG
+x80b2101f  PR_EMS_AB_GATEWAY_PROXY
+x80b30102  PR_EMS_AB_GATEWAY_ROUTING_TREE
+x80b40040  PR_EMS_AB_GWART_LAST_MODIFIED
+x80b5000d  PR_EMS_AB_HAS_FULL_REPLICA_NCS_O
+x80b5101f  PR_EMS_AB_HAS_FULL_REPLICA_NCS
+x80b6000d  PR_EMS_AB_HAS_MASTER_NCS_O
+x80b6101f  PR_EMS_AB_HAS_MASTER_NCS
+x80b70003  PR_EMS_AB_HEURISTICS
+x80b8000b  PR_EMS_AB_HIDE_DL_MEMBERSHIP
+x80b9000b  PR_EMS_AB_HIDE_FROM_ADDRESS_BOOK
+x80ba000d  PR_EMS_AB_IMPORT_CONTAINER_O
+x80ba001f  PR_EMS_AB_IMPORT_CONTAINER
+x80bb0003  PR_EMS_AB_IMPORT_SENSITIVITY
+x80bc000d  PR_EMS_AB_INBOUND_SITES_O
+x80bc101f  PR_EMS_AB_INBOUND_SITES
+x80bd0003  PR_EMS_AB_INSTANCE_TYPE
+x80be101f  PR_EMS_AB_INTERNATIONAL_ISDN_NUMBER
+x80bf0102  PR_EMS_AB_INVOCATION_ID
+x80c0000b  PR_EMS_AB_IS_DELETED
+x80c1000b  PR_EMS_AB_IS_SINGLE_VALUED
+x80c21102  PR_EMS_AB_KCC_STATUS
+x80c3101f  PR_EMS_AB_KNOWLEDGE_INFORMATION
+x80c40003  PR_EMS_AB_LINE_WRAP
+x80c50003  PR_EMS_AB_LINK_ID
+x80c6001f  PR_EMS_AB_LOCAL_BRIDGE_HEAD
+x80c7001f  PR_EMS_AB_LOCAL_BRIDGE_HEAD_ADDRESS
+x80c8000b  PR_EMS_AB_LOCAL_INITIAL_TURN
+x80c9000d  PR_EMS_AB_LOCAL_SCOPE_O
+x80c9101f  PR_EMS_AB_LOCAL_SCOPE
+x80ca001f  PR_EMS_AB_LOG_FILENAME
+x80cb0003  PR_EMS_AB_LOG_ROLLOVER_INTERVAL
+x80cc000b  PR_EMS_AB_MAINTAIN_AUTOREPLY_HISTORY
+x80cd0003  PR_EMS_AB_MAPI_DISPLAY_TYPE
+x80ce0003  PR_EMS_AB_MAPI_ID
+x80cf0003  PR_EMS_AB_MDB_BACKOFF_INTERVAL
+x80d00003  PR_EMS_AB_MDB_MSG_TIME_OUT_PERIOD
+x80d10003  PR_EMS_AB_MDB_OVER_QUOTA_LIMIT
+x80d20003  PR_EMS_AB_MDB_STORAGE_QUOTA
+x80d30003  PR_EMS_AB_MDB_UNREAD_LIMIT
+x80d4000b  PR_EMS_AB_MDB_USE_DEFAULTS
+x80d5000b  PR_EMS_AB_MESSAGE_TRACKING_ENABLED
+x80d6000b  PR_EMS_AB_MONITOR_CLOCK
+x80d7000b  PR_EMS_AB_MONITOR_SERVERS
+x80d8000b  PR_EMS_AB_MONITOR_SERVICES
+x80d9000d  PR_EMS_AB_MONITORED_CONFIGURATIONS_O
+x80d9101f  PR_EMS_AB_MONITORED_CONFIGURATIONS
+x80da000d  PR_EMS_AB_MONITORED_SERVERS_O
+x80da101f  PR_EMS_AB_MONITORED_SERVERS
+x80db101f  PR_EMS_AB_MONITORED_SERVICES
+x80dc0003  PR_EMS_AB_MONITORING_ALERT_DELAY
+x80dd0003  PR_EMS_AB_MONITORING_ALERT_UNITS
+x80de0003  PR_EMS_AB_MONITORING_AVAILABILITY_STYLE
+x80df0102  PR_EMS_AB_MONITORING_AVAILABILITY_WINDOW
+x80e0000d  PR_EMS_AB_MONITORING_CACHED_VIA_MAIL_O
+x80e0101f  PR_EMS_AB_MONITORING_CACHED_VIA_MAIL
+x80e1000d  PR_EMS_AB_MONITORING_CACHED_VIA_RPC_O
+x80e1101f  PR_EMS_AB_MONITORING_CACHED_VIA_RPC
+x80e21102  PR_EMS_AB_MONITORING_ESCALATION_PROCEDURE
+x80e30003  PR_EMS_AB_MONITORING_HOTSITE_POLL_INTERVAL
+x80e40003  PR_EMS_AB_MONITORING_HOTSITE_POLL_UNITS
+x80e50003  PR_EMS_AB_MONITORING_MAIL_UPDATE_INTERVAL
+x80e60003  PR_EMS_AB_MONITORING_MAIL_UPDATE_UNITS
+x80e70003  PR_EMS_AB_MONITORING_NORMAL_POLL_INTERVAL
+x80e80003  PR_EMS_AB_MONITORING_NORMAL_POLL_UNITS
+x80e9000d  PR_EMS_AB_MONITORING_RECIPIENTS_O
+x80e9101f  PR_EMS_AB_MONITORING_RECIPIENTS
+x80ea000d  PR_EMS_AB_MONITORING_RECIPIENTS_NDR_O
+x80ea101f  PR_EMS_AB_MONITORING_RECIPIENTS_NDR
+x80eb0003  PR_EMS_AB_MONITORING_RPC_UPDATE_INTERVAL
+x80ec0003  PR_EMS_AB_MONITORING_RPC_UPDATE_UNITS
+x80ed0003  PR_EMS_AB_MONITORING_WARNING_DELAY
+x80ee0003  PR_EMS_AB_MONITORING_WARNING_UNITS
+x80ef001f  PR_EMS_AB_MTA_LOCAL_CRED
+x80f0001f  PR_EMS_AB_MTA_LOCAL_DESIG
+x80f10102  PR_EMS_AB_N_ADDRESS
+x80f20003  PR_EMS_AB_N_ADDRESS_TYPE
+x80f3001f  PR_EMS_AB_NT_MACHINE_NAME
+x80f40003  PR_EMS_AB_NUM_OF_OPEN_RETRIES
+x80f50003  PR_EMS_AB_NUM_OF_TRANSFER_RETRIES
+x80f60003  PR_EMS_AB_OBJECT_CLASS_CATEGORY
+x80f70003  PR_EMS_AB_OBJECT_VERSION
+x80f8000d  PR_EMS_AB_OFF_LINE_AB_CONTAINERS_O
+x80f8101f  PR_EMS_AB_OFF_LINE_AB_CONTAINERS
+x80f90102  PR_EMS_AB_OFF_LINE_AB_SCHEDULE
+x80fa000d  PR_EMS_AB_OFF_LINE_AB_SERVER_O
+x80fa001f  PR_EMS_AB_OFF_LINE_AB_SERVER
+x80fb0003  PR_EMS_AB_OFF_LINE_AB_STYLE
+x80fc0003  PR_EMS_AB_OID_TYPE
+x80fd0102  PR_EMS_AB_OM_OBJECT_CLASS
+x80fe0003  PR_EMS_AB_OM_SYNTAX
+x80ff000b  PR_EMS_AB_OOF_REPLY_TO_ORIGINATOR
+x81000003  PR_EMS_AB_OPEN_RETRY_INTERVAL
+x8101101f  PR_EMS_AB_ORGANIZATION_NAME
+x8102101f  PR_EMS_AB_ORGANIZATIONAL_UNIT_NAME
+x81030102  PR_EMS_AB_ORIGINAL_DISPLAY_TABLE
+x81040102  PR_EMS_AB_ORIGINAL_DISPLAY_TABLE_MSDOS
+x8105000d  PR_EMS_AB_OUTBOUND_SITES_O
+x8105101f  PR_EMS_AB_OUTBOUND_SITES
+x81060102  PR_EMS_AB_P_SELECTOR
+x81070102  PR_EMS_AB_P_SELECTOR_INBOUND
+x81080102  PR_EMS_AB_PER_MSG_DIALOG_DISPLAY_TABLE
+x81090102  PR_EMS_AB_PER_RECIP_DIALOG_DISPLAY_TABLE
+x810a0102  PR_EMS_AB_PERIOD_REP_SYNC_TIMES
+x810b0003  PR_EMS_AB_PERIOD_REPL_STAGGER
+x810c1102  PR_EMS_AB_POSTAL_ADDRESS
+x810d1003  PR_EMS_AB_PREFERRED_DELIVERY_METHOD
+x810e001f  PR_EMS_AB_PRMD
+x810f001f  PR_EMS_AB_PROXY_GENERATOR_DLL
+x8110000d  PR_EMS_AB_PUBLIC_DELEGATES_BL_O
+x8110101f  PR_EMS_AB_PUBLIC_DELEGATES_BL
+x81110102  PR_EMS_AB_QUOTA_NOTIFICATION_SCHEDULE
+x81120003  PR_EMS_AB_QUOTA_NOTIFICATION_STYLE
+x81130003  PR_EMS_AB_RANGE_LOWER
+x81140003  PR_EMS_AB_RANGE_UPPER
+x8115001f  PR_EMS_AB_RAS_CALLBACK_NUMBER
+x8116001f  PR_EMS_AB_RAS_PHONE_NUMBER
+x8117001f  PR_EMS_AB_RAS_PHONEBOOK_ENTRY_NAME
+x8118001f  PR_EMS_AB_RAS_REMOTE_SRVR_NAME
+x81191102  PR_EMS_AB_REGISTERED_ADDRESS
+x811a001f  PR_EMS_AB_REMOTE_BRIDGE_HEAD
+x811b001f  PR_EMS_AB_REMOTE_BRIDGE_HEAD_ADDRESS
+x811c000d  PR_EMS_AB_REMOTE_OUT_BH_SERVER_O
+x811c001f  PR_EMS_AB_REMOTE_OUT_BH_SERVER
+x811d000d  PR_EMS_AB_REMOTE_SITE_O
+x811d001f  PR_EMS_AB_REMOTE_SITE
+x811e0003  PR_EMS_AB_REPLICATION_SENSITIVITY
+x811f0003  PR_EMS_AB_REPLICATION_STAGGER
+x8120000b  PR_EMS_AB_REPORT_TO_ORIGINATOR
+x8121000b  PR_EMS_AB_REPORT_TO_OWNER
+x81220003  PR_EMS_AB_REQ_SEQ
+x8123000d  PR_EMS_AB_RESPONSIBLE_LOCAL_DXA_O
+x8123001f  PR_EMS_AB_RESPONSIBLE_LOCAL_DXA
+x8124000d  PR_EMS_AB_RID_SERVER_O
+x8124001f  PR_EMS_AB_RID_SERVER
+x8125000d  PR_EMS_AB_ROLE_OCCUPANT_O
+x8125101f  PR_EMS_AB_ROLE_OCCUPANT
+x8126101f  PR_EMS_AB_ROUTING_LIST
+x81270003  PR_EMS_AB_RTS_CHECKPOINT_SIZE
+x81280003  PR_EMS_AB_RTS_RECOVERY_TIMEOUT
+x81290003  PR_EMS_AB_RTS_WINDOW_SIZE
+x812a000d  PR_EMS_AB_RUNS_ON_O
+x812a101f  PR_EMS_AB_RUNS_ON
+x812b0102  PR_EMS_AB_S_SELECTOR
+x812c0102  PR_EMS_AB_S_SELECTOR_INBOUND
+x812d0003  PR_EMS_AB_SEARCH_FLAGS
+x812e1102  PR_EMS_AB_SEARCH_GUIDE
+x812f000d  PR_EMS_AB_SEE_ALSO_O
+x812f101f  PR_EMS_AB_SEE_ALSO
+x8130101f  PR_EMS_AB_SERIAL_NUMBER
+x81310003  PR_EMS_AB_SERVICE_ACTION_FIRST
+x81320003  PR_EMS_AB_SERVICE_ACTION_OTHER
+x81330003  PR_EMS_AB_SERVICE_ACTION_SECOND
+x81340003  PR_EMS_AB_SERVICE_RESTART_DELAY
+x8135001f  PR_EMS_AB_SERVICE_RESTART_MESSAGE
+x81360003  PR_EMS_AB_SESSION_DISCONNECT_TIMER
+x8137101f  PR_EMS_AB_SITE_AFFINITY
+x8138101f  PR_EMS_AB_SITE_PROXY_SPACE
+x81390040  PR_EMS_AB_SPACE_LAST_COMPUTED
+x813a001f  PR_EMS_AB_STREET_ADDRESS
+x813b000d  PR_EMS_AB_SUB_REFS_O
+x813b101f  PR_EMS_AB_SUB_REFS
+x813c0003  PR_EMS_AB_SUBMISSION_CONT_LENGTH
+x813d1102  PR_EMS_AB_SUPPORTED_APPLICATION_CONTEXT
+x813e000d  PR_EMS_AB_SUPPORTING_STACK_O
+x813e101f  PR_EMS_AB_SUPPORTING_STACK
+x813f000d  PR_EMS_AB_SUPPORTING_STACK_BL_O
+x813f101f  PR_EMS_AB_SUPPORTING_STACK_BL
+x81400102  PR_EMS_AB_T_SELECTOR
+x81410102  PR_EMS_AB_T_SELECTOR_INBOUND
+x8142101f  PR_EMS_AB_TARGET_MTAS
+x81431102  PR_EMS_AB_TELETEX_TERMINAL_IDENTIFIER
+x81440003  PR_EMS_AB_TEMP_ASSOC_THRESHOLD
+x81450003  PR_EMS_AB_TOMBSTONE_LIFETIME
+x8146001f  PR_EMS_AB_TRACKING_LOG_PATH_NAME
+x81470003  PR_EMS_AB_TRANS_RETRY_MINS
+x81480003  PR_EMS_AB_TRANS_TIMEOUT_MINS
+x81490003  PR_EMS_AB_TRANSFER_RETRY_INTERVAL
+x814a0003  PR_EMS_AB_TRANSFER_TIMEOUT_NON_URGENT
+x814b0003  PR_EMS_AB_TRANSFER_TIMEOUT_NORMAL
+x814c0003  PR_EMS_AB_TRANSFER_TIMEOUT_URGENT
+x814d0003  PR_EMS_AB_TRANSLATION_TABLE_USED
+x814e000b  PR_EMS_AB_TRANSPORT_EXPEDITED_DATA
+x814f0003  PR_EMS_AB_TRUST_LEVEL
+x81500003  PR_EMS_AB_TURN_REQUEST_THRESHOLD
+x8151000b  PR_EMS_AB_TWO_WAY_ALTERNATE_FACILITY
+x8152000d  PR_EMS_AB_UNAUTH_ORIG_BL_O
+x8152101f  PR_EMS_AB_UNAUTH_ORIG_BL
+x81531102  PR_EMS_AB_USER_PASSWORD
+x81540003  PR_EMS_AB_USN_CREATED
+x81550003  PR_EMS_AB_USN_DSA_LAST_OBJ_REMOVED
+x81560003  PR_EMS_AB_USN_LAST_OBJ_REM
+x81570003  PR_EMS_AB_USN_SOURCE
+x8158101f  PR_EMS_AB_X121_ADDRESS
+x81590102  PR_EMS_AB_X25_CALL_USER_DATA_INCOMING
+x815a0102  PR_EMS_AB_X25_CALL_USER_DATA_OUTGOING
+x815b0102  PR_EMS_AB_X25_FACILITIES_DATA_INCOMING
+x815c0102  PR_EMS_AB_X25_FACILITIES_DATA_OUTGOING
+x815d0102  PR_EMS_AB_X25_LEASED_LINE_PORT
+x815e000b  PR_EMS_AB_X25_LEASED_OR_SWITCHED
+x815f001f  PR_EMS_AB_X25_REMOTE_MTA_PHONE
+x81600102  PR_EMS_AB_X400_ATTACHMENT_TYPE
+x81610003  PR_EMS_AB_X400_SELECTOR_SYNTAX
+x81620102  PR_EMS_AB_X500_ACCESS_CONTROL_LIST
+x81630003  PR_EMS_AB_XMIT_TIMEOUT_NON_URGENT
+x81640003  PR_EMS_AB_XMIT_TIMEOUT_NORMAL
+x81650003  PR_EMS_AB_XMIT_TIMEOUT_URGENT
+x81660102  PR_EMS_AB_SITE_FOLDER_GUID
+x8167000d  PR_EMS_AB_SITE_FOLDER_SERVER_O
+x8167001f  PR_EMS_AB_SITE_FOLDER_SERVER
+x81680003  PR_EMS_AB_REPLICATION_MAIL_MSG_SIZE
+x81690102  PR_EMS_AB_MAXIMUM_OBJECT_ID
+x8170101f  PR_EMS_AB_NETWORK_ADDRESS
+x8171101f  PR_EMS_AB_LDAP_DISPLAY_NAME
+x81730003  PR_EMS_AB_SCHEMA_FLAGS
+x8174000d  PR_EMS_AB_BRIDGEHEAD_SERVERS_O
+x8174101f  PR_EMS_AB_BRIDGEHEAD_SERVERS
+x8175001f  PR_EMS_AB_WWW_HOME_PAGE
+x8176001f  PR_EMS_AB_NNTP_CONTENT_FORMAT
+x8177001f  PR_EMS_AB_POP_CONTENT_FORMAT
+x81780003  PR_EMS_AB_LANGUAGE
+x8179001f  PR_EMS_AB_POP_CHARACTER_SET
+x817a0003  PR_EMS_AB_USN_INTERSITE
+x817b001f  PR_EMS_AB_SUB_SITE
+x817c1003  PR_EMS_AB_SCHEMA_VERSION
+x817d001f  PR_EMS_AB_NNTP_CHARACTER_SET
+x817e000b  PR_EMS_AB_USE_SERVER_VALUES
+x817f0003  PR_EMS_AB_ENABLED_PROTOCOLS
+x81800102  PR_EMS_AB_CONNECTION_LIST_FILTER
+x8181101f  PR_EMS_AB_AVAILABLE_AUTHORIZATION_PACKAGES
+x8182101f  PR_EMS_AB_CHARACTER_SET_LIST
+x8183000b  PR_EMS_AB_USE_SITE_VALUES
+x8184101f  PR_EMS_AB_ENABLED_AUTHORIZATION_PACKAGES
+x8185001f  PR_EMS_AB_CHARACTER_SET
+x81860003  PR_EMS_AB_CONTENT_TYPE
+x8187000b  PR_EMS_AB_ANONYMOUS_ACCESS
+x81880102  PR_EMS_AB_CONTROL_MSG_FOLDER_ID
+x8189001f  PR_EMS_AB_USENET_SITE_NAME
+x818a0102  PR_EMS_AB_CONTROL_MSG_RULES
+x818b001f  PR_EMS_AB_AVAILABLE_DISTRIBUTIONS
+x818d0102  PR_EMS_AB_OUTBOUND_HOST
+x818e101f  PR_EMS_AB_INBOUND_HOST
+x818f0003  PR_EMS_AB_OUTGOING_MSG_SIZE_LIMIT
+x81900003  PR_EMS_AB_INCOMING_MSG_SIZE_LIMIT
+x8191000b  PR_EMS_AB_SEND_TNEF
+x81920102  PR_EMS_AB_AUTHORIZED_PASSWORD_CONFIRM
+x8193001f  PR_EMS_AB_INBOUND_NEWSFEED
+x81940003  PR_EMS_AB_NEWSFEED_TYPE
+x8195001f  PR_EMS_AB_OUTBOUND_NEWSFEED
+x81960102  PR_EMS_AB_NEWSGROUP_LIST
+x8197101f  PR_EMS_AB_NNTP_DISTRIBUTIONS
+x8198001f  PR_EMS_AB_NEWSGROUP
+x8199001f  PR_EMS_AB_MODERATOR
+x819a001f  PR_EMS_AB_AUTHENTICATION_TO_USE
+x819b000b  PR_EMS_AB_HTTP_PUB_GAL
+x819c0003  PR_EMS_AB_HTTP_PUB_GAL_LIMIT
+x819e1102  PR_EMS_AB_HTTP_PUB_PF
+x81a1001f  PR_EMS_AB_X500_RDN
+x81a2001f  PR_EMS_AB_X500_NC
+x81a3101f  PR_EMS_AB_REFERRAL_LIST
+x81a4000b  PR_EMS_AB_NNTP_DISTRIBUTIONS_FLAG
+x81a5000d  PR_EMS_AB_ASSOC_PROTOCOL_CFG_NNTP_O
+x81a5001f  PR_EMS_AB_ASSOC_PROTOCOL_CFG_NNTP
+x81a6000d  PR_EMS_AB_NNTP_NEWSFEEDS_O
+x81a6101f  PR_EMS_AB_NNTP_NEWSFEEDS
+x81a8000b  PR_EMS_AB_ENABLED_PROTOCOL_CFG
+x81a9101f  PR_EMS_AB_HTTP_PUB_AB_ATTRIBUTES
+x81ab101f  PR_EMS_AB_HTTP_SERVERS
+x81ac000b  PR_EMS_AB_MODERATED
+x81ad001f  PR_EMS_AB_RAS_ACCOUNT
+x81ae0102  PR_EMS_AB_RAS_PASSWORD
+x81af0102  PR_EMS_AB_INCOMING_PASSWORD
+x81b0000b  PR_EMS_AB_OUTBOUND_HOST_TYPE
+x81b1000b  PR_EMS_AB_PROXY_GENERATION_ENABLED
+x81b20102  PR_EMS_AB_ROOT_NEWSGROUPS_FOLDER_ID
+x81b3000b  PR_EMS_AB_CONNECTION_TYPE
+x81b40003  PR_EMS_AB_CONNECTION_LIST_FILTER_TYPE
+x81b50003  PR_EMS_AB_PORT_NUMBER
+x81b6101f  PR_EMS_AB_PROTOCOL_SETTINGS
+x81b7001f  PR_EMS_AB_GROUP_BY_ATTR_1
+x81b8001f  PR_EMS_AB_GROUP_BY_ATTR_2
+x81b9001f  PR_EMS_AB_GROUP_BY_ATTR_3
+x81ba001f  PR_EMS_AB_GROUP_BY_ATTR_4
+x81be001f  PR_EMS_AB_VIEW_SITE
+x81bf001f  PR_EMS_AB_VIEW_CONTAINER_1
+x81c0001f  PR_EMS_AB_VIEW_CONTAINER_2
+x81c1001f  PR_EMS_AB_VIEW_CONTAINER_3
+x81c20040  PR_EMS_AB_PROMO_EXPIRATION
+x81c3101f  PR_EMS_AB_DISABLED_GATEWAY_PROXY
+x81c40102  PR_EMS_AB_COMPROMISED_KEY_LIST
+x81c5000d  PR_EMS_AB_INSADMIN_O
+x81c5001f  PR_EMS_AB_INSADMIN
+x81c6000b  PR_EMS_AB_OVERRIDE_NNTP_CONTENT_FORMAT
+x81c7000d  PR_EMS_AB_OBJ_VIEW_CONTAINERS_O
+x81c7101f  PR_EMS_AB_OBJ_VIEW_CONTAINERS
+x8c180003  PR_EMS_AB_VIEW_FLAGS
+x8c19001f  PR_EMS_AB_GROUP_BY_ATTR_VALUE_STR
+x8c1a000d  PR_EMS_AB_GROUP_BY_ATTR_VALUE_DN_O
+x8c1a001f  PR_EMS_AB_GROUP_BY_ATTR_VALUE_DN
+x8c1b1102  PR_EMS_AB_VIEW_DEFINITION
+x8c1c0102  PR_EMS_AB_MIME_TYPES
+x8c1d0003  PR_EMS_AB_LDAP_SEARCH_CFG
+x8c1e000d  PR_EMS_AB_INBOUND_DN_O
+x8c1e001f  PR_EMS_AB_INBOUND_DN
+x8c1f000b  PR_EMS_AB_INBOUND_NEWSFEED_TYPE
+x8c20000b  PR_EMS_AB_INBOUND_ACCEPT_ALL
+x8c21000b  PR_EMS_AB_ENABLED
+x8c22000b  PR_EMS_AB_PRESERVE_INTERNET_CONTENT
+x8c23000b  PR_EMS_AB_DISABLE_DEFERRED_COMMIT
+x8c24000b  PR_EMS_AB_CLIENT_ACCESS_ENABLED
+x8c25000b  PR_EMS_AB_REQUIRE_SSL
+x8c26001f  PR_EMS_AB_ANONYMOUS_ACCOUNT
+x8c270102  PR_EMS_AB_CERTIFICATE_CHAIN_V3
+x8c280102  PR_EMS_AB_CERTIFICATE_REVOCATION_LIST_V3
+x8c290102  PR_EMS_AB_CERTIFICATE_REVOCATION_LIST_V1
+x8c301102  PR_EMS_AB_CROSS_CERTIFICATE_CRL
+x8c31000b  PR_EMS_AB_SEND_EMAIL_MESSAGE
+x8c32000b  PR_EMS_AB_ENABLE_COMPATIBILITY
+x8c33101f  PR_EMS_AB_SMIME_ALG_LIST_NA
+x8c34101f  PR_EMS_AB_SMIME_ALG_LIST_OTHER
+x8c35001f  PR_EMS_AB_SMIME_ALG_SELECTED_NA
+x8c36001f  PR_EMS_AB_SMIME_ALG_SELECTED_OTHER
+x8c37000b  PR_EMS_AB_DEFAULT_MESSAGE_FORMAT
+x8c38001f  PR_EMS_AB_TYPE
+x8c3a0003  PR_EMS_AB_DO_OAB_VERSION
+x8c3b0102  PR_EMS_AB_VOICE_MAIL_SYSTEM_GUID
+x8c3c001f  PR_EMS_AB_VOICE_MAIL_USER_ID
+x8c3d001f  PR_EMS_AB_VOICE_MAIL_PASSWORD
+x8c3e0102  PR_EMS_AB_VOICE_MAIL_RECORDED_NAME
+x8c3f101f  PR_EMS_AB_VOICE_MAIL_GREETINGS
+x8c401102  PR_EMS_AB_VOICE_MAIL_FLAGS
+x8c410003  PR_EMS_AB_VOICE_MAIL_VOLUME
+x8c420003  PR_EMS_AB_VOICE_MAIL_SPEED
+x8c431003  PR_EMS_AB_VOICE_MAIL_RECORDING_LENGTH
+x8c44001f  PR_EMS_AB_DISPLAY_NAME_SUFFIX
+x8c451102  PR_EMS_AB_ATTRIBUTE_CERTIFICATE
+x8c461102  PR_EMS_AB_DELTA_REVOCATION_LIST
+x8c471102  PR_EMS_AB_SECURITY_POLICY
+x8c48000b  PR_EMS_AB_SUPPORT_SMIME_SIGNATURES
+x8c49000b  PR_EMS_AB_DELEGATE_USER
+x8c50000b  PR_EMS_AB_LIST_PUBLIC_FOLDERS
+x8c51001f  PR_EMS_AB_LABELEDURI
+x8c52000b  PR_EMS_AB_RETURN_EXACT_MSG_SIZE
+x8c53001f  PR_EMS_AB_GENERATION_QUALIFIER
+x8c54001f  PR_EMS_AB_HOUSE_IDENTIFIER
+x8c550102  PR_EMS_AB_SUPPORTED_ALGORITHMS
+x8c56001f  PR_EMS_AB_DMD_NAME
+x8c57001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_11
+x8c58001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_12
+x8c59001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_13
+x8c60001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_14
+x8c61001f  PR_EMS_AB_EXTENSION_ATTRIBUTE_15
+x8c620003  PR_EMS_AB_REPLICATED_OBJECT_VERSION
+x8c63001f  PR_EMS_AB_MAIL_DROP
+x8c64001f  PR_EMS_AB_FORWARDING_ADDRESS
+x8c650102  PR_EMS_AB_FORM_DATA
+x8c66001f  PR_EMS_AB_OWA_SERVER
+x8c67001f  PR_EMS_AB_EMPLOYEE_NUMBER
+x8c68001f  PR_EMS_AB_TELEPHONE_PERSONAL_PAGER
+x8c69001f  PR_EMS_AB_EMPLOYEE_TYPE
+x8c6a1102  PR_EMS_AB_TAGGED_X509_CERT
+x8c6b001f  PR_EMS_AB_PERSONAL_TITLE
+x8c6c001f  PR_EMS_AB_LANGUAGE_ISO639
+xf000000d  PR_EMS_AB_OTHER_RECIPS
+xfff8101f  PR_EMS_AB_CHILD_RDNS
+xfff9001f  PR_EMS_AB_HIERARCHY_PATH
+xfffa0102  PR_EMS_AB_OBJECT_OID
+xfffb000b  PR_EMS_AB_IS_MASTER
+xfffc0102  PR_EMS_AB_PARENT_ENTRYID
+xfffd0003  PR_EMS_AB_CONTAINERID
+xfffd0003  PR_EMS_AB_DOS_ENTRYID
+xfffe001f  PR_EMS_AB_SERVER
diff --git a/server/lib/mapi.h b/server/lib/mapi.h
new file mode 100644
index 0000000..e9188f6
--- /dev/null
+++ b/server/lib/mapi.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2002-2004 Novell, Inc. */
+
+#ifndef __MAPI_H__
+#define __MAPI_H__
+
+G_BEGIN_DECLS
+
+typedef enum {
+	MAPI_ACCESS_MODIFY            = (1 << 0),
+	MAPI_ACCESS_READ              = (1 << 1),
+	MAPI_ACCESS_DELETE            = (1 << 2),
+	MAPI_ACCESS_CREATE_HIERARCHY  = (1 << 3),
+	MAPI_ACCESS_CREATE_CONTENTS   = (1 << 4),
+	MAPI_ACCESS_CREATE_ASSOCIATED = (1 << 5)
+} MapiAccess;
+
+typedef enum {
+	cdoSingle    = 0,	/* non-recurring appointment */
+	cdoMaster    = 1,	/* recurring appointment */
+	cdoInstance  = 2,	/* single instance of recurring appointment */
+	cdoException = 3	/* exception to recurring appointment */
+} CdoInstanceTypes;
+
+typedef enum {
+	MAPI_STORE    = 0x1,	/* Message Store */
+	MAPI_ADDRBOOK = 0x2,	/* Address Book */
+	MAPI_FOLDER   = 0x3,	/* Folder */
+	MAPI_ABCONT   = 0x4,	/* Address Book Container */
+	MAPI_MESSAGE  = 0x5,	/* Message */
+	MAPI_MAILUSER = 0x6,	/* Individual Recipient */
+	MAPI_ATTACH   = 0x7,	/* Attachment */
+	MAPI_DISTLIST = 0x8,	/* Distribution List Recipient */
+	MAPI_PROFSECT = 0x9,	/* Profile Section */
+	MAPI_STATUS   = 0xA,	/* Status Object */
+	MAPI_SESSION  = 0xB,	/* Session */
+	MAPI_FORMINFO = 0xC	/* Form Information */
+} MapiObjectType;
+
+typedef enum {
+/*  For address book contents tables */
+	DT_MAILUSER         = 0x00000000,
+	DT_DISTLIST         = 0x00000001,
+	DT_FORUM            = 0x00000002,
+	DT_AGENT            = 0x00000003,
+	DT_ORGANIZATION     = 0x00000004,
+	DT_PRIVATE_DISTLIST = 0x00000005,
+	DT_REMOTE_MAILUSER  = 0x00000006,
+/*  For address book hierarchy tables */
+	DT_MODIFIABLE       = 0x00010000,
+	DT_GLOBAL           = 0x00020000,
+	DT_LOCAL            = 0x00030000,
+	DT_WAN              = 0x00040000,
+	DT_NOT_SPECIFIC     = 0x00050000,
+/*  For folder hierarchy tables */
+	DT_FOLDER           = 0x01000000,
+	DT_FOLDER_LINK      = 0x02000000,
+	DT_FOLDER_SPECIAL   = 0x04000000
+} MapiPrDisplayType;
+
+typedef enum {
+	MAPI_ORIG = 0,
+	MAPI_TO   = 1,
+	MAPI_CC   = 2,
+	MAPI_BCC  = 3
+} MapiPrRecipientType;
+
+typedef enum {
+	MAPI_MSGFLAG_READ            = 0x0001,
+	MAPI_MSGFLAG_UNMODIFIED      = 0x0002,
+	MAPI_MSGFLAG_SUBMIT          = 0x0004,
+	MAPI_MSGFLAG_UNSENT          = 0x0008,
+	MAPI_MSGFLAG_HASATTACH       = 0x0010,
+	MAPI_MSGFLAG_FROMME          = 0x0020,
+	MAPI_MSGFLAG_ASSOCIATED      = 0x0040,
+	MAPI_MSGFLAG_RESEND          = 0x0080,
+	MAPI_MSGFLAG_RN_PENDING      = 0x0100,
+	MAPI_MSGFLAG_NRN_PENDING     = 0x0200,
+	MAPI_MSGFLAG_ORIGIN_X400     = 0x1000,
+	MAPI_MSGFLAG_ORIGIN_INTERNET = 0x2000,
+	MAPI_MSGFLAG_ORIGIN_MISC_EXT = 0x8000
+} MapiPrMessageFlags;
+
+typedef enum {
+	MAPI_ACTION_REPLIED   = 261,
+	MAPI_ACTION_FORWARDED = 262
+} MapiPrAction;
+
+typedef enum {
+	MAPI_ACTION_FLAG_REPLIED_TO_SENDER = 102,
+	MAPI_ACTION_FLAG_REPLIED_TO_ALL    = 103,
+	MAPI_ACTION_FLAG_FORWARDED         = 104
+} MapiPrActionFlag;
+
+typedef enum {
+	MAPI_FOLLOWUP_UNFLAGGED = 0,
+	MAPI_FOLLOWUP_COMPLETED = 1,
+	MAPI_FOLLOWUP_FLAGGED   = 2
+} MapiPrFlagStatus;
+
+typedef enum {
+	MAPI_PRIO_URGENT    =  1,
+	MAPI_PRIO_NORMAL    =  0,
+	MAPI_PRIO_NONURGENT = -1
+} MapiPrPriority;
+
+typedef enum {
+	MAPI_SENSITIVITY_NONE                 = 0,
+	MAPI_SENSITIVITY_PERSONAL             = 1,
+	MAPI_SENSITIVITY_PRIVATE              = 2,
+	MAPI_SENSITIVITY_COMPANY_CONFIDENTIAL = 3
+} MapiPrSensitivity;
+
+typedef enum {
+	MAPI_IMPORTANCE_LOW    = 0,
+	MAPI_IMPORTANCE_NORMAL = 1,
+	MAPI_IMPORTANCE_HIGH   = 2
+} MapiPrImportance;
+
+G_END_DECLS
+
+#endif /* __MAPI_H__ */
diff --git a/server/lib/ruletest.c b/server/lib/ruletest.c
new file mode 100644
index 0000000..7cf0bd4
--- /dev/null
+++ b/server/lib/ruletest.c
@@ -0,0 +1,90 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2003-2004 Novell, Inc.
+ *
+ * This  program is free  software; you  can redistribute  it and/or
+ * modify it under the terms of version 2  of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* Server-side rule test program */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <string.h>
+
+#include "e2k-context.h"
+#include "e2k-propnames.h"
+#include "e2k-rule.h"
+#include "e2k-rule-xml.h"
+#include "test-utils.h"
+
+const gchar *test_program_name = "ruletest";
+
+static const gchar *rules_props[] = {
+	PR_RULES_DATA,
+};
+static const gint n_rules_props = sizeof (rules_props) / sizeof (rules_props[0]);
+
+void
+test_main (gint argc, gchar **argv)
+{
+	const gchar *url;
+	E2kContext *ctx;
+	E2kHTTPStatus status;
+	E2kResult *results;
+	gint nresults;
+	GByteArray *ba;
+	E2kRules *rules;
+	xmlDoc *doc;
+
+	if (argc != 2) {
+		fprintf (stderr, "Usage: %s URL\n", argv[0]);
+		exit (1);
+	}
+	url = argv[1];
+
+	ctx = test_get_context (url);
+	status = e2k_context_propfind (ctx, NULL, url,
+				       rules_props, n_rules_props,
+				       &results, &nresults);
+	test_abort_if_http_error (status);
+
+	ba = e2k_properties_get_prop (results[0].props, PR_RULES_DATA);
+	if (!ba) {
+		printf ("No rules\n");
+		goto done;
+	}
+
+	rules = e2k_rules_from_binary (ba);
+	if (!rules) {
+		printf ("Could not parse rules\n");
+		goto done;
+	}
+
+	doc = e2k_rules_to_xml (rules);
+	if (doc) {
+		xmlDocFormatDump (stdout, doc, TRUE);
+		xmlFreeDoc (doc);
+	} else
+		printf ("Could not convert normal rules to XML\n");
+
+	e2k_rules_free (rules);
+	e2k_results_free (results, nresults);
+
+ done:
+	test_quit ();
+}
diff --git a/server/lib/test-utils.c b/server/lib/test-utils.c
new file mode 100644
index 0000000..5d40881
--- /dev/null
+++ b/server/lib/test-utils.c
@@ -0,0 +1,259 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <libedataserverui/e-passwords.h>
+#include <gtk/gtk.h>
+
+#include "e2k-context.h"
+#include "e2k-global-catalog.h"
+#include "e2k-uri.h"
+
+#include "test-utils.h"
+
+extern const gchar *test_program_name;
+
+/**
+ * test_ask_password:
+ * @prompt: prompt string
+ *
+ * Prints @prompt followed by ": " and waits for the user to type
+ * a password (with echoing disabled).
+ *
+ * Return value: the password (or %NULL if stdin is not a tty).
+ **/
+gchar *
+test_ask_password (const gchar *prompt)
+{
+	gchar *password;
+	struct termios t;
+	gint old_lflag;
+	gchar buf[80];
+
+	if (tcgetattr (STDIN_FILENO, &t) != 0)
+		return NULL;
+
+	old_lflag = t.c_lflag;
+	t.c_lflag = (t.c_lflag | ICANON | ECHONL) & ~ECHO;
+	tcsetattr (STDIN_FILENO, TCSANOW, &t);
+
+	fprintf (stderr, "%s: ", prompt);
+	fflush (stdout);
+
+	/* For some reason, fgets can return EINTR on
+	 * Linux if ECHO is false...
+	 */
+	do
+		password = fgets (buf, sizeof (buf), stdin);
+	while (password == NULL && errno == EINTR);
+
+	t.c_lflag = old_lflag;
+	tcsetattr (STDIN_FILENO, TCSANOW, &t);
+
+	if (!password)
+		exit (1);
+	return g_strndup (password, strcspn (password, "\n"));
+}
+
+/**
+ * test_get_password:
+ * @user: username to get the password for
+ * @host: Exchange (or global catalog) server name
+ *
+ * Tries to get a password for @user on @host, by looking it up in
+ * the Evolution password database or by prompting the user.
+ *
+ * Return value: the password, or %NULL if it could not be determined.
+ **/
+const gchar *
+test_get_password (const gchar *user, const gchar *host)
+{
+	static gchar *password = NULL;
+	gchar *prompt;
+
+	if (password)
+		return password;
+
+	if (host) {
+		gchar *key;
+
+		key = g_strdup_printf ("exchange://%s %s", user, host);
+		password = e_passwords_get_password ("Exchange", key);
+		g_free (key);
+	}
+
+	if (!password) {
+		if (host) {
+			prompt = g_strdup_printf ("Password for %s %s",
+						  user, host);
+		} else
+			prompt = g_strdup_printf ("Password for %s", user);
+
+		password = test_ask_password (prompt);
+		g_free (prompt);
+	}
+
+	return password;
+}
+
+/**
+ * test_get_context:
+ * @uri: an Exchange HTTP/HTTPS URI
+ *
+ * Creates an %E2kContext based on @uri. If @uri does not contain a
+ * username, the user's local username will be used. If it does not
+ * contain a password, test_get_password() will be called to get one.
+ *
+ * Return value: the new %E2kContext (always; if an error occurs,
+ * test_get_context() will exit the program).
+ **/
+E2kContext *
+test_get_context (const gchar *uri)
+{
+	E2kContext *ctx;
+	E2kUri *euri;
+
+	ctx = e2k_context_new (uri);
+	if (!ctx) {
+		fprintf (stderr, "Could not parse %s as URI\n", uri);
+		exit (1);
+	}
+
+	euri = e2k_uri_new (uri);
+	if (!euri->user)
+		euri->user = g_strdup (g_get_user_name ());
+	if (!euri->passwd)
+		euri->passwd = g_strdup (test_get_password (euri->user, euri->host));
+
+	e2k_context_set_auth (ctx, euri->user, euri->domain,
+			      euri->authmech, euri->passwd);
+
+	e2k_uri_free (euri);
+	return ctx;
+}
+
+/**
+ * test_get_gc:
+ * @server: the global catalog server to contact
+ *
+ * Creates an %E2kGlobalCatalog for the server @server.
+ * test_get_password() will be called to get a password.
+ *
+ * Return value: the new %E2kGlobalCatalog (always; if an error occurs,
+ * test_get_gc() will exit the program).
+ **/
+E2kGlobalCatalog *
+test_get_gc (const gchar *server)
+{
+	E2kGlobalCatalog *gc;
+	const gchar *password;
+	gchar *user, *p;
+
+	if (strchr (server, '@')) {
+		user = g_strdup (server);
+		p = strchr (user, '@');
+		*p = '\0';
+		server = p + 1;
+	} else
+		user = g_strdup (g_get_user_name ());
+
+	password = test_get_password (user, server);
+	gc = e2k_global_catalog_new (server, -1, user, NULL, password, E2K_AUTOCONFIG_USE_GAL_DEFAULT);
+	if (!gc) {
+		fprintf (stderr, "Could not create GC\n");
+		exit (1);
+	}
+	g_free (user);
+
+	return gc;
+}
+
+static gchar **global_argv;
+static gint global_argc;
+static GMainLoop *loop;
+
+/**
+ * test_main:
+ * @argc: argc
+ * @argv: argv
+ *
+ * test-utils.o includes a main() function that calls various
+ * initialization routines, starts the main loop, and then calls
+ * test_main(). So test_main() is the entry point for a
+ * test-utils-using program.
+ **/
+
+/**
+ * test_quit:
+ *
+ * Cleanly quits a test program.
+ **/
+void
+test_quit (void)
+{
+	g_main_loop_quit (loop);
+}
+
+/**
+ * test_abort_if_http_error:
+ * @status: an HTTP status code
+ *
+ * Checks if @status is an HTTP or libsoup error, and if so, prints
+ * the error message and exits.
+ **/
+void
+test_abort_if_http_error (E2kHTTPStatus status)
+{
+	if (E2K_HTTP_STATUS_IS_TRANSPORT_ERROR (status)) {
+		fprintf (stderr, "\n%s\n", soup_status_get_phrase (status));
+		exit (1);
+	} else if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		fprintf (stderr, "\n%d\n", status);
+		exit (1);
+	}
+}
+
+static gboolean
+idle_run (gpointer data)
+{
+	test_main (global_argc, global_argv);
+	return FALSE;
+}
+
+gint
+main (gint argc, gchar **argv)
+{
+	gtk_init (&argc, &argv);
+
+	global_argc = argc;
+	global_argv = argv;
+
+	loop = g_main_loop_new (NULL, TRUE);
+	g_idle_add (idle_run, NULL);
+	g_main_loop_run (loop);
+
+	return 0;
+}
diff --git a/server/lib/test-utils.h b/server/lib/test-utils.h
new file mode 100644
index 0000000..0a62574
--- /dev/null
+++ b/server/lib/test-utils.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H
+
+#include "e2k-types.h"
+#include "e2k-http-utils.h"
+
+void              test_main                (gint             argc,
+					    gchar          **argv);
+void              test_quit                (void);
+
+const gchar       *test_get_password        (const gchar     *user,
+					    const gchar     *host);
+E2kContext       *test_get_context         (const gchar     *uri);
+E2kGlobalCatalog *test_get_gc              (const gchar     *server);
+
+void              test_abort_if_http_error (E2kHTTPStatus   status);
+
+/* lower-level util */
+gchar             *test_ask_password        (const gchar     *prompt);
+
+#endif /* TEST_UTILS_H */
diff --git a/server/lib/urltest.c b/server/lib/urltest.c
new file mode 100644
index 0000000..5e13fa9
--- /dev/null
+++ b/server/lib/urltest.c
@@ -0,0 +1,158 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This  program is free  software; you  can redistribute  it and/or
+ * modify it under the terms of version 2  of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * This program takes account URL as input and dups E2KUri structure
+ * values
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "e2k-uri.h"
+#include "test-utils.h"
+
+const gchar *test_program_name = "urltest";
+
+static void
+dump_uri (E2kUri *euri) {
+	const gchar *temp;
+
+	printf ("URI contents \n");
+	printf ("==================== \n");
+
+	if (euri->protocol)
+		printf("Protocol : %s \n", euri->protocol);
+	else
+		printf ("Protocol : NULL \n");
+	if (euri->user)
+		printf("User : %s \n", euri->user);
+	else
+		printf ("User : NULL \n");
+	if (euri->domain)
+		printf("Domain : %s \n", euri->domain);
+	else
+		printf ("Domain : NULL \n");
+	if (euri->authmech)
+		printf("Authmech : %s \n", euri->authmech);
+	else
+		printf ("Authmech : NULL \n");
+	if (euri->passwd)
+		printf("Password : %s \n", euri->passwd);
+	else
+		printf ("Password : NULL \n");
+	if (euri->host)
+		printf("Host : %s \n", euri->host);
+	else
+		printf ("Host : NULL \n");
+	if (euri->port)
+		printf("Port : %d \n", euri->port);
+	else
+		printf ("Port : NULL \n");
+	if (euri->path)
+		printf("Path : %s \n", euri->path);
+	else
+		printf ("Path : NULL \n");
+	if (euri->params) {
+		printf("\nParams : \n");
+		temp = e2k_uri_get_param (euri, "ad_server");
+		if (temp) printf ("\tAd server = %s\n", temp);
+		else printf ("\tAd server = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "ad_limit");
+		if (temp) printf ("\tAd Limit = %s\n", temp);
+		else printf ("\tAd Limit = NULL\n");
+
+		temp = e2k_uri_get_param (euri, "passwd_exp_warn_period");
+		if (temp) printf ("\tPasswd expiry warn period = %s\n", temp);
+		else printf ("\tPasswd expiry warn period = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "offline_sync");
+		if (temp) printf ("\tOffline Sync = %s\n", temp);
+		else printf ("\tOffline Sync = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "owa_path");
+		if (temp) printf ("\tOwa path = %s\n", temp);
+		else printf ("\tOwa path = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "pf_server");
+		if (temp) printf ("\tPf server = %s\n", temp);
+		else printf ("\tPf server = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "use_ssl");
+		if (temp) printf ("\tSSL = %s\n", temp);
+		else printf ("\tSSL = NULL\n");
+
+		temp = e2k_uri_get_param (euri, "mailbox");
+		if (temp) printf ("\tMailbox = %s\n", temp);
+		else printf ("\tMailbox = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "filter");
+		if (temp) printf ("\tFilter = %s\n", temp);
+		else printf ("\tFilter = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "filter_junk");
+		if (temp) printf ("\tFilter junk = %s\n", temp);
+		else printf ("\tFilter junk = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "filter_junk_inbox");
+		if (temp) printf ("\tFilter junk inbox = %s\n", temp);
+		else printf ("\tFilter junk inbox = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "owa_protocol");
+		if (temp) printf ("\tOwa protocol = %s\n", temp);
+		else printf ("\tOwa protocol = NULL \n");
+
+		temp = e2k_uri_get_param (euri, "owa_url");
+		if (temp) printf ("\tOwa url = %s\n", temp);
+		else printf ("\tOwa url = NULL \n");
+	}
+	else
+		printf ("Params : NULL \n");
+	if (euri->query)
+		printf("Query : %s \n", euri->query);
+	else
+		printf ("Query : NULL \n");
+	if (euri->fragment)
+		printf("Fragment : %s \n", euri->fragment);
+	else
+		printf ("Fragment : NULL \n");
+}
+
+void
+test_main (gint argc, gchar **argv)
+{
+	const gchar *url;
+	E2kUri *euri;
+
+	if (argc != 2) {
+		fprintf (stderr, "Usage: %s url \n", argv[0]);
+		exit (1);
+	}
+
+	url = argv[1];
+	euri = e2k_uri_new (url);
+	dump_uri (euri);
+	test_quit ();
+}
diff --git a/server/storage/Makefile.am b/server/storage/Makefile.am
new file mode 100644
index 0000000..7a93c5f
--- /dev/null
+++ b/server/storage/Makefile.am
@@ -0,0 +1,77 @@
+if OS_WIN32
+WIN32_BOOTSTRAP_LIBS = \
+	$(top_builddir)/win32/libedataserverui-1.2.la
+endif
+
+noinst_LTLIBRARIES = \
+	libexchange-storage.la
+
+libexchange_storage_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-DG_LOG_DOMAIN=\""evolution-exchange-storage"\" \
+	-DPREFIX=\""$(prefix)"\" \
+	-DSYSCONFDIR=\""$(sysconfdir)"\" \
+	-DDATADIR=\""$(datadir)"\" \
+	-DLIBDIR=\""$(datadir)"\" \
+	-DCONNECTOR_LOCALEDIR=\""$(localedir)"\" \
+	-DCONNECTOR_GLADEDIR=\""$(gladedir)"\" \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/server/lib \
+	-I$(top_srcdir)/server/xntlm \
+	-I$(top_builddir)/server/lib \
+	$(GNOME_PLATFORM_CFLAGS) \
+	$(EVOLUTION_DATA_SERVER_CFLAGS) \
+	$(KRB5_CFLAGS) \
+	$(LDAP_CFLAGS) \
+	$(SOUP_CFLAGS)
+
+libexchange_storage_la_SOURCES = \
+	e-folder-exchange.c \
+	e-folder-exchange.h \
+	e-folder-tree.c \
+	e-folder-tree.h \
+	e-folder-type-registry.c \
+	e-folder-type-registry.h \
+	e-folder.c \
+	e-folder.h \
+	e-storage.c \
+	e-storage.h \
+	exchange-account.c \
+	exchange-account.h \
+	exchange-constants.h \
+	exchange-esource.c \
+	exchange-esource.h \
+	exchange-folder-size.c \
+	exchange-folder-size.h \
+	exchange-hierarchy-favorites.c \
+	exchange-hierarchy-favorites.h \
+	exchange-hierarchy-foreign.c \
+	exchange-hierarchy-foreign.h \
+	exchange-hierarchy-gal.c \
+	exchange-hierarchy-gal.h \
+	exchange-hierarchy-somedav.c \
+	exchange-hierarchy-somedav.h \
+	exchange-hierarchy-webdav.c \
+	exchange-hierarchy-webdav.h \
+	exchange-hierarchy.c \
+	exchange-hierarchy.h \
+	exchange-oof.c \
+	exchange-oof.h \
+	exchange-types.h
+
+libexchange_storage_la_LIBADD = \
+	$(WIN32_BOOTSTRAP_LIBS) \
+	$(top_builddir)/server/lib/libexchange.la \
+	$(top_builddir)/server/xntlm/libxntlm.la \
+	$(GNOME_PLATFORM_LIBS) \
+	$(EVOLUTION_DATA_SERVER_LIBS) \
+	$(LDAP_LIBS) \
+	$(SOUP_LIBS) \
+	$(SOCKET_LIBS)
+
+libexchange_storage_la_LDFLAGS = \
+	$(KRB5_LIBS) \
+	$(NO_UNDEFINED) \
+	-version-info $(LIBEXCHANGE_STORAGE_CURRENT):$(LIBEXCHANGE_STORAGE_REVISION):$(LIBEXCHANGE_STORAGE_AGE)
+
+-include $(top_srcdir)/git.mk
diff --git a/server/storage/e-folder-exchange.c b/server/storage/e-folder-exchange.c
new file mode 100644
index 0000000..a22f8e3
--- /dev/null
+++ b/server/storage/e-folder-exchange.c
@@ -0,0 +1,1039 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "libedataserver/e-source-list.h"
+#include "libedataserver/e-data-server-util.h"
+#include "libedataserver/e-xml-utils.h"
+
+#include "e-folder-exchange.h"
+#include "e2k-path.h"
+#include "e2k-uri.h"
+#include "exchange-account.h"
+#include "exchange-esource.h"
+#include "exchange-hierarchy.h"
+
+#define d(x)
+
+struct _EFolderExchangePrivate {
+	ExchangeHierarchy *hier;
+	gchar *internal_uri, *permanent_uri;
+	gchar *outlook_class, *storage_dir;
+	gchar *path;
+	gint64 folder_size;
+	gboolean has_subfolders;
+	gboolean rescan_tree;
+};
+
+#define PARENT_TYPE E_TYPE_FOLDER
+static EFolderClass *parent_class = NULL;
+
+#define EF_CLASS(hier) (E_FOLDER_CLASS (G_OBJECT_GET_CLASS (hier)))
+
+static void dispose (GObject *object);
+static void finalize (GObject *object);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* methods */
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
+}
+
+static void
+init (GObject *object)
+{
+	EFolderExchange *folder = E_FOLDER_EXCHANGE (object);
+
+	folder->priv = g_new0 (EFolderExchangePrivate, 1);
+	folder->priv->rescan_tree = TRUE;
+}
+
+static void
+dispose (GObject *object)
+{
+	EFolderExchange *folder = E_FOLDER_EXCHANGE (object);
+
+	if (folder->priv->hier) {
+		g_object_unref (folder->priv->hier);
+		folder->priv->hier = NULL;
+	}
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	EFolderExchange *folder = E_FOLDER_EXCHANGE (object);
+
+	g_free (folder->priv->internal_uri);
+	g_free (folder->priv->permanent_uri);
+	g_free (folder->priv->outlook_class);
+	g_free (folder->priv->storage_dir);
+	g_free (folder->priv->path);
+	g_free (folder->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (e_folder_exchange, EFolderExchange, class_init, init, PARENT_TYPE)
+
+static gchar *
+sanitize_path (const gchar *path)
+{
+	gchar **comps;
+	gchar *new_path = NULL;
+
+	if (!path)
+		return g_strdup("");	/* ??? or NULL? */
+
+	comps = g_strsplit (path, ";", 2);
+	if (comps[1])
+		new_path = g_strdup_printf ("%s%s", comps[0], comps[1]);
+	else if (comps[0])
+		new_path = g_strdup (comps[0]);
+
+	g_strfreev (comps);
+	return new_path;
+}
+
+#define d(x)
+
+/**
+ * e_folder_exchange_new:
+ * @hier: the #ExchangeHierarchy containing the new folder
+ * @name: the display name of the folder
+ * @type: the Evolution type of the folder (eg, "mail")
+ * @outlook_class: the Outlook IPM class of the folder (eg, "IPM.Note")
+ * @physical_uri: the "exchange:" URI of the folder
+ * @internal_uri: the "http:" URI of the folder
+ *
+ * Return value: a new #EFolderExchange
+ **/
+EFolder *
+e_folder_exchange_new (ExchangeHierarchy *hier, const gchar *name,
+		       const gchar *type, const gchar *outlook_class,
+		       const gchar *physical_uri, const gchar *internal_uri)
+{
+	EFolderExchange *efe;
+	EFolder *ef;
+	gchar *sanitized_path;
+
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY (hier), NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+	g_return_val_if_fail (type != NULL, NULL);
+	g_return_val_if_fail (physical_uri != NULL, NULL);
+	g_return_val_if_fail (internal_uri != NULL, NULL);
+
+	d(g_print ("e_folder_exchange_new: name=[%s], type=[%s], internal_uri=[%s], physical_uri=[%s]\n",
+		   name, type, internal_uri, physical_uri));
+
+	efe = g_object_new (E_TYPE_FOLDER_EXCHANGE, NULL);
+	ef = (EFolder *)efe;
+
+	e_folder_construct (ef, name, type, "");
+
+	efe->priv->hier = hier;
+	g_object_ref (hier);
+
+	efe->priv->internal_uri = g_strdup (internal_uri);
+	e_folder_set_physical_uri (ef, physical_uri);
+
+	sanitized_path = sanitize_path (e2k_uri_path (physical_uri));
+	e2k_uri_decode (sanitized_path);
+	efe->priv->path = sanitized_path;
+	d(g_print ("e_folder_exchange_new: sanitized=[%s]\n", sanitized_path));
+
+	efe->priv->outlook_class = g_strdup (outlook_class);
+
+	/* Add ESources */
+	if (hier->type == EXCHANGE_HIERARCHY_PERSONAL ||
+	    hier->type == EXCHANGE_HIERARCHY_FAVORITES) {
+
+		if ((strcmp (type, "calendar") == 0) ||
+		    (strcmp (type, "calendar/public") == 0)) {
+			add_folder_esource (hier->account,
+					    EXCHANGE_CALENDAR_FOLDER,
+					    name,
+					    physical_uri);
+		}
+		else if ((strcmp (type, "tasks") == 0) ||
+			 (strcmp (type, "tasks/public") == 0)) {
+			add_folder_esource (hier->account,
+					    EXCHANGE_TASKS_FOLDER,
+					    name,
+					    physical_uri);
+		}
+		else if ((strcmp (type, "contacts") == 0) ||
+			 (strcmp (type, "contacts/public") == 0)) {
+			add_folder_esource (hier->account,
+					    EXCHANGE_CONTACTS_FOLDER,
+					    name,
+					    physical_uri);
+		}
+	}
+	return ef;
+}
+
+/**
+ * e_folder_exchange_get_internal_uri:
+ * @folder: an #EFolderExchange
+ *
+ * Returns the folder's internal (http/https) URI. The caller
+ * should not cache this value, since it may change if the server
+ * sends a redirect when we try to use it.
+ *
+ * Return value: @folder's internal (http/https) URI
+ **/
+const gchar *
+e_folder_exchange_get_internal_uri (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return E_FOLDER_EXCHANGE (folder)->priv->internal_uri;
+}
+
+/**
+ * e_folder_exchange_set_internal_uri:
+ * @folder: an #EFolderExchange
+ * @internal_uri: new internal_uri value
+ *
+ * Updates @folder's internal URI to reflect a redirection response
+ * from the server.
+ **/
+void
+e_folder_exchange_set_internal_uri (EFolder *folder, const gchar *internal_uri)
+{
+	EFolderExchange *efe;
+
+	g_return_if_fail (E_IS_FOLDER_EXCHANGE (folder));
+	g_return_if_fail (internal_uri != NULL);
+
+	efe = E_FOLDER_EXCHANGE (folder);
+	g_free (efe->priv->internal_uri);
+	efe->priv->internal_uri = g_strdup (internal_uri);
+}
+
+/**
+ * e_folder_exchange_get_path:
+ * @folder: an #EFolderExchange
+ *
+ * Return value: @folder's path within its Evolution storage
+ **/
+const gchar *
+e_folder_exchange_get_path (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return E_FOLDER_EXCHANGE (folder)->priv->path;
+}
+
+/**
+ * e_folder_exchange_get_permanent_uri:
+ * @folder: an #EFolderExchange
+ *
+ * Returns the folder's permanent URI. See docs/entryids for more
+ * details.
+ *
+ * Return value: @folder's permanent URI
+ **/
+const gchar *
+e_folder_exchange_get_permanent_uri (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return E_FOLDER_EXCHANGE (folder)->priv->permanent_uri;
+}
+
+/**
+ * e_folder_exchange_set_permanent_uri:
+ * @folder: an #EFolderExchange
+ * @permanent_uri: permanent_uri value
+ *
+ * Sets @folder's permanent URI (which must, for obvious reasons, have
+ * previously been unset).
+ **/
+void
+e_folder_exchange_set_permanent_uri (EFolder *folder, const gchar *permanent_uri)
+{
+	EFolderExchange *efe;
+
+	g_return_if_fail (E_IS_FOLDER_EXCHANGE (folder));
+
+	efe = E_FOLDER_EXCHANGE (folder);
+	g_return_if_fail (efe->priv->permanent_uri == NULL && permanent_uri != NULL);
+
+	efe->priv->permanent_uri = g_strdup (permanent_uri);
+}
+
+/**
+ * e_folder_exchange_get_folder_size:
+ * @folder: an #EFolderExchange
+ *
+ * Returns the folder's size. See docs/entryids for more
+ * details.
+ *
+ * Return value: @folder's size
+ **/
+gint64
+e_folder_exchange_get_folder_size (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), -1);
+
+	return E_FOLDER_EXCHANGE (folder)->priv->folder_size;
+}
+
+/**
+ * e_folder_exchange_set_folder_size:
+ * @folder: an #EFolderExchange
+ * @folder_size: folder size
+ *
+ * Sets @folder's folder_size
+ **/
+void
+e_folder_exchange_set_folder_size (EFolder *folder, gint64 folder_size)
+{
+	EFolderExchange *efe;
+
+	g_return_if_fail (E_IS_FOLDER_EXCHANGE (folder));
+
+	efe = E_FOLDER_EXCHANGE (folder);
+
+	efe->priv->folder_size = folder_size;
+}
+
+/**
+ * e_folder_exchange_get_has_subfolders:
+ * @folder: an #EFolderExchange
+ *
+ * Return value: whether or not @folder has subfolders
+ **/
+gboolean
+e_folder_exchange_get_has_subfolders (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), FALSE);
+
+	return E_FOLDER_EXCHANGE (folder)->priv->has_subfolders;
+}
+
+/**
+ * e_folder_exchange_set_has_subfolders
+ * @folder: an #EFolderExchange
+ * @has_subfolders: whether or not @folder has subfolders
+ *
+ * Sets @folder's has_subfolders flag.
+ **/
+void
+e_folder_exchange_set_has_subfolders (EFolder *folder,
+				      gboolean has_subfolders)
+{
+	g_return_if_fail (E_IS_FOLDER_EXCHANGE (folder));
+
+	E_FOLDER_EXCHANGE (folder)->priv->has_subfolders = has_subfolders;
+}
+
+/**
+ * e_folder_exchange_get_rescan_tree:
+ * @folder: an #EFolderExchange
+ *
+ * Return value: whether or not to rescan @folder tree
+ **/
+gboolean
+e_folder_exchange_get_rescan_tree (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), FALSE);
+
+	return E_FOLDER_EXCHANGE (folder)->priv->rescan_tree;
+}
+
+/**
+ * e_folder_exchange_set_rescan_tree
+ * @folder: an #EFolderExchange
+ * @rescan_tree: whether or not @folder needs to be rescanned
+ *
+ * Sets @folder's has_subfolders flag.
+ **/
+void
+e_folder_exchange_set_rescan_tree (EFolder *folder,
+				   gboolean rescan_tree)
+{
+	g_return_if_fail (E_IS_FOLDER_EXCHANGE (folder));
+
+	E_FOLDER_EXCHANGE (folder)->priv->rescan_tree = rescan_tree;
+}
+
+/**
+ * e_folder_exchange_get_outlook_class:
+ * @folder: an #EFolderExchange
+ *
+ * Return value: @folder's Outlook IPM class
+ **/
+const gchar *
+e_folder_exchange_get_outlook_class (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return E_FOLDER_EXCHANGE (folder)->priv->outlook_class;
+}
+
+/**
+ * e_folder_exchange_get_hierarchy
+ * @folder: an #EFolderExchange
+ *
+ * Return value: @folder's hierarchy
+ **/
+ExchangeHierarchy *
+e_folder_exchange_get_hierarchy (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return E_FOLDER_EXCHANGE (folder)->priv->hier;
+}
+
+/**
+ * e_folder_exchange_get_storage_file:
+ * @folder: an #EFolderExchange
+ * @filename: name of a file
+ *
+ * This returns a unique filename ending in @filename in the local
+ * storage space reserved for @folder.
+ *
+ * Return value: the full filename, which must be freed.
+ **/
+gchar *
+e_folder_exchange_get_storage_file (EFolder *folder, const gchar *filename)
+{
+	EFolderExchange *efe;
+	gchar *path;
+
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	efe = (EFolderExchange *)folder;
+
+	if (!efe->priv->storage_dir) {
+		efe->priv->storage_dir = e_path_to_physical (
+			efe->priv->hier->account->storage_dir,
+			efe->priv->path);
+		g_mkdir_with_parents (efe->priv->storage_dir, 0755);
+	}
+
+	path = g_build_filename (efe->priv->storage_dir, filename, NULL);
+	return path;
+}
+
+/**
+ * e_folder_exchange_save_to_file:
+ * @folder: the folder
+ * @filename: a filename
+ *
+ * Saves all relevant information about @folder to @filename.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+e_folder_exchange_save_to_file (EFolder *folder, const gchar *filename)
+{
+	xmlDoc *doc;
+	xmlNode *root;
+	const gchar *name, *type, *outlook_class;
+	const gchar *physical_uri, *internal_uri, *permanent_uri;
+	gchar *folder_size;
+	gint64 fsize;
+	gint status;
+
+	name = e_folder_get_name (folder);
+	type = e_folder_get_type_string (folder);
+	outlook_class = e_folder_exchange_get_outlook_class (folder);
+	physical_uri = e_folder_get_physical_uri (folder);
+	internal_uri = e_folder_exchange_get_internal_uri (folder);
+	permanent_uri = e_folder_exchange_get_permanent_uri (folder);
+
+	g_return_val_if_fail (name && type && physical_uri && internal_uri,
+			      FALSE);
+
+	if ((fsize = e_folder_exchange_get_folder_size (folder)) >= 0)
+		folder_size = g_strdup_printf ("%" G_GINT64_FORMAT, fsize);
+	else
+		return FALSE;
+
+	doc = xmlNewDoc ((xmlChar *) "1.0");
+	root = xmlNewDocNode (doc, NULL, (xmlChar *) "connector-folder", NULL);
+	xmlNewProp (root, (xmlChar *) "version", (xmlChar *) "1");
+	xmlDocSetRootElement (doc, root);
+
+	xmlNewChild (
+		root, NULL,
+		(xmlChar *) "displayname",
+		(xmlChar *) name);
+	xmlNewChild (
+		root, NULL,
+		(xmlChar *) "type",
+		(xmlChar *) type);
+	xmlNewChild (
+		root, NULL,
+		(xmlChar *) "outlook_class",
+		(xmlChar *) outlook_class);
+	xmlNewChild (
+		root, NULL,
+		(xmlChar *) "physical_uri",
+		(xmlChar *) physical_uri);
+	xmlNewChild (
+		root, NULL,
+		(xmlChar *) "internal_uri",
+		(xmlChar *) internal_uri);
+	xmlNewChild (
+		root, NULL,
+		(xmlChar *) "folder_size",
+		(xmlChar *) folder_size);
+	if (permanent_uri)
+		xmlNewChild (
+			root, NULL,
+			(xmlChar *) "permanent_uri",
+			(xmlChar *) permanent_uri);
+
+	status = e_xml_save_file (filename, doc);
+
+	if (status < 0)
+		g_unlink (filename);
+
+	xmlFreeDoc (doc);
+
+	g_free (folder_size);
+
+	return status == 0;
+}
+
+/**
+ * e_folder_exchange_new_from_file:
+ * @hier: the hierarchy to create the folder under
+ * @filename: a filename
+ *
+ * Loads information about a folder from a saved file.
+ *
+ * Return value: the folder, or %NULL on a failed load.
+ **/
+EFolder *
+e_folder_exchange_new_from_file (ExchangeHierarchy *hier, const gchar *filename)
+{
+	EFolder *folder = NULL;
+	xmlDoc *doc;
+	xmlNode *root, *node;
+	xmlChar *version, *display_name = NULL;
+	xmlChar *type = NULL, *outlook_class = NULL;
+	xmlChar *physical_uri = NULL, *internal_uri = NULL;
+	xmlChar *permanent_uri = NULL;
+	xmlChar *folder_size = NULL;
+
+	doc = e_xml_parse_file (filename);
+
+	if (!doc)
+		return NULL;
+
+	root = xmlDocGetRootElement (doc);
+	if (root == NULL || strcmp ((gchar *) root->name, "connector-folder") != 0) {
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	version = xmlGetProp (root, (xmlChar *) "version");
+	if (!version) {
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	if (strcmp ((gchar *) version, "1") != 0) {
+		xmlFreeDoc (doc);
+		xmlFree (version);
+		return NULL;
+	}
+	xmlFree (version);
+
+	node = e_xml_get_child_by_name (root, (xmlChar *) "displayname");
+	if (!node)
+		goto done;
+	display_name = xmlNodeGetContent (node);
+
+	node = e_xml_get_child_by_name (root, (xmlChar *) "type");
+	if (!node)
+		goto done;
+	type = xmlNodeGetContent (node);
+
+	node = e_xml_get_child_by_name (root, (xmlChar *) "outlook_class");
+	if (!node)
+		goto done;
+	outlook_class = xmlNodeGetContent (node);
+
+	node = e_xml_get_child_by_name (root, (xmlChar *) "physical_uri");
+	if (!node)
+		goto done;
+	physical_uri = xmlNodeGetContent (node);
+
+	node = e_xml_get_child_by_name (root, (xmlChar *) "internal_uri");
+	if (!node)
+		goto done;
+	internal_uri = xmlNodeGetContent (node);
+
+	if (!display_name || !type || !physical_uri || !internal_uri)
+		goto done;
+
+	folder = e_folder_exchange_new (
+		hier,
+		(gchar *) display_name,
+		(gchar *) type,
+		(gchar *) outlook_class,
+		(gchar *) physical_uri,
+		(gchar *) internal_uri);
+
+	node = e_xml_get_child_by_name (root, (xmlChar *) "permanent_uri");
+	if (node) {
+		permanent_uri = xmlNodeGetContent (node);
+		e_folder_exchange_set_permanent_uri (folder, (gchar *) permanent_uri);
+	}
+
+	node = e_xml_get_child_by_name (root, (xmlChar *) "folder_size");
+	if (node) {
+		folder_size = xmlNodeGetContent (node);
+		e_folder_exchange_set_folder_size (folder, atoi ((gchar *) folder_size));
+	}
+
+ done:
+	xmlFree (display_name);
+	xmlFree (type);
+	xmlFree (outlook_class);
+	xmlFree (physical_uri);
+	xmlFree (internal_uri);
+	xmlFree (permanent_uri);
+	xmlFree (folder_size);
+	xmlFreeDoc (doc);
+
+	return folder;
+}
+
+/* E2kContext wrappers */
+#define E_FOLDER_EXCHANGE_CONTEXT(efe) (exchange_account_get_context (((EFolderExchange *)efe)->priv->hier->account))
+#define E_FOLDER_EXCHANGE_URI(efe) (((EFolderExchange *)efe)->priv->internal_uri)
+
+/**
+ * e_folder_exchange_propfind:
+ * @folder: the folder
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @props: array of properties to find
+ * @nprops: length of @props
+ * @results: on return, the results
+ * @nresults: length of @results
+ *
+ * Performs a PROPFIND operation on @folder. This is a convenience
+ * wrapper around e2k_context_propfind(), qv.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e_folder_exchange_propfind (EFolder *folder, E2kOperation *op,
+			    const gchar **props, gint nprops,
+			    E2kResult **results, gint *nresults)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), E2K_HTTP_MALFORMED);
+
+	return e2k_context_propfind (
+		E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+		E_FOLDER_EXCHANGE_URI (folder),
+		props, nprops, results, nresults);
+}
+
+/**
+ * e_folder_exchange_bpropfind_start:
+ * @folder: the folder
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @hrefs: array of URIs, relative to @folder
+ * @nhrefs: length of @hrefs
+ * @props: array of properties to find
+ * @nprops: length of @props
+ *
+ * Begins a BPROPFIND (bulk PROPFIND) operation on @folder for @hrefs.
+ * This is a convenience wrapper around e2k_context_bpropfind_start(),
+ * qv.
+ *
+ * Return value: an iterator for getting the results
+ **/
+E2kResultIter *
+e_folder_exchange_bpropfind_start (EFolder *folder, E2kOperation *op,
+				   const gchar **hrefs, gint nhrefs,
+				   const gchar **props, gint nprops)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return e2k_context_bpropfind_start (
+		E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+		E_FOLDER_EXCHANGE_URI (folder),
+		hrefs, nhrefs, props, nprops);
+}
+
+/**
+ * e_folder_exchange_search_start:
+ * @folder: the folder
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @props: the properties to search for
+ * @nprops: size of @props array
+ * @rn: the search restriction
+ * @orderby: if non-%NULL, the field to sort the search results by
+ * @ascending: %TRUE for an ascending search, %FALSE for descending.
+ *
+ * Begins a SEARCH on the contents of @folder. This is a convenience
+ * wrapper around e2k_context_search_start(), qv.
+ *
+ * Return value: an iterator for returning the search results
+ **/
+E2kResultIter *
+e_folder_exchange_search_start (EFolder *folder, E2kOperation *op,
+				const gchar **props, gint nprops,
+				E2kRestriction *rn, const gchar *orderby,
+				gboolean ascending)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return e2k_context_search_start (
+		E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+		E_FOLDER_EXCHANGE_URI (folder),
+		props, nprops, rn, orderby, ascending);
+}
+
+/**
+ * e_folder_exchange_subscribe:
+ * @folder: the folder to subscribe to notifications on
+ * @type: the type of notification to subscribe to
+ * @min_interval: the minimum interval (in seconds) between
+ * notifications.
+ * @callback: the callback to call when a notification has been
+ * received
+ * @user_data: data to pass to @callback.
+ *
+ * This subscribes to change notifications of the given @type on
+ * @folder. This is a convenience wrapper around
+ * e2k_context_subscribe(), qv.
+ **/
+void
+e_folder_exchange_subscribe (EFolder *folder,
+			     E2kContextChangeType type, gint min_interval,
+			     E2kContextChangeCallback callback,
+			     gpointer user_data)
+{
+	g_return_if_fail (E_IS_FOLDER_EXCHANGE (folder));
+
+	e2k_context_subscribe (E_FOLDER_EXCHANGE_CONTEXT (folder),
+			       E_FOLDER_EXCHANGE_URI (folder),
+			       type, min_interval, callback, user_data);
+}
+
+/**
+ * e_folder_exchange_unsubscribe:
+ * @folder: the folder to unsubscribe from
+ *
+ * Unsubscribes to all notifications on @folder. This is a convenience
+ * wrapper around e2k_context_unsubscribe(), qv.
+ **/
+void
+e_folder_exchange_unsubscribe (EFolder *folder)
+{
+	E2kContext *ctx;
+
+	g_return_if_fail (E_IS_FOLDER_EXCHANGE (folder));
+
+	/* FIXME : This is a hack as of now. The free_folder in mail-stub
+	gets called when we are in offline and the context is NULL then. */
+	ctx = E_FOLDER_EXCHANGE_CONTEXT (folder);
+	if (ctx) {
+		e2k_context_unsubscribe (E_FOLDER_EXCHANGE_CONTEXT (folder),
+					 E_FOLDER_EXCHANGE_URI (folder));
+	}
+}
+
+/**
+ * e_folder_exchange_transfer_start:
+ * @source: the source folder
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @dest: the destination folder
+ * @source_hrefs: an array of hrefs to move, relative to @source_folder
+ * @delete_originals: whether or not to delete the original objects
+ *
+ * Starts a BMOVE or BCOPY (depending on @delete_originals) operation
+ * on @source. This is a convenience wrapper around
+ * e2k_context_transfer_start(), qv.
+ *
+ * Return value: the iterator for the results
+ **/
+E2kResultIter *
+e_folder_exchange_transfer_start (EFolder *source, E2kOperation *op,
+				  EFolder *dest, GPtrArray *source_hrefs,
+				  gboolean delete_originals)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (source), NULL);
+
+	return e2k_context_transfer_start (E_FOLDER_EXCHANGE_CONTEXT (source), op,
+					   E_FOLDER_EXCHANGE_URI (source),
+					   E_FOLDER_EXCHANGE_URI (dest),
+					   source_hrefs, delete_originals);
+}
+
+/**
+ * e_folder_exchange_put_new:
+ * @folder: the folder to PUT the new item into
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @object_name: base name of the new object (not URI-encoded)
+ * @test_callback: callback to use to test possible object URIs
+ * @user_data: data for @test_callback
+ * @content_type: MIME Content-Type of the data
+ * @body: data to PUT
+ * @length: length of @body
+ * @location: if not %NULL, will contain the Location of the POSTed
+ * object on return
+ * @repl_uid: if not %NULL, will contain the Repl-UID of the POSTed
+ * object on return
+ *
+ * PUTs data into @folder with a new name based on @object_name. This
+ * is a convenience wrapper around e2k_context_put_new(), qv.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e_folder_exchange_put_new (EFolder *folder,
+			   E2kOperation *op,
+			   const gchar *object_name,
+			   E2kContextTestCallback test_callback,
+			   gpointer user_data,
+			   const gchar *content_type,
+			   const gchar *body, gint length,
+			   gchar **location, gchar **repl_uid)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), E2K_HTTP_MALFORMED);
+
+	return e2k_context_put_new (E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+				    E_FOLDER_EXCHANGE_URI (folder),
+				    object_name, test_callback, user_data,
+				    content_type, body, length,
+				    location, repl_uid);
+}
+
+/**
+ * e_folder_exchange_proppatch_new:
+ * @folder: the folder to PROPPATCH a new object in
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @object_name: base name of the new object (not URI-encoded)
+ * @test_callback: callback to use to test possible object URIs
+ * @user_data: data for @test_callback
+ * @props: the properties to set/remove
+ * @location: if not %NULL, will contain the Location of the
+ * PROPPATCHed object on return
+ * @repl_uid: if not %NULL, will contain the Repl-UID of the
+ * PROPPATCHed object on return
+ *
+ * PROPPATCHes data into @folder with a new name based on
+ * @object_name. This is a convenience wrapper around
+ * e2k_context_proppatch_new(), qv.
+
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e_folder_exchange_proppatch_new (EFolder *folder, E2kOperation *op,
+				 const gchar *object_name,
+				 E2kContextTestCallback test_callback,
+				 gpointer user_data,
+				 E2kProperties *props,
+				 gchar **location, gchar **repl_uid)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), E2K_HTTP_MALFORMED);
+
+	return e2k_context_proppatch_new (E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+					  E_FOLDER_EXCHANGE_URI (folder),
+					  object_name,
+					  test_callback, user_data,
+					  props,
+					  location, repl_uid);
+}
+
+/**
+ * e_folder_exchange_bproppatch_start:
+ * @folder: the folder
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @hrefs: array of URIs, relative to @folder
+ * @nhrefs: length of @hrefs
+ * @props: the properties to set/remove
+ * @create: whether or not to create the objects if they do not exist
+ *
+ * Begins BPROPPATCHing @hrefs under @folder. This is a convenience
+ * wrapper around e2k_context_bproppatch_start(), qv.
+ *
+ * Return value: an iterator for getting the results of the BPROPPATCH
+ **/
+E2kResultIter *
+e_folder_exchange_bproppatch_start (EFolder *folder, E2kOperation *op,
+				    const gchar **hrefs, gint nhrefs,
+				    E2kProperties *props, gboolean create)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return e2k_context_bproppatch_start (E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+					     E_FOLDER_EXCHANGE_URI (folder),
+					     hrefs, nhrefs, props, create);
+}
+
+/**
+ * e_folder_exchange_bdelete_start:
+ * @folder: the folder
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @hrefs: array of URIs, relative to @folder, to delete
+ * @nhrefs: length of @hrefs
+ *
+ * Begins a BDELETE (bulk DELETE) operation in @folder for @hrefs.
+ * This is a convenience wrapper around e2k_context_bdelete_start(),
+ * qv.
+ *
+ * Return value: an iterator for returning the results
+ **/
+E2kResultIter *
+e_folder_exchange_bdelete_start (EFolder *folder, E2kOperation *op,
+				 const gchar **hrefs, gint nhrefs)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), NULL);
+
+	return e2k_context_bdelete_start (E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+					  E_FOLDER_EXCHANGE_URI (folder),
+					  hrefs, nhrefs);
+}
+
+/**
+ * e_folder_exchange_mkcol:
+ * @folder: the folder to create
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @props: properties to set on the new folder, or %NULL
+ * @permanent_url: if not %NULL, will contain the permanent URL of the
+ * new folder on return
+ *
+ * Performs a MKCOL operation to create @folder, with optional
+ * additional properties. This is a convenience wrapper around
+ * e2k_context_mkcol(), qv.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e_folder_exchange_mkcol (EFolder *folder, E2kOperation *op,
+			 E2kProperties *props,
+			 gchar **permanent_url)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), E2K_HTTP_MALFORMED);
+
+	return e2k_context_mkcol (E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+				  E_FOLDER_EXCHANGE_URI (folder),
+				  props, permanent_url);
+}
+
+/**
+ * e_folder_exchange_delete:
+ * @folder: the folder to delete
+ * @op: pointer to an #E2kOperation to use for cancellation
+ *
+ * Attempts to DELETE @folder. This is a convenience wrapper around
+ * e2k_context_delete(), qv.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e_folder_exchange_delete (EFolder *folder, E2kOperation *op)
+{
+	ExchangeHierarchy *hier;
+	const gchar *folder_type, *physical_uri;
+
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), E2K_HTTP_MALFORMED);
+	/* remove ESources */
+	hier = e_folder_exchange_get_hierarchy (folder);
+
+	if (hier->type == EXCHANGE_HIERARCHY_PERSONAL ||
+	    hier->type == EXCHANGE_HIERARCHY_FAVORITES) {
+		folder_type = e_folder_get_type_string (folder);
+		physical_uri = e_folder_get_physical_uri (folder);
+
+		if ((strcmp (folder_type, "calendar") == 0) ||
+		    (strcmp (folder_type, "calendar/public") == 0)) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_CALENDAR_FOLDER,
+					       physical_uri);
+		}
+		else if ((strcmp (folder_type, "tasks") == 0) ||
+			 (strcmp (folder_type, "tasks/public") == 0)) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_TASKS_FOLDER,
+					       physical_uri);
+		}
+		else if ((strcmp (folder_type, "contacts") == 0) ||
+			 (strcmp (folder_type, "contacts/public") == 0)) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_CONTACTS_FOLDER,
+					       physical_uri);
+		}
+	}
+
+	return e2k_context_delete (E_FOLDER_EXCHANGE_CONTEXT (folder), op,
+				   E_FOLDER_EXCHANGE_URI (folder));
+}
+
+/**
+ * e_folder_exchange_transfer_dir:
+ * @source: source folder
+ * @op: pointer to an #E2kOperation to use for cancellation
+ * @dest: destination folder
+ * @delete_original: whether or not to delete the original folder
+ * @permanent_url: if not %NULL, will contain the permanent URL of the
+ * new folder on return
+ *
+ * Performs a MOVE or COPY (depending on @delete_original) operation
+ * on @source. This is a convenience wrapper around
+ * e2k_context_transfer_dir(), qv.
+ *
+ * Return value: the HTTP status
+ **/
+E2kHTTPStatus
+e_folder_exchange_transfer_dir (EFolder *source, E2kOperation *op,
+				EFolder *dest, gboolean delete_original,
+				gchar **permanent_url)
+{
+	g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (source), E2K_HTTP_MALFORMED);
+
+	return e2k_context_transfer_dir (E_FOLDER_EXCHANGE_CONTEXT (source), op,
+					 E_FOLDER_EXCHANGE_URI (source),
+					 E_FOLDER_EXCHANGE_URI (dest),
+					 delete_original, permanent_url);
+}
diff --git a/server/storage/e-folder-exchange.h b/server/storage/e-folder-exchange.h
new file mode 100644
index 0000000..975270e
--- /dev/null
+++ b/server/storage/e-folder-exchange.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E_FOLDER_EXCHANGE_H__
+#define __E_FOLDER_EXCHANGE_H__
+
+#include "e-folder.h"
+#include "exchange-types.h"
+#include "e2k-context.h"
+
+G_BEGIN_DECLS
+
+#define E_TYPE_FOLDER_EXCHANGE            (e_folder_exchange_get_type ())
+#define E_FOLDER_EXCHANGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_FOLDER_EXCHANGE, EFolderExchange))
+#define E_FOLDER_EXCHANGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_FOLDER_EXCHANGE, EFolderExchangeClass))
+#define E_IS_FOLDER_EXCHANGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_FOLDER_EXCHANGE))
+#define E_IS_FOLDER_EXCHANGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_FOLDER_EXCHANGE))
+
+struct _EFolderExchange {
+	EFolder parent;
+
+	EFolderExchangePrivate *priv;
+};
+
+struct _EFolderExchangeClass {
+	EFolderClass parent_class;
+
+};
+
+GType       e_folder_exchange_get_type      (void);
+
+EFolder    *e_folder_exchange_new           (ExchangeHierarchy     *hier,
+					     const gchar            *name,
+					     const gchar            *type,
+					     const gchar            *outlook_class,
+					     const gchar            *phys_uri,
+					     const gchar            *int_uri);
+
+EFolder    *e_folder_exchange_new_from_file (ExchangeHierarchy     *hier,
+					     const gchar            *filename);
+gboolean    e_folder_exchange_save_to_file  (EFolder               *folder,
+					     const gchar            *filename);
+
+const gchar *e_folder_exchange_get_internal_uri     (EFolder    *folder);
+void        e_folder_exchange_set_internal_uri     (EFolder    *folder,
+						    const gchar *internal_uri);
+
+const gchar *e_folder_exchange_get_path             (EFolder    *folder);
+
+const gchar *e_folder_exchange_get_permanent_uri    (EFolder    *folder);
+void        e_folder_exchange_set_permanent_uri    (EFolder    *folder,
+						    const gchar *permanent_uri);
+
+gint64	e_folder_exchange_get_folder_size (EFolder *folder);
+void		e_folder_exchange_set_folder_size (EFolder *folder, gint64 folder_size);
+
+gboolean    e_folder_exchange_get_has_subfolders   (EFolder    *folder);
+void        e_folder_exchange_set_has_subfolders   (EFolder    *folder,
+						    gboolean   has_subfolders);
+
+gboolean    e_folder_exchange_get_rescan_tree   (EFolder    *folder);
+void        e_folder_exchange_set_rescan_tree   (EFolder    *folder,
+						 gboolean   has_subfolders);
+
+const gchar *e_folder_exchange_get_outlook_class    (EFolder    *folder);
+
+gchar       *e_folder_exchange_get_storage_file     (EFolder    *folder,
+						    const gchar *filename);
+
+ExchangeHierarchy *e_folder_exchange_get_hierarchy (EFolder    *folder);
+
+/* E2kContext wrappers */
+E2kHTTPStatus  e_folder_exchange_propfind          (EFolder *folder,
+						    E2kOperation *op,
+						    const gchar **props,
+						    gint nprops,
+						    E2kResult **results,
+						    gint *nresults);
+E2kResultIter *e_folder_exchange_bpropfind_start   (EFolder *folder,
+						    E2kOperation *op,
+						    const gchar **hrefs,
+						    gint nhrefs,
+						    const gchar **props,
+						    gint nprops);
+
+E2kResultIter *e_folder_exchange_search_start      (EFolder *folder,
+						    E2kOperation *op,
+						    const gchar **props,
+						    gint nprops,
+						    E2kRestriction *rn,
+						    const gchar *orderby,
+						    gboolean ascending);
+
+void           e_folder_exchange_subscribe         (EFolder *folder,
+						    E2kContextChangeType,
+						    gint min_interval,
+						    E2kContextChangeCallback,
+						    gpointer user_data);
+void           e_folder_exchange_unsubscribe       (EFolder *folder);
+
+E2kResultIter *e_folder_exchange_transfer_start    (EFolder *source,
+						    E2kOperation *op,
+						    EFolder *dest,
+						    GPtrArray *source_hrefs,
+						    gboolean delete_originals);
+
+E2kHTTPStatus  e_folder_exchange_put_new           (EFolder *folder,
+						    E2kOperation *op,
+						    const gchar *object_name,
+						    E2kContextTestCallback,
+						    gpointer user_data,
+						    const gchar *content_type,
+						    const gchar *body,
+						    gint length,
+						    gchar **location,
+						    gchar **repl_uid);
+
+E2kHTTPStatus  e_folder_exchange_proppatch_new     (EFolder *folder,
+						    E2kOperation *op,
+						    const gchar *object_name,
+						    E2kContextTestCallback,
+						    gpointer user_data,
+						    E2kProperties *props,
+						    gchar **location,
+						    gchar **repl_uid);
+
+E2kResultIter *e_folder_exchange_bproppatch_start  (EFolder *folder,
+						    E2kOperation *op,
+						    const gchar **hrefs,
+						    gint nhrefs,
+						    E2kProperties *props,
+						    gboolean create);
+
+E2kResultIter *e_folder_exchange_bdelete_start     (EFolder *folder,
+						    E2kOperation *op,
+						    const gchar **hrefs,
+						    gint nhrefs);
+
+E2kHTTPStatus  e_folder_exchange_mkcol             (EFolder *folder,
+						    E2kOperation *op,
+						    E2kProperties *props,
+						    gchar **permanent_url);
+E2kHTTPStatus  e_folder_exchange_delete            (EFolder *folder,
+						    E2kOperation *op);
+E2kHTTPStatus  e_folder_exchange_transfer_dir      (EFolder *source,
+						    E2kOperation *op,
+						    EFolder *dest,
+						    gboolean delete_original,
+						    gchar **permanent_url);
+
+G_END_DECLS
+
+#endif /* __E_FOLDER_EXCHANGE_H__ */
diff --git a/server/storage/e-folder-tree.c b/server/storage/e-folder-tree.c
new file mode 100644
index 0000000..1fa2491
--- /dev/null
+++ b/server/storage/e-folder-tree.c
@@ -0,0 +1,449 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder-set.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-folder-tree.h"
+
+#include <string.h>
+#include <glib.h>
+
+struct Folder {
+	struct Folder *parent;
+	gchar *path;
+	gpointer data;
+	GList *subfolders;
+};
+typedef struct Folder Folder;
+
+struct EFolderTree {
+	GHashTable *path_to_folder;
+	GHashTable *data_to_path;
+
+	EFolderDestroyNotify folder_destroy_notify;
+	gpointer folder_destroy_notify_closure;
+};
+
+/* Utility functions.  */
+
+static gchar *
+get_parent_path (const gchar *path)
+{
+	const gchar *last_separator;
+
+	g_assert (g_path_is_absolute (path));
+
+	last_separator = strrchr (path, '/');
+
+	if (last_separator == path)
+		return g_strdup ("/");
+
+	return g_strndup (path, last_separator - path);
+}
+
+static void
+traverse_subtree (EFolderTree *tree,
+		  Folder *root_folder,
+		  EFolderTreeForeachFunc foreach_func,
+		  gpointer data)
+{
+	GList *p;
+
+	g_assert (foreach_func != NULL);
+
+	(* foreach_func) (tree, root_folder->path, root_folder->data, data);
+
+	for (p = root_folder->subfolders; p != NULL; p = p->next) {
+		Folder *folder;
+
+		folder = (Folder *) p->data;
+		traverse_subtree (tree, folder, foreach_func, data);
+	}
+}
+
+/* Folder handling.  */
+
+static Folder *
+folder_new (const gchar *path,
+	    gpointer data)
+{
+	Folder *folder;
+
+	folder = g_new0 (Folder, 1);
+	folder->path       = g_strdup (path);
+	folder->data       = data;
+
+	return folder;
+}
+
+static void
+folder_remove_subfolder (Folder *folder,
+			 Folder *subfolder)
+{
+	folder->subfolders = g_list_remove (folder->subfolders, subfolder);
+	subfolder->parent = NULL;
+}
+
+static void
+folder_add_subfolder (Folder *folder,
+		      Folder *subfolder)
+{
+	folder->subfolders = g_list_prepend (folder->subfolders, subfolder);
+	subfolder->parent = folder;
+}
+
+static void
+folder_destroy (Folder *folder)
+{
+	g_assert (folder->subfolders == NULL);
+
+	if (folder->parent != NULL)
+		folder_remove_subfolder (folder->parent, folder);
+
+	g_free (folder->path);
+
+	g_free (folder);
+}
+
+static void
+remove_folder (EFolderTree *folder_tree,
+	       Folder *folder)
+{
+	if (folder->subfolders != NULL) {
+		GList *p;
+
+		for (p = folder->subfolders; p != NULL; p = p->next) {
+			Folder *subfolder;
+
+			subfolder = (Folder *) p->data;
+			subfolder->parent = NULL;
+			remove_folder (folder_tree, subfolder);
+		}
+
+		g_list_free (folder->subfolders);
+		folder->subfolders = NULL;
+	}
+
+	g_hash_table_remove (folder_tree->path_to_folder, folder->path);
+	g_hash_table_remove (folder_tree->data_to_path, folder->data);
+
+	if (folder_tree->folder_destroy_notify != NULL)
+		(* folder_tree->folder_destroy_notify) (folder_tree,
+							folder->path,
+							folder->data,
+							folder_tree->folder_destroy_notify_closure);
+
+	folder_destroy (folder);
+}
+
+/**
+ * e_folder_tree_new:
+ * @folder_destroy_notify: Function to be called when a folder gets removed from the tree
+ * @closure: Additional data to pass to @folder_destroy_notify
+ *
+ * Create a new EFolderTree.
+ *
+ * Return value: A pointer to the newly created EFolderTree.
+ **/
+EFolderTree *
+e_folder_tree_new (EFolderDestroyNotify folder_destroy_notify,
+		   gpointer closure)
+{
+	EFolderTree *new;
+
+	new = g_new0 (EFolderTree, 1);
+
+	new->folder_destroy_notify         = folder_destroy_notify;
+	new->folder_destroy_notify_closure = closure;
+
+	new->path_to_folder = g_hash_table_new (g_str_hash, g_str_equal);
+	new->data_to_path = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+	e_folder_tree_add (new, "/", NULL);
+
+	return new;
+}
+
+/**
+ * e_folder_tree_destroy:
+ * @folder_tree: A pointer to an EFolderTree
+ *
+ * Destroy @folder_tree.
+ **/
+void
+e_folder_tree_destroy (EFolderTree *folder_tree)
+{
+	Folder *root_folder;
+
+	g_return_if_fail (folder_tree != NULL);
+
+	root_folder = g_hash_table_lookup (folder_tree->path_to_folder, "/");
+	remove_folder (folder_tree, root_folder);
+
+	g_hash_table_destroy (folder_tree->path_to_folder);
+	g_hash_table_destroy (folder_tree->data_to_path);
+
+	g_free (folder_tree);
+}
+
+/**
+ * e_folder_tree_add:
+ * @folder_tree: A pointer to an EFolderTree
+ * @path: Path at which the new folder must be added
+ * @data: Data associated with the new folder
+ *
+ * Insert a new folder at @path, with the specified @data.
+ *
+ * Return value: %TRUE if successful, %FALSE if failed.
+ **/
+gboolean
+e_folder_tree_add (EFolderTree *folder_tree,
+		   const gchar *path,
+		   gpointer data)
+{
+	Folder *parent_folder;
+	Folder *folder;
+	const gchar *existing_path;
+	gchar *parent_path;
+
+	g_return_val_if_fail (folder_tree != NULL, FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (g_path_is_absolute (path), FALSE);
+
+	/* Can only "add" a new root folder if the tree is empty */
+	if (! strcmp (path, "/")) {
+		folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
+		if (folder) {
+			if (folder->subfolders) {
+				g_warning ("e_folder_tree_add() -- Trying to change root folder after adding children");
+				return FALSE;
+			}
+			remove_folder (folder_tree, folder);
+		}
+
+		folder = folder_new (path, data);
+		g_hash_table_insert (folder_tree->path_to_folder, folder->path, folder);
+		g_hash_table_insert (folder_tree->data_to_path, data, folder->path);
+		return TRUE;
+	}
+
+	parent_path = get_parent_path (path);
+
+	parent_folder = g_hash_table_lookup (folder_tree->path_to_folder, parent_path);
+	if (parent_folder == NULL) {
+		g_warning ("e_folder_tree_add() -- Trying to add a subfolder to a path that does not exist yet -- %s",
+			   parent_path);
+		g_free (parent_path);
+		return FALSE;
+	}
+	g_free (parent_path);
+
+	folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
+	if (folder != NULL) {
+		g_warning ("e_folder_tree_add() -- Trying to add a subfolder for a path that already exists -- %s",
+			   path);
+		return FALSE;
+	}
+
+	existing_path = g_hash_table_lookup (folder_tree->data_to_path, data);
+	if (existing_path != NULL) {
+		g_warning ("e_folder_tree_add() -- Trying to add a folder with duplicate data -- %s",
+			   path);
+		return FALSE;
+	}
+
+	folder = folder_new (path, data);
+	folder_add_subfolder (parent_folder, folder);
+
+	g_hash_table_insert (folder_tree->path_to_folder, folder->path, folder);
+	g_hash_table_insert (folder_tree->data_to_path, data, folder->path);
+
+	return TRUE;
+}
+
+/**
+ * e_folder_tree_remove:
+ * @folder_tree: A pointer to an EFolderTree
+ * @path: Path of the folder to remove
+ *
+ * Remove the folder at @path from @folder_tree.
+ *
+ * Return value: %TRUE if successful, %FALSE if failed.
+ **/
+gboolean
+e_folder_tree_remove (EFolderTree *folder_tree,
+		      const gchar *path)
+{
+	Folder *folder;
+
+	g_return_val_if_fail (folder_tree != NULL, FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (g_path_is_absolute (path), FALSE);
+
+	folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
+	if (folder == NULL)
+		return FALSE;
+
+	remove_folder (folder_tree, folder);
+	return TRUE;
+}
+
+static void
+count_nodes (EFolderTree *tree,
+	     const gchar *path,
+	     gpointer data,
+	     gpointer closure)
+{
+	gint *count = closure;
+
+	(*count)++;
+}
+
+/**
+ * e_folder_tree_get_count:
+ * @folder_tree: A pointer to an EFolderTree
+ *
+ * Gets the number of folders in the tree
+ *
+ * Return value: The number of folders in the tree
+ **/
+gint
+e_folder_tree_get_count (EFolderTree *folder_tree)
+{
+	gint count = 0;
+
+	e_folder_tree_foreach (folder_tree, count_nodes, &count);
+
+	return count;
+}
+
+/**
+ * e_folder_tree_get_folder:
+ * @folder_tree: A pointer to an EFolderTree
+ * @path: Path of the folder for which we want to get the data
+ *
+ * Get the data for the folder at @path.
+ *
+ * Return value: The pointer to the data for the folder at @path.
+ **/
+gpointer
+e_folder_tree_get_folder (EFolderTree *folder_tree,
+			  const gchar *path)
+{
+	Folder *folder;
+
+	g_return_val_if_fail (folder_tree != NULL, NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+	g_return_val_if_fail (g_path_is_absolute (path), NULL);
+
+	folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
+	if (folder == NULL)
+		return NULL;
+
+	return folder->data;
+}
+
+/**
+ * e_folder_tree_get_subfolders:
+ * @folder_tree: A pointer to an EFolderTree
+ * @path: A path in @folder_tree
+ *
+ * Get a list of the paths of the subfolders of @path.
+ *
+ * Return value: A list of pointers to the paths of the subfolders.  The list
+ * and the strings must be freed by the caller.
+ **/
+GList *
+e_folder_tree_get_subfolders (EFolderTree *folder_tree,
+			      const gchar *path)
+{
+	Folder *folder;
+	GList *list;
+	GList *p;
+
+	g_return_val_if_fail (folder_tree != NULL, NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+	g_return_val_if_fail (g_path_is_absolute (path), NULL);
+
+	folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
+	if (folder == NULL)
+		return NULL;
+
+	list = NULL;
+	for (p = folder->subfolders; p != NULL; p = p->next) {
+		const Folder *folder;
+
+		folder = (const Folder *) p->data;
+		list = g_list_prepend (list, g_strdup (folder->path));
+	}
+
+	return list;
+}
+
+/**
+ * e_folder_tree_foreach:
+ * @folder_tree:
+ * @foreach_func:
+ * @data:
+ *
+ * Call @foreach_func with the specified @data for all the folders
+ * in @folder_tree, starting at the root node.
+ **/
+void
+e_folder_tree_foreach (EFolderTree *folder_tree,
+		       EFolderTreeForeachFunc foreach_func,
+		       gpointer data)
+{
+	Folder *root_node;
+
+	g_return_if_fail (folder_tree != NULL);
+	g_return_if_fail (foreach_func != NULL);
+
+	root_node = g_hash_table_lookup (folder_tree->path_to_folder, "/");
+	if (root_node == NULL) {
+		g_warning ("e_folder_tree_foreach -- What?!  No root node!?");
+		return;
+	}
+
+	traverse_subtree (folder_tree, root_node, foreach_func, data);
+}
+
+/**
+ * e_folder_tree_get_path_for_data:
+ * @folder_tree: A pointer to an EFolderTree
+ * @data: The data for the folder for which the path is needed
+ *
+ * Look up the path for the specified @data.
+ *
+ * Return value: The path for the folder that holds that @data.
+ **/
+const gchar *
+e_folder_tree_get_path_for_data  (EFolderTree *folder_tree,
+				  gconstpointer data)
+{
+	g_return_val_if_fail (folder_tree != NULL, NULL);
+	g_return_val_if_fail (data != NULL, NULL);
+
+	return (const gchar *) g_hash_table_lookup (folder_tree->data_to_path, data);
+}
diff --git a/server/storage/e-folder-tree.h b/server/storage/e-folder-tree.h
new file mode 100644
index 0000000..f41de66
--- /dev/null
+++ b/server/storage/e-folder-tree.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder-tree.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifndef _E_FOLDER_TREE_H_
+#define _E_FOLDER_TREE_H_
+
+#include <glib.h>
+
+typedef struct EFolderTree EFolderTree;
+
+typedef void (* EFolderDestroyNotify)   (EFolderTree *tree, const gchar *path, gpointer data, gpointer closure);
+typedef void (* EFolderTreeForeachFunc) (EFolderTree *tree, const gchar *path, gpointer data, gpointer closure);
+
+EFolderTree *e_folder_tree_new               (EFolderDestroyNotify    folder_destroy_notify,
+					      void                   *closure);
+
+void        e_folder_tree_destroy            (EFolderTree            *folder_tree);
+
+gboolean    e_folder_tree_add                (EFolderTree            *folder_tree,
+					      const gchar             *path,
+					      void                   *data);
+gboolean    e_folder_tree_remove             (EFolderTree            *folder_tree,
+					      const gchar             *path);
+
+gint         e_folder_tree_get_count          (EFolderTree            *folder_tree);
+
+void       *e_folder_tree_get_folder         (EFolderTree            *folder_tree,
+					      const gchar             *path);
+GList      *e_folder_tree_get_subfolders     (EFolderTree            *folder_tree,
+					      const gchar             *path);
+
+void        e_folder_tree_foreach            (EFolderTree            *folder_tree,
+					      EFolderTreeForeachFunc  foreach_func,
+					      void                   *data);
+
+const gchar *e_folder_tree_get_path_for_data  (EFolderTree            *folder_tree,
+					      const void             *data);
+
+#endif /* _E_FOLDER_TREE_H_ */
diff --git a/server/storage/e-folder-type-registry.c b/server/storage/e-folder-type-registry.c
new file mode 100644
index 0000000..8f3f03a
--- /dev/null
+++ b/server/storage/e-folder-type-registry.c
@@ -0,0 +1,409 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder-type-registry.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-folder-type-registry.h"
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (EFolderTypeRegistry, e_folder_type_registry, G_TYPE_OBJECT)
+
+typedef struct {
+	gchar *name;
+	gchar *icon_name;
+
+	gchar *display_name;
+	gchar *description;
+
+	gboolean user_creatable;
+
+	GList *accepted_dnd_types; /* gchar * */
+
+	GObject *handler;
+
+} FolderType;
+
+struct EFolderTypeRegistryPrivate {
+	GHashTable *name_to_type;
+};
+
+/* FolderType handling.  */
+
+static FolderType *
+folder_type_new (const gchar *name,
+		 const gchar *icon_name,
+		 const gchar *display_name,
+		 const gchar *description,
+		 gboolean user_creatable,
+		 gint num_accepted_dnd_types,
+		 const gchar **accepted_dnd_types)
+{
+	FolderType *new;
+	gint i;
+
+	new = g_new0 (FolderType, 1);
+
+	new->name           = g_strdup (name);
+	new->icon_name      = g_strdup (icon_name);
+	new->display_name   = g_strdup (display_name);
+	new->description    = g_strdup (description);
+
+	new->user_creatable = user_creatable;
+
+	new->accepted_dnd_types = NULL;
+	for (i = 0; i < num_accepted_dnd_types; i++)
+		new->accepted_dnd_types = g_list_prepend (new->accepted_dnd_types,
+							  g_strdup (accepted_dnd_types[i]));
+	new->accepted_dnd_types = g_list_reverse (new->accepted_dnd_types);
+
+	new->handler = NULL;
+
+	return new;
+}
+
+static void
+folder_type_free (FolderType *folder_type)
+{
+	g_free (folder_type->name);
+	g_free (folder_type->icon_name);
+	g_free (folder_type->display_name);
+	g_free (folder_type->description);
+
+	if (folder_type->handler != NULL)
+		g_object_unref (folder_type->handler);
+
+	g_free (folder_type);
+}
+
+static FolderType *
+get_folder_type (EFolderTypeRegistry *folder_type_registry,
+		 const gchar *type_name)
+{
+	EFolderTypeRegistryPrivate *priv;
+
+	priv = folder_type_registry->priv;
+
+	return g_hash_table_lookup (priv->name_to_type, type_name);
+}
+
+static gboolean
+register_folder_type (EFolderTypeRegistry *folder_type_registry,
+		      const gchar *name,
+		      const gchar *icon_name,
+		      const gchar *display_name,
+		      const gchar *description,
+		      gboolean user_creatable,
+		      gint num_accepted_dnd_types,
+		      const gchar **accepted_dnd_types)
+{
+	EFolderTypeRegistryPrivate *priv;
+	FolderType *folder_type;
+
+	priv = folder_type_registry->priv;
+
+	/* Make sure we don't add the same type twice.  */
+	if (get_folder_type (folder_type_registry, name) != NULL)
+		return FALSE;
+
+	folder_type = folder_type_new (name, icon_name,
+				       display_name, description,
+				       user_creatable,
+				       num_accepted_dnd_types, accepted_dnd_types);
+	g_hash_table_insert (priv->name_to_type, folder_type->name, folder_type);
+
+	return TRUE;
+}
+
+static gboolean
+set_handler (EFolderTypeRegistry *folder_type_registry,
+	     const gchar *name,
+	     GObject *handler)
+{
+	EFolderTypeRegistryPrivate *priv;
+	FolderType *folder_type;
+
+	priv = folder_type_registry->priv;
+
+	folder_type = get_folder_type (folder_type_registry, name);
+	if (folder_type == NULL)
+		return FALSE;
+	if (folder_type->handler != NULL)
+		return FALSE;
+
+	g_object_ref (handler);
+	folder_type->handler = handler;
+
+	return TRUE;
+}
+
+/* GObject methods.  */
+
+static void
+hash_forall_free_folder_type (gpointer key,
+			      gpointer value,
+			      gpointer data)
+{
+	FolderType *folder_type;
+
+	folder_type = (FolderType *) value;
+	folder_type_free (folder_type);
+}
+
+static void
+impl_finalize (GObject *object)
+{
+	EFolderTypeRegistry *folder_type_registry;
+	EFolderTypeRegistryPrivate *priv;
+
+	folder_type_registry = E_FOLDER_TYPE_REGISTRY (object);
+	priv = folder_type_registry->priv;
+
+	g_hash_table_foreach (priv->name_to_type, hash_forall_free_folder_type, NULL);
+	g_hash_table_destroy (priv->name_to_type);
+
+	g_free (priv);
+
+	(* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+e_folder_type_registry_class_init (EFolderTypeRegistryClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = impl_finalize;
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+}
+
+static void
+e_folder_type_registry_init (EFolderTypeRegistry *folder_type_registry)
+{
+	EFolderTypeRegistryPrivate *priv;
+
+	priv = g_new0 (EFolderTypeRegistryPrivate, 1);
+	priv->name_to_type = g_hash_table_new (g_str_hash, g_str_equal);
+
+	folder_type_registry->priv = priv;
+}
+
+EFolderTypeRegistry *
+e_folder_type_registry_new (void)
+{
+	return g_object_new (E_TYPE_FOLDER_TYPE_REGISTRY, NULL);
+}
+
+gboolean
+e_folder_type_registry_register_type (EFolderTypeRegistry *folder_type_registry,
+				      const gchar *type_name,
+				      const gchar *icon_name,
+				      const gchar *display_name,
+				      const gchar *description,
+				      gboolean user_creatable,
+				      gint num_accepted_dnd_types,
+				      const gchar **accepted_dnd_types)
+{
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), FALSE);
+	g_return_val_if_fail (type_name != NULL, FALSE);
+	g_return_val_if_fail (icon_name != NULL, FALSE);
+
+	return register_folder_type (folder_type_registry, type_name, icon_name,
+				     display_name, description, user_creatable,
+				     num_accepted_dnd_types, accepted_dnd_types);
+}
+
+gboolean
+e_folder_type_registry_set_handler_for_type  (EFolderTypeRegistry *folder_type_registry,
+					      const gchar *type_name,
+					      GObject *handler)
+{
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), FALSE);
+	/* g_return_val_if_fail (EVOLUTION_IS_SHELL_COMPONENT_CLIENT (handler), FALSE); */
+
+	return set_handler (folder_type_registry, type_name, handler);
+}
+
+gboolean
+e_folder_type_registry_type_registered  (EFolderTypeRegistry *folder_type_registry,
+					 const gchar *type_name)
+{
+	EFolderTypeRegistryPrivate *priv;
+
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), FALSE);
+	g_return_val_if_fail (type_name != NULL, FALSE);
+
+	priv = folder_type_registry->priv;
+
+	if (get_folder_type (folder_type_registry, type_name) == NULL)
+		return FALSE;
+
+	return TRUE;
+}
+
+void
+e_folder_type_registry_unregister_type (EFolderTypeRegistry *folder_type_registry,
+					const gchar *type_name)
+{
+	EFolderTypeRegistryPrivate *priv;
+	FolderType *folder_type;
+
+	g_return_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry));
+	g_return_if_fail (type_name != NULL);
+
+	priv = folder_type_registry->priv;
+
+	folder_type = get_folder_type (folder_type_registry, type_name);
+	if (folder_type == NULL)
+		return;
+
+	g_hash_table_remove (priv->name_to_type, folder_type->name);
+	folder_type_free (folder_type);
+}
+
+static void
+get_type_names_hash_forall (gpointer key,
+			    gpointer value,
+			    gpointer data)
+{
+	GList **type_name_list;
+
+	type_name_list = (GList **) data;
+
+	*type_name_list = g_list_prepend (*type_name_list, g_strdup ((const gchar *) key));
+}
+
+GList *
+e_folder_type_registry_get_type_names (EFolderTypeRegistry *folder_type_registry)
+{
+	GList *type_name_list;
+	EFolderTypeRegistryPrivate *priv;
+
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), NULL);
+
+	priv = folder_type_registry->priv;
+
+	type_name_list = NULL;
+	g_hash_table_foreach (priv->name_to_type, get_type_names_hash_forall, &type_name_list);
+
+	return type_name_list;
+}
+
+const gchar *
+e_folder_type_registry_get_icon_name_for_type (EFolderTypeRegistry *folder_type_registry,
+					       const gchar *type_name)
+{
+	const FolderType *folder_type;
+
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), NULL);
+	g_return_val_if_fail (type_name != NULL, NULL);
+
+	folder_type = get_folder_type (folder_type_registry, type_name);
+	if (folder_type == NULL)
+		return NULL;
+
+	return folder_type->icon_name;
+}
+
+GObject *
+e_folder_type_registry_get_handler_for_type (EFolderTypeRegistry *folder_type_registry,
+					     const gchar *type_name)
+{
+	const FolderType *folder_type;
+
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), NULL);
+	g_return_val_if_fail (type_name != NULL, NULL);
+
+	folder_type = get_folder_type (folder_type_registry, type_name);
+	if (folder_type == NULL)
+		return NULL;
+
+	return folder_type->handler;
+}
+
+gboolean
+e_folder_type_registry_type_is_user_creatable  (EFolderTypeRegistry *folder_type_registry,
+						const gchar          *type_name)
+{
+	const FolderType *folder_type;
+
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), FALSE);
+	g_return_val_if_fail (type_name != NULL, FALSE);
+
+	folder_type = get_folder_type (folder_type_registry, type_name);
+	if (folder_type == NULL)
+		return FALSE;
+
+	return folder_type->user_creatable;
+}
+
+const gchar *
+e_folder_type_registry_get_display_name_for_type (EFolderTypeRegistry *folder_type_registry,
+						  const gchar *type_name)
+{
+	const FolderType *folder_type;
+
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), NULL);
+	g_return_val_if_fail (type_name != NULL, NULL);
+
+	folder_type = get_folder_type (folder_type_registry, type_name);
+	if (folder_type == NULL)
+		return NULL;
+
+	return folder_type->display_name;
+}
+
+const gchar *
+e_folder_type_registry_get_description_for_type (EFolderTypeRegistry *folder_type_registry,
+						 const gchar *type_name)
+{
+	const FolderType *folder_type;
+
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), NULL);
+	g_return_val_if_fail (type_name != NULL, NULL);
+
+	folder_type = get_folder_type (folder_type_registry, type_name);
+	if (folder_type == NULL)
+		return NULL;
+
+	return folder_type->description;
+}
+
+GList *
+e_folder_type_registry_get_accepted_dnd_types_for_type (EFolderTypeRegistry *folder_type_registry,
+							const gchar *type_name)
+{
+	const FolderType *folder_type;
+
+	g_return_val_if_fail (E_IS_FOLDER_TYPE_REGISTRY (folder_type_registry), NULL);
+	g_return_val_if_fail (type_name != NULL, NULL);
+
+	folder_type = get_folder_type (folder_type_registry, type_name);
+	if (folder_type == NULL)
+		return NULL;
+
+	return folder_type->accepted_dnd_types;
+}
diff --git a/server/storage/e-folder-type-registry.h b/server/storage/e-folder-type-registry.h
new file mode 100644
index 0000000..beede01
--- /dev/null
+++ b/server/storage/e-folder-type-registry.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder-type-registry.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifndef _E_FOLDER_TYPE_REGISTRY_H_
+#define _E_FOLDER_TYPE_REGISTRY_H_
+
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_FOLDER_TYPE_REGISTRY		(e_folder_type_registry_get_type ())
+#define E_FOLDER_TYPE_REGISTRY(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_FOLDER_TYPE_REGISTRY, EFolderTypeRegistry))
+#define E_FOLDER_TYPE_REGISTRY_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_FOLDER_TYPE_REGISTRY, EFolderTypeRegistryClass))
+#define E_IS_FOLDER_TYPE_REGISTRY(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_FOLDER_TYPE_REGISTRY))
+#define E_IS_FOLDER_TYPE_REGISTRY_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_FOLDER_TYPE_REGISTRY))
+
+typedef struct EFolderTypeRegistry        EFolderTypeRegistry;
+typedef struct EFolderTypeRegistryPrivate EFolderTypeRegistryPrivate;
+typedef struct EFolderTypeRegistryClass   EFolderTypeRegistryClass;
+
+struct EFolderTypeRegistry {
+	GObject parent;
+
+	EFolderTypeRegistryPrivate *priv;
+};
+
+struct EFolderTypeRegistryClass {
+	GObjectClass parent_class;
+};
+
+GType                e_folder_type_registry_get_type   (void);
+EFolderTypeRegistry *e_folder_type_registry_new        (void);
+
+gboolean  e_folder_type_registry_register_type         (EFolderTypeRegistry           *folder_type_registry,
+							const gchar                    *type_name,
+							const gchar                    *icon_name,
+							const gchar                    *display_name,
+							const gchar                    *description,
+							gboolean                       user_creatable,
+							gint                            num_accepted_dnd_types,
+							const gchar                   **accepted_dnd_types);
+gboolean  e_folder_type_registry_set_handler_for_type  (EFolderTypeRegistry           *folder_type_registry,
+							const gchar                    *type_name,
+							GObject                       *handler);
+
+GList *e_folder_type_registry_get_type_names  (EFolderTypeRegistry *folder_type_registry);
+
+gboolean  e_folder_type_registry_type_registered  (EFolderTypeRegistry *folder_type_registry,
+						   const gchar          *type_name);
+void      e_folder_type_registry_unregister_type  (EFolderTypeRegistry *folder_type_registry,
+						   const gchar          *type_name);
+
+const gchar                    *e_folder_type_registry_get_icon_name_for_type     (EFolderTypeRegistry *folder_type_registry,
+										  const gchar          *type_name);
+GObject                       *e_folder_type_registry_get_handler_for_type       (EFolderTypeRegistry *folder_type_registry,
+										  const gchar          *type_name);
+gboolean                       e_folder_type_registry_type_is_user_creatable     (EFolderTypeRegistry *folder_type_registry,
+										  const gchar          *type_name);
+const gchar                    *e_folder_type_registry_get_display_name_for_type  (EFolderTypeRegistry *folder_type_registry,
+										  const gchar          *type_name);
+const gchar                    *e_folder_type_registry_get_description_for_type   (EFolderTypeRegistry *folder_type_registry,
+										  const gchar          *type_name);
+
+GList *e_folder_type_registry_get_accepted_dnd_types_for_type (EFolderTypeRegistry *folder_type_registry,
+							       const gchar *type_name);
+
+G_END_DECLS
+
+#endif /* _E_FOLDER_TYPE_REGISTRY_H_ */
diff --git a/server/storage/e-folder.c b/server/storage/e-folder.c
new file mode 100644
index 0000000..7413530
--- /dev/null
+++ b/server/storage/e-folder.c
@@ -0,0 +1,462 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-folder.h"
+
+#include <string.h>
+#include <glib.h>
+#include <libedataserver/e-data-server-util.h>
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (EFolder, e_folder, G_TYPE_OBJECT)
+
+struct EFolderPrivate {
+	gchar *name;
+	gchar *type;
+	gchar *description;
+	gchar *physical_uri;
+
+	gint child_highlight;
+	gint unread_count;
+
+	/* Folders have a default sorting priority of zero; when deciding the
+	   sort order in the Evolution folder tree, folders with the same
+	   priority value are compared by name, while folders with a higher
+	   priority number always come after the folders with a lower priority
+	   number.  */
+	gint sorting_priority;
+
+	guint self_highlight : 1;
+	guint is_stock : 1;
+	guint can_sync_offline : 1;
+	guint has_subfolders : 1;
+
+	/* Custom icon for this folder; if NULL the folder will just use the
+	   icon for its type.  */
+	gchar *custom_icon_name;
+};
+
+enum {
+	CHANGED,
+	NAME_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* EFolder methods.   */
+
+static gboolean
+accept_drop (EFolder *folder, GdkDragContext *context,
+	     const gchar *target_type,
+	     GtkSelectionData *selection_data)
+{
+	return FALSE;
+}
+
+/* GObject methods.  */
+
+static void
+impl_finalize (GObject *object)
+{
+	EFolder *folder;
+	EFolderPrivate *priv;
+
+	folder = E_FOLDER (object);
+	priv = folder->priv;
+
+	g_free (priv->name);
+	g_free (priv->type);
+	g_free (priv->description);
+	g_free (priv->physical_uri);
+
+	g_free (priv->custom_icon_name);
+
+	g_free (priv);
+
+	(* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+e_folder_class_init (EFolderClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = impl_finalize;
+
+	klass->accept_drop = accept_drop;
+	signals[CHANGED] = g_signal_new ("changed",
+					 G_OBJECT_CLASS_TYPE (object_class),
+					 G_SIGNAL_RUN_FIRST,
+					 G_STRUCT_OFFSET (EFolderClass, changed),
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+
+	signals[NAME_CHANGED] = g_signal_new ("name_changed",
+					      G_OBJECT_CLASS_TYPE (object_class),
+					      G_SIGNAL_RUN_FIRST,
+					      G_STRUCT_OFFSET (EFolderClass, name_changed),
+					      NULL, NULL,
+					      g_cclosure_marshal_VOID__VOID,
+					      G_TYPE_NONE, 0);
+}
+
+static void
+e_folder_init (EFolder *folder)
+{
+	EFolderPrivate *priv;
+
+	priv = g_new0 (EFolderPrivate, 1);
+	folder->priv = priv;
+}
+
+void
+e_folder_construct (EFolder *folder,
+		    const gchar *name,
+		    const gchar *type,
+		    const gchar *description)
+{
+	EFolderPrivate *priv;
+
+	g_return_if_fail (E_IS_FOLDER (folder));
+	g_return_if_fail (name != NULL);
+	g_return_if_fail (type != NULL);
+
+	priv = folder->priv;
+
+	priv->name        = g_strdup (name);
+	priv->type        = g_strdup (type);
+	priv->description = g_strdup (description);
+}
+
+EFolder *
+e_folder_new (const gchar *name,
+	      const gchar *type,
+	      const gchar *description)
+{
+	EFolder *folder;
+
+	g_return_val_if_fail (name != NULL, NULL);
+	g_return_val_if_fail (type != NULL, NULL);
+	g_return_val_if_fail (description != NULL, NULL);
+
+	folder = g_object_new (E_TYPE_FOLDER, NULL);
+
+	e_folder_construct (folder, name, type, description);
+
+	return folder;
+}
+
+const gchar *
+e_folder_get_name (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+	return folder->priv->name;
+}
+
+const gchar *
+e_folder_get_type_string (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+	return folder->priv->type;
+}
+
+const gchar *
+e_folder_get_description (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+	return folder->priv->description;
+}
+
+const gchar *
+e_folder_get_physical_uri (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+	return folder->priv->physical_uri;
+}
+
+gint
+e_folder_get_unread_count (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), FALSE);
+
+	return folder->priv->unread_count;
+}
+
+gboolean
+e_folder_get_highlighted (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), FALSE);
+
+	return folder->priv->child_highlight || folder->priv->unread_count;
+}
+
+gboolean
+e_folder_get_is_stock (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), FALSE);
+
+	return folder->priv->is_stock;
+}
+
+gboolean
+e_folder_get_can_sync_offline (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), FALSE);
+
+	return folder->priv->can_sync_offline;
+}
+
+gboolean
+e_folder_get_has_subfolders (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), FALSE);
+
+	return folder->priv->has_subfolders;
+}
+
+/**
+ * e_folder_get_custom_icon:
+ * @folder: An EFolder
+ *
+ * Get the name of the custom icon for @folder, or NULL if no custom icon is
+ * associated with it.
+ **/
+const gchar *
+e_folder_get_custom_icon_name (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+	return folder->priv->custom_icon_name;
+}
+
+/**
+ * e_folder_get_sorting_priority:
+ * @folder: An EFolder
+ *
+ * Get the sorting priority for @folder.
+ *
+ * Return value: Sorting priority value for @folder.
+ **/
+gint
+e_folder_get_sorting_priority (EFolder *folder)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), 0);
+
+	return folder->priv->sorting_priority;
+}
+
+void
+e_folder_set_name (EFolder *folder,
+		   const gchar *name)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+	g_return_if_fail (name != NULL);
+
+	if (folder->priv->name == name)
+		return;
+
+	g_free (folder->priv->name);
+	folder->priv->name = g_strdup (name);
+
+	g_signal_emit (folder, signals[NAME_CHANGED], 0);
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+void
+e_folder_set_type_string (EFolder *folder,
+			  const gchar *type)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+	g_return_if_fail (type != NULL);
+
+	g_free (folder->priv->type);
+	folder->priv->type = g_strdup (type);
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+void
+e_folder_set_description (EFolder *folder,
+			  const gchar *description)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+	g_return_if_fail (description != NULL);
+
+	g_free (folder->priv->description);
+	folder->priv->description = g_strdup (description);
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+void
+e_folder_set_physical_uri (EFolder *folder,
+			   const gchar *physical_uri)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+	g_return_if_fail (physical_uri != NULL);
+
+	if (folder->priv->physical_uri == physical_uri)
+		return;
+
+	g_free (folder->priv->physical_uri);
+	folder->priv->physical_uri = g_strdup (physical_uri);
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+void
+e_folder_set_unread_count (EFolder *folder,
+			   gint unread_count)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+
+	folder->priv->unread_count = unread_count;
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+void
+e_folder_set_child_highlight (EFolder *folder,
+			      gboolean highlighted)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+
+	if (highlighted)
+		folder->priv->child_highlight++;
+	else
+		folder->priv->child_highlight--;
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+void
+e_folder_set_is_stock (EFolder *folder,
+		       gboolean is_stock)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+
+	folder->priv->is_stock = !! is_stock;
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+void
+e_folder_set_can_sync_offline (EFolder *folder,
+			       gboolean can_sync_offline)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+
+	folder->priv->can_sync_offline = !! can_sync_offline;
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+void
+e_folder_set_has_subfolders (EFolder *folder,
+			     gboolean has_subfolders)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+
+	folder->priv->has_subfolders = !! has_subfolders;
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+/**
+ * e_folder_set_custom_icon_name:
+ * @folder: An EFolder
+ * @icon_name: Name of the icon to be set (to be found in the standard
+ * Evolution icon dir)
+ *
+ * Set a custom icon for @folder (thus overriding the default icon, which is
+ * the one associated to the type of the folder).
+ **/
+void
+e_folder_set_custom_icon (EFolder *folder,
+			  const gchar *icon_name)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+
+	if (icon_name == folder->priv->custom_icon_name)
+		return;
+
+	if (folder->priv->custom_icon_name == NULL
+	    || (icon_name != NULL && strcmp (icon_name, folder->priv->custom_icon_name) != 0)) {
+		g_free (folder->priv->custom_icon_name);
+		folder->priv->custom_icon_name = g_strdup (icon_name);
+
+		g_signal_emit (folder, signals[CHANGED], 0);
+	}
+}
+
+/**
+ * e_folder_set_sorting_priority:
+ * @folder: An EFolder
+ * @sorting_priority: A sorting priority number
+ *
+ * Set the sorting priority for @folder.  Folders have a default sorting
+ * priority of zero; when deciding the sort order in the Evolution folder tree,
+ * folders with the same priority value are compared by name, while folders
+ * with a higher priority number always come after the folders with a lower
+ * priority number.
+ **/
+void
+e_folder_set_sorting_priority (EFolder *folder,
+			       gint sorting_priority)
+{
+	g_return_if_fail (E_IS_FOLDER (folder));
+
+	if (folder->priv->sorting_priority == sorting_priority)
+		return;
+
+	folder->priv->sorting_priority = sorting_priority;
+
+	g_signal_emit (folder, signals[CHANGED], 0);
+}
+
+gboolean
+e_folder_accept_drop (EFolder *folder, GdkDragContext *context,
+		      const gchar *target_type,
+		      GtkSelectionData *selection_data)
+{
+	g_return_val_if_fail (E_IS_FOLDER (folder), FALSE);
+	g_return_val_if_fail (context != NULL, FALSE);
+
+	return E_FOLDER_GET_CLASS (folder)->accept_drop (folder, context,
+							 target_type,
+							 selection_data);
+}
diff --git a/server/storage/e-folder.h b/server/storage/e-folder.h
new file mode 100644
index 0000000..449e5ed
--- /dev/null
+++ b/server/storage/e-folder.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifndef _E_FOLDER_H_
+#define _E_FOLDER_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_FOLDER			(e_folder_get_type ())
+#define E_FOLDER(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_FOLDER, EFolder))
+#define E_FOLDER_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_FOLDER, EFolderClass))
+#define E_IS_FOLDER(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_FOLDER))
+#define E_IS_FOLDER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_FOLDER))
+#define E_FOLDER_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_FOLDER, EFolderClass))
+
+typedef struct EFolder        EFolder;
+typedef struct EFolderPrivate EFolderPrivate;
+typedef struct EFolderClass   EFolderClass;
+
+struct EFolder {
+	GObject parent;
+
+	EFolderPrivate *priv;
+};
+
+struct EFolderClass {
+	GObjectClass parent_class;
+
+	/* Methods.  */
+	gboolean (* accept_drop) (EFolder *folder,
+				  GdkDragContext *context,
+				  const gchar *target_type,
+				  GtkSelectionData *selection_data);
+
+	/* Signals.  */
+	void (* changed) (EFolder *folder);
+	void (* name_changed) (EFolder *folder);
+};
+
+GType    e_folder_get_type   (void);
+void     e_folder_construct  (EFolder    *folder,
+			      const gchar *name,
+			      const gchar *type,
+			      const gchar *description);
+EFolder *e_folder_new        (const gchar *name,
+			      const gchar *type,
+			      const gchar *description);
+
+const gchar *e_folder_get_name		  (EFolder *folder);
+const gchar *e_folder_get_type_string	  (EFolder *folder);
+const gchar *e_folder_get_description	  (EFolder *folder);
+const gchar *e_folder_get_physical_uri	  (EFolder *folder);
+gint         e_folder_get_unread_count	  (EFolder *folder);
+gboolean    e_folder_get_highlighted	  (EFolder *folder);
+gboolean    e_folder_get_is_stock	  (EFolder *folder);
+gboolean    e_folder_get_can_sync_offline (EFolder *folder);
+gboolean    e_folder_get_has_subfolders   (EFolder *folder);
+const gchar *e_folder_get_custom_icon_name (EFolder *folder);
+gint         e_folder_get_sorting_priority (EFolder *folder);
+
+void  e_folder_set_name              (EFolder *folder, const gchar *name);
+void  e_folder_set_type_string       (EFolder *folder, const gchar *type);
+void  e_folder_set_description       (EFolder *folder, const gchar *description);
+void  e_folder_set_physical_uri      (EFolder *folder, const gchar *physical_uri);
+void  e_folder_set_unread_count      (EFolder *folder, gint unread_count);
+void  e_folder_set_child_highlight   (EFolder *folder, gboolean highlighted);
+void  e_folder_set_is_stock          (EFolder *folder, gboolean is_stock);
+void  e_folder_set_can_sync_offline  (EFolder *folder, gboolean can_sync_offline);
+void  e_folder_set_has_subfolders    (EFolder *folder, gboolean has_subfolders);
+void  e_folder_set_custom_icon       (EFolder *folder, const gchar *icon_name);
+void  e_folder_set_sorting_priority  (EFolder *folder, gint sorting_priority);
+
+gboolean e_folder_accept_drop        (EFolder *folder, GdkDragContext *context,
+				      const gchar *target_type,
+				      GtkSelectionData *selection_data);
+G_END_DECLS
+
+#endif /* _E_FOLDER_H_ */
diff --git a/server/storage/e-storage.c b/server/storage/e-storage.c
new file mode 100644
index 0000000..8a201dc
--- /dev/null
+++ b/server/storage/e-storage.c
@@ -0,0 +1,828 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-storage.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-storage.h"
+
+#include "e-folder-tree.h"
+
+#include <glib/gi18n-lib.h>
+#include <libedataserver/e-data-server-util.h>
+
+#include <string.h>
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (EStorage, e_storage, G_TYPE_OBJECT)
+
+struct EStoragePrivate {
+	/* The set of folders we have in this storage.  */
+	EFolderTree *folder_tree;
+
+	/* Internal name of the storage */
+	gchar *name;
+};
+
+enum {
+	NEW_FOLDER,
+	UPDATED_FOLDER,
+	REMOVED_FOLDER,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* Destroy notification function for the folders in the tree.  */
+
+static void
+folder_destroy_notify (EFolderTree *tree,
+		       const gchar *path,
+		       gpointer data,
+		       gpointer closure)
+{
+	EFolder *e_folder;
+
+	if (data == NULL) {
+		/* The root folder has no EFolder associated to it.  */
+		return;
+	}
+
+	e_folder = E_FOLDER (data);
+	g_object_unref (e_folder);
+}
+
+/* Signal callbacks for the EFolders.  */
+
+static void
+folder_changed_cb (EFolder *folder,
+		   gpointer data)
+{
+	EStorage *storage;
+	EStoragePrivate *priv;
+	const gchar *path, *p;
+	gboolean highlight;
+
+	g_assert (E_IS_STORAGE (data));
+
+	storage = E_STORAGE (data);
+	priv = storage->priv;
+
+	path = e_folder_tree_get_path_for_data (priv->folder_tree, folder);
+	g_assert (path != NULL);
+
+	g_signal_emit (storage, signals[UPDATED_FOLDER], 0, path);
+
+	highlight = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (folder), "last_highlight"));
+	if (highlight != e_folder_get_highlighted (folder)) {
+		highlight = !highlight;
+		g_object_set_data (G_OBJECT (folder), "last_highlight", GINT_TO_POINTER (highlight));
+		p = strrchr (path, '/');
+		if (p && p != path) {
+			gchar *name;
+
+			name = g_strndup (path, p - path);
+			folder = e_folder_tree_get_folder (priv->folder_tree, name);
+			g_free (name);
+			if (folder)
+				e_folder_set_child_highlight (folder, highlight);
+		}
+	}
+}
+
+/* GObject methods.  */
+
+static void
+impl_finalize (GObject *object)
+{
+	EStorage *storage;
+	EStoragePrivate *priv;
+
+	storage = E_STORAGE (object);
+	priv = storage->priv;
+
+	if (priv->folder_tree != NULL)
+		e_folder_tree_destroy (priv->folder_tree);
+
+	g_free (priv->name);
+
+	g_free (priv);
+
+	(* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* EStorage methods.  */
+
+static GList *
+impl_get_subfolder_paths (EStorage *storage,
+			  const gchar *path)
+{
+	EStoragePrivate *priv;
+
+	priv = storage->priv;
+
+	return e_folder_tree_get_subfolders (priv->folder_tree, path);
+}
+
+static EFolder *
+impl_get_folder (EStorage *storage,
+		 const gchar *path)
+{
+	EStoragePrivate *priv;
+	EFolder *e_folder;
+
+	priv = storage->priv;
+
+	e_folder = (EFolder *) e_folder_tree_get_folder (priv->folder_tree, path);
+
+	return e_folder;
+}
+
+static const gchar *
+impl_get_name (EStorage *storage)
+{
+	return storage->priv->name;
+}
+
+static void
+impl_async_create_folder (EStorage *storage,
+			  const gchar *path,
+			  const gchar *type,
+			  EStorageResultCallback callback,
+			  gpointer data)
+{
+	(* callback) (storage, E_STORAGE_NOTIMPLEMENTED, data);
+}
+
+static void
+impl_async_remove_folder (EStorage *storage,
+			  const gchar *path,
+			  EStorageResultCallback callback,
+			  gpointer data)
+{
+	(* callback) (storage, E_STORAGE_NOTIMPLEMENTED, data);
+}
+
+static void
+impl_async_xfer_folder (EStorage *storage,
+			const gchar *source_path,
+			const gchar *destination_path,
+			gboolean remove_source,
+			EStorageResultCallback callback,
+			gpointer data)
+{
+	(* callback) (storage, E_STORAGE_NOTIMPLEMENTED, data);
+}
+
+static void
+impl_async_open_folder (EStorage *storage,
+			const gchar *path,
+			EStorageDiscoveryCallback callback,
+			gpointer data)
+{
+	(* callback) (storage, E_STORAGE_NOTIMPLEMENTED, NULL, data);
+}
+
+static gboolean
+impl_will_accept_folder (EStorage *storage,
+			 EFolder *new_parent,
+			 EFolder *source)
+{
+	EStoragePrivate *priv = storage->priv;
+	const gchar *parent_path, *source_path;
+	gint source_len;
+
+	/* By default, we only disallow dragging a folder into
+	 * a subfolder of itself.
+	 */
+
+	if (new_parent == source)
+		return FALSE;
+
+	parent_path = e_folder_tree_get_path_for_data (priv->folder_tree,
+						       new_parent);
+	source_path = e_folder_tree_get_path_for_data (priv->folder_tree,
+						       source);
+	if (!parent_path || !source_path)
+		return FALSE;
+
+	source_len = strlen (source_path);
+	if (!strncmp (parent_path, source_path, source_len) &&
+	    parent_path[source_len] == '/')
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+impl_async_discover_shared_folder (EStorage *storage,
+				   const gchar *owner,
+				   const gchar *folder_name,
+				   EStorageDiscoveryCallback callback,
+				   gpointer data)
+{
+	(* callback) (storage, E_STORAGE_NOTIMPLEMENTED, NULL, data);
+}
+
+static void
+impl_async_remove_shared_folder (EStorage *storage,
+				 const gchar *path,
+				 EStorageResultCallback callback,
+				 gpointer data)
+{
+	(* callback) (storage, E_STORAGE_NOTIMPLEMENTED, data);
+}
+
+/* Initialization.  */
+
+static void
+e_storage_class_init (EStorageClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	object_class->finalize = impl_finalize;
+
+	class->get_subfolder_paths = impl_get_subfolder_paths;
+	class->get_folder          = impl_get_folder;
+	class->get_name            = impl_get_name;
+	class->async_create_folder = impl_async_create_folder;
+	class->async_remove_folder = impl_async_remove_folder;
+	class->async_xfer_folder   = impl_async_xfer_folder;
+	class->async_open_folder   = impl_async_open_folder;
+	class->will_accept_folder  = impl_will_accept_folder;
+
+	class->async_discover_shared_folder  = impl_async_discover_shared_folder;
+	class->async_remove_shared_folder    = impl_async_remove_shared_folder;
+	signals[NEW_FOLDER] =
+		g_signal_new ("new_folder",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (EStorageClass, new_folder),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_STRING);
+	signals[UPDATED_FOLDER] =
+		g_signal_new ("updated_folder",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (EStorageClass, updated_folder),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_STRING);
+	signals[REMOVED_FOLDER] =
+		g_signal_new ("removed_folder",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (EStorageClass, removed_folder),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_STRING);
+}
+
+static void
+e_storage_init (EStorage *storage)
+{
+	EStoragePrivate *priv;
+
+	priv = g_new0 (EStoragePrivate, 1);
+
+	priv->folder_tree   = e_folder_tree_new (folder_destroy_notify, NULL);
+
+	storage->priv = priv;
+}
+
+/* Creation.  */
+
+void
+e_storage_construct (EStorage *storage,
+		     const gchar *name,
+		     EFolder *root_folder)
+{
+	EStoragePrivate *priv;
+
+	g_return_if_fail (E_IS_STORAGE (storage));
+
+	priv = storage->priv;
+
+	priv->name = g_strdup (name);
+
+	e_storage_new_folder (storage, "/", root_folder);
+}
+
+EStorage *
+e_storage_new (const gchar *name,
+	       EFolder *root_folder)
+{
+	EStorage *new;
+
+	new = g_object_new (e_storage_get_type (), NULL);
+
+	e_storage_construct (new, name, root_folder);
+
+	return new;
+}
+
+gboolean
+e_storage_path_is_absolute (const gchar *path)
+{
+	g_return_val_if_fail (path != NULL, FALSE);
+
+	return *path == '/';
+}
+
+gboolean
+e_storage_path_is_relative (const gchar *path)
+{
+	g_return_val_if_fail (path != NULL, FALSE);
+
+	return *path != '/';
+}
+
+GList *
+e_storage_get_subfolder_paths (EStorage *storage,
+			       const gchar *path)
+{
+	g_return_val_if_fail (E_IS_STORAGE (storage), NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+	g_return_val_if_fail (g_path_is_absolute (path), NULL);
+
+	return (* E_STORAGE_GET_CLASS (storage)->get_subfolder_paths) (storage, path);
+}
+
+EFolder *
+e_storage_get_folder (EStorage *storage,
+		      const gchar *path)
+{
+	g_return_val_if_fail (E_IS_STORAGE (storage), NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+	g_return_val_if_fail (e_storage_path_is_absolute (path), NULL);
+
+	return (* E_STORAGE_GET_CLASS (storage)->get_folder) (storage, path);
+}
+
+const gchar *
+e_storage_get_name (EStorage *storage)
+{
+	g_return_val_if_fail (E_IS_STORAGE (storage), NULL);
+
+	return (* E_STORAGE_GET_CLASS (storage)->get_name) (storage);
+}
+
+/* Folder operations.  */
+
+void
+e_storage_async_create_folder (EStorage *storage,
+			       const gchar *path,
+			       const gchar *type,
+			       EStorageResultCallback callback,
+			       gpointer data)
+{
+	g_return_if_fail (E_IS_STORAGE (storage));
+	g_return_if_fail (path != NULL);
+	g_return_if_fail (g_path_is_absolute (path));
+	g_return_if_fail (type != NULL);
+	g_return_if_fail (callback != NULL);
+
+	(* E_STORAGE_GET_CLASS (storage)->async_create_folder) (storage, path, type, callback, data);
+}
+
+void
+e_storage_async_remove_folder (EStorage              *storage,
+			       const gchar            *path,
+			       EStorageResultCallback callback,
+			       void                  *data)
+{
+	g_return_if_fail (E_IS_STORAGE (storage));
+	g_return_if_fail (path != NULL);
+	g_return_if_fail (g_path_is_absolute (path));
+	g_return_if_fail (callback != NULL);
+
+	(* E_STORAGE_GET_CLASS (storage)->async_remove_folder) (storage, path, callback, data);
+}
+
+void
+e_storage_async_xfer_folder (EStorage *storage,
+			     const gchar *source_path,
+			     const gchar *destination_path,
+			     const gboolean remove_source,
+			     EStorageResultCallback callback,
+			     gpointer data)
+{
+	g_return_if_fail (E_IS_STORAGE (storage));
+	g_return_if_fail (source_path != NULL);
+	g_return_if_fail (g_path_is_absolute (source_path));
+	g_return_if_fail (destination_path != NULL);
+	g_return_if_fail (g_path_is_absolute (destination_path));
+
+	if (strcmp (source_path, destination_path) == 0) {
+		(* callback) (storage, E_STORAGE_OK, data);
+		return;
+	}
+
+	if (remove_source) {
+		gint destination_len;
+		gint source_len;
+
+		source_len = strlen (source_path);
+		destination_len = strlen (destination_path);
+
+		if (source_len < destination_len
+		    && destination_path[source_len] == '/'
+		    && strncmp (destination_path, source_path, source_len) == 0) {
+			(* callback) (storage, E_STORAGE_CANTMOVETODESCENDANT, data);
+			return;
+		}
+	}
+
+	(* E_STORAGE_GET_CLASS (storage)->async_xfer_folder) (storage, source_path, destination_path, remove_source, callback, data);
+}
+
+void
+e_storage_async_open_folder (EStorage *storage,
+			     const gchar *path,
+			     EStorageDiscoveryCallback callback,
+			     gpointer data)
+{
+	EStoragePrivate *priv;
+	EFolder *folder;
+
+	g_return_if_fail (E_IS_STORAGE (storage));
+	g_return_if_fail (path != NULL);
+	g_return_if_fail (g_path_is_absolute (path));
+
+	priv = storage->priv;
+
+	folder = e_folder_tree_get_folder (priv->folder_tree, path);
+	if (folder == NULL) {
+		(* callback) (storage, E_STORAGE_NOTFOUND, path, data);
+		return;
+	}
+
+	if (! e_folder_get_has_subfolders (folder)) {
+		(* callback) (storage, E_STORAGE_OK, path, data);
+		return;
+	}
+
+	(* E_STORAGE_GET_CLASS (storage)->async_open_folder) (storage, path, callback, data);
+}
+
+gboolean
+e_storage_will_accept_folder (EStorage *storage,
+			      EFolder *new_parent, EFolder *source)
+{
+	g_return_val_if_fail (E_IS_STORAGE (storage), FALSE);
+	g_return_val_if_fail (E_IS_FOLDER (new_parent), FALSE);
+	g_return_val_if_fail (E_IS_FOLDER (source), FALSE);
+
+	return (* E_STORAGE_GET_CLASS (storage)->will_accept_folder) (storage, new_parent, source);
+}
+
+/* Shared folders.  */
+
+void
+e_storage_async_discover_shared_folder (EStorage *storage,
+					const gchar *owner,
+					const gchar *folder_name,
+					EStorageDiscoveryCallback callback,
+					gpointer data)
+{
+	g_return_if_fail (E_IS_STORAGE (storage));
+	g_return_if_fail (owner != NULL);
+	g_return_if_fail (folder_name != NULL);
+
+	(* E_STORAGE_GET_CLASS (storage)->async_discover_shared_folder) (storage, owner, folder_name, callback, data);
+}
+
+void
+e_storage_cancel_discover_shared_folder (EStorage *storage,
+					 const gchar *owner,
+					 const gchar *folder_name)
+{
+	g_return_if_fail (E_IS_STORAGE (storage));
+	g_return_if_fail (owner != NULL);
+	g_return_if_fail (folder_name != NULL);
+	g_return_if_fail (E_STORAGE_GET_CLASS (storage)->cancel_discover_shared_folder != NULL);
+
+	(* E_STORAGE_GET_CLASS (storage)->cancel_discover_shared_folder) (storage, owner, folder_name);
+}
+
+void
+e_storage_async_remove_shared_folder (EStorage *storage,
+				      const gchar *path,
+				      EStorageResultCallback callback,
+				      gpointer data)
+{
+	g_return_if_fail (E_IS_STORAGE (storage));
+	g_return_if_fail (path != NULL);
+	g_return_if_fail (g_path_is_absolute (path));
+
+	(* E_STORAGE_GET_CLASS (storage)->async_remove_shared_folder) (storage, path, callback, data);
+}
+
+const gchar *
+e_storage_result_to_string (EStorageResult result)
+{
+	switch (result) {
+	case E_STORAGE_OK:
+		return _("No error");
+	case E_STORAGE_GENERICERROR:
+		return _("Generic error");
+	case E_STORAGE_EXISTS:
+		return _("A folder with the same name already exists");
+	case E_STORAGE_INVALIDTYPE:
+		return _("The specified folder type is not valid");
+	case E_STORAGE_IOERROR:
+		return _("I/O error");
+	case E_STORAGE_NOSPACE:
+		return _("Not enough space to create the folder");
+	case E_STORAGE_NOTEMPTY:
+		return _("The folder is not empty");
+	case E_STORAGE_NOTFOUND:
+		return _("The specified folder was not found");
+	case E_STORAGE_NOTIMPLEMENTED:
+		return _("Function not implemented in this storage");
+	case E_STORAGE_PERMISSIONDENIED:
+		return _("Permission denied");
+	case E_STORAGE_UNSUPPORTEDOPERATION:
+		return _("Operation not supported");
+	case E_STORAGE_UNSUPPORTEDTYPE:
+		return _("The specified type is not supported in this storage");
+	case E_STORAGE_CANTCHANGESTOCKFOLDER:
+		return _("The specified folder cannot be modified or removed");
+	case E_STORAGE_CANTMOVETODESCENDANT:
+		return _("Cannot make a folder a child of one of its descendants");
+	case E_STORAGE_INVALIDNAME:
+		return _("Cannot create a folder with that name");
+	case E_STORAGE_NOTONLINE:
+		return _("This operation cannot be performed in off-line mode");
+	default:
+		return _("Unknown error");
+	}
+}
+
+/* Public utility functions.  */
+
+typedef struct {
+	const gchar *physical_uri;
+	gchar *retval;
+} GetPathForPhysicalUriForeachData;
+
+static void
+get_path_for_physical_uri_foreach (EFolderTree *folder_tree,
+				   const gchar *path,
+				   gpointer path_data,
+				   gpointer user_data)
+{
+	GetPathForPhysicalUriForeachData *foreach_data;
+	const gchar *physical_uri;
+	EFolder *e_folder;
+
+	foreach_data = (GetPathForPhysicalUriForeachData *) user_data;
+	if (foreach_data->retval != NULL)
+		return;
+
+	e_folder = (EFolder *) path_data;
+	if (e_folder == NULL)
+		return;
+
+	physical_uri = e_folder_get_physical_uri (e_folder);
+	if (physical_uri == NULL)
+		return;
+
+	if (strcmp (foreach_data->physical_uri, physical_uri) == 0)
+		foreach_data->retval = g_strdup (path);
+}
+
+/**
+ * e_storage_get_path_for_physical_uri:
+ * @storage: A storage
+ * @physical_uri: A physical URI
+ *
+ * Look for the folder having the specified @physical_uri.
+ *
+ * Return value: The path of the folder having the specified @physical_uri in
+ * @storage.  If such a folder does not exist, just return NULL.  The return
+ * value must be freed by the caller.
+ **/
+gchar *
+e_storage_get_path_for_physical_uri (EStorage *storage,
+				     const gchar *physical_uri)
+{
+	GetPathForPhysicalUriForeachData foreach_data;
+	EStoragePrivate *priv;
+
+	g_return_val_if_fail (E_IS_STORAGE (storage), NULL);
+	g_return_val_if_fail (physical_uri != NULL, NULL);
+
+	priv = storage->priv;
+
+	foreach_data.physical_uri = physical_uri;
+	foreach_data.retval       = NULL;
+
+	e_folder_tree_foreach (priv->folder_tree, get_path_for_physical_uri_foreach, &foreach_data);
+
+	return foreach_data.retval;
+}
+
+/* Protected functions.  */
+
+/* These functions are used by subclasses to add and remove folders from the
+   state stored in the storage object.  */
+
+static void
+remove_subfolders_except (EStorage *storage, const gchar *path, const gchar *except)
+{
+	EStoragePrivate *priv;
+	GList *subfolders, *f;
+	const gchar *folder_path;
+
+	priv = storage->priv;
+
+	subfolders = e_folder_tree_get_subfolders (priv->folder_tree, path);
+	for (f = subfolders; f; f = f->next) {
+		folder_path = f->data;
+		if (!except || strcmp (folder_path, except) != 0)
+			e_storage_removed_folder (storage, folder_path);
+	}
+	for (f = subfolders; f != NULL; f = f->next)
+		g_free (f->data);
+
+	g_list_free (subfolders);
+}
+
+gboolean
+e_storage_new_folder (EStorage *storage,
+		      const gchar *path,
+		      EFolder *e_folder)
+{
+	EStoragePrivate *priv;
+	gchar *parent_path, *p;
+	EFolder *parent;
+
+	g_return_val_if_fail (E_IS_STORAGE (storage), FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (g_path_is_absolute (path), FALSE);
+	g_return_val_if_fail (E_IS_FOLDER (e_folder), FALSE);
+
+	priv = storage->priv;
+
+	if (! e_folder_tree_add (priv->folder_tree, path, e_folder))
+		return FALSE;
+
+	/* If this is the child of a folder that has a pseudo child,
+	 * remove the pseudo child now.
+	 */
+	p = strrchr (path, '/');
+	if (p && p != path)
+		parent_path = g_strndup (path, p - path);
+	else
+		parent_path = g_strdup ("/");
+	parent = e_folder_tree_get_folder (priv->folder_tree, parent_path);
+	if (parent && e_folder_get_has_subfolders (parent)) {
+		remove_subfolders_except (storage, parent_path, path);
+		e_folder_set_has_subfolders (parent, FALSE);
+	}
+	g_free (parent_path);
+
+	g_signal_connect_object (e_folder, "changed", G_CALLBACK (folder_changed_cb), storage, 0);
+
+	g_signal_emit (storage, signals[NEW_FOLDER], 0, path);
+
+	folder_changed_cb (e_folder, storage);
+
+	return TRUE;
+}
+
+/* This really should be called e_storage_set_has_subfolders, but then
+ * it would look like it was an EStorageSet function. (Fact o' the
+ * day: The word "set" has more distinct meanings than any other word
+ * in the English language.) Anyway, we now return you to your
+ * regularly scheduled source code, already in progress.
+ */
+gboolean
+e_storage_declare_has_subfolders (EStorage *storage,
+				  const gchar *path,
+				  const gchar *message)
+{
+	EStoragePrivate *priv;
+	EFolder *parent, *pseudofolder;
+	gchar *pseudofolder_path;
+	gboolean ok;
+
+	g_return_val_if_fail (E_IS_STORAGE (storage), FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (g_path_is_absolute (path), FALSE);
+	g_return_val_if_fail (message != NULL, FALSE);
+
+	priv = storage->priv;
+
+	parent = e_folder_tree_get_folder (priv->folder_tree, path);
+	if (parent == NULL)
+		return FALSE;
+	if (e_folder_get_has_subfolders (parent))
+		return TRUE;
+
+	remove_subfolders_except (storage, path, NULL);
+
+	pseudofolder = e_folder_new (message, "working", "");
+	if (strcmp (path, "/") == 0)
+		pseudofolder_path = g_strdup_printf ("/%s", message);
+	else
+		pseudofolder_path = g_strdup_printf ("%s/%s", path, message);
+	e_folder_set_physical_uri (pseudofolder, pseudofolder_path);
+
+	ok = e_storage_new_folder (storage, pseudofolder_path, pseudofolder);
+	g_free (pseudofolder_path);
+	if (!ok) {
+		g_object_unref (pseudofolder);
+		return FALSE;
+	}
+
+	e_folder_set_has_subfolders (parent, TRUE);
+	return TRUE;
+}
+
+gboolean
+e_storage_get_has_subfolders (EStorage *storage,
+			      const gchar *path)
+{
+	EStoragePrivate *priv;
+	EFolder *folder;
+
+	g_return_val_if_fail (E_IS_STORAGE (storage), FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (g_path_is_absolute (path), FALSE);
+
+	priv = storage->priv;
+
+	folder = e_folder_tree_get_folder (priv->folder_tree, path);
+
+	return folder && e_folder_get_has_subfolders (folder);
+}
+
+gboolean
+e_storage_removed_folder (EStorage *storage,
+			  const gchar *path)
+{
+	EStoragePrivate *priv;
+	EFolder *folder;
+	const gchar *p;
+
+	g_return_val_if_fail (E_IS_STORAGE (storage), FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (g_path_is_absolute (path), FALSE);
+
+	priv = storage->priv;
+
+	folder = e_folder_tree_get_folder (priv->folder_tree, path);
+	if (folder == NULL)
+		return FALSE;
+
+	p = strrchr (path, '/');
+	if (p != NULL && p != path) {
+		EFolder *parent_folder;
+		gchar *parent_path;
+
+		parent_path = g_strndup (path, p - path);
+		parent_folder = e_folder_tree_get_folder (priv->folder_tree, parent_path);
+
+		if (e_folder_get_highlighted (folder))
+			e_folder_set_child_highlight (parent_folder, FALSE);
+
+		g_free (parent_path);
+	}
+
+	g_signal_emit (storage, signals[REMOVED_FOLDER], 0, path);
+
+	e_folder_tree_remove (priv->folder_tree, path);
+
+	return TRUE;
+}
diff --git a/server/storage/e-storage.h b/server/storage/e-storage.h
new file mode 100644
index 0000000..78a449d
--- /dev/null
+++ b/server/storage/e-storage.h
@@ -0,0 +1,208 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-storage.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifndef _E_STORAGE_H_
+#define _E_STORAGE_H_
+
+#include <glib-object.h>
+#include "e-folder.h"
+
+G_BEGIN_DECLS
+
+#define E_TYPE_STORAGE			(e_storage_get_type ())
+#define E_STORAGE(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_STORAGE, EStorage))
+#define E_STORAGE_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_STORAGE, EStorageClass))
+#define E_IS_STORAGE(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_STORAGE))
+#define E_IS_STORAGE_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_STORAGE))
+#define E_STORAGE_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_STORAGE, EStorageClass))
+
+typedef struct EStorage        EStorage;
+typedef struct EStoragePrivate EStoragePrivate;
+typedef struct EStorageClass   EStorageClass;
+
+typedef enum {
+	E_STORAGE_OK,
+	E_STORAGE_GENERICERROR,
+	E_STORAGE_EXISTS,
+	E_STORAGE_INVALIDTYPE,
+	E_STORAGE_IOERROR,
+	E_STORAGE_NOSPACE,
+	E_STORAGE_NOTEMPTY,
+	E_STORAGE_NOTFOUND,
+	E_STORAGE_NOTIMPLEMENTED,
+	E_STORAGE_PERMISSIONDENIED,
+	E_STORAGE_UNSUPPORTEDOPERATION,
+	E_STORAGE_UNSUPPORTEDTYPE,
+	E_STORAGE_CANTCHANGESTOCKFOLDER,
+	E_STORAGE_CANTMOVETODESCENDANT,
+	E_STORAGE_NOTONLINE,
+	E_STORAGE_INVALIDNAME
+} EStorageResult;
+
+typedef void (* EStorageResultCallback) (EStorage *storage, EStorageResult result, gpointer data);
+typedef void (* EStorageDiscoveryCallback) (EStorage *storage, EStorageResult result, const gchar *path, gpointer data);
+
+struct EStorage {
+	GObject parent;
+
+	EStoragePrivate *priv;
+};
+
+struct EStorageClass {
+	GObjectClass parent_class;
+
+	/* Signals.  */
+
+	void (* new_folder)     (EStorage *storage, const gchar *path);
+	void (* updated_folder) (EStorage *storage, const gchar *path);
+	void (* removed_folder) (EStorage *storage, const gchar *path);
+
+	/* Virtual methods.  */
+
+	GList      * (* get_subfolder_paths)     (EStorage *storage,
+						  const gchar *path);
+	EFolder    * (* get_folder)		 (EStorage *storage,
+						  const gchar *path);
+	const gchar * (* get_name)		 (EStorage *storage);
+
+	void         (* async_create_folder)  (EStorage *storage,
+					       const gchar *path,
+					       const gchar *type,
+					       EStorageResultCallback callback,
+					       gpointer data);
+
+	void         (* async_remove_folder)  (EStorage *storage,
+					       const gchar *path,
+					       EStorageResultCallback callback,
+					       gpointer data);
+
+	void         (* async_xfer_folder)    (EStorage *storage,
+					       const gchar *source_path,
+					       const gchar *destination_path,
+					       const gboolean remove_source,
+					       EStorageResultCallback callback,
+					       gpointer data);
+
+	void         (* async_open_folder)    (EStorage *storage,
+					       const gchar *path,
+					       EStorageDiscoveryCallback callback,
+					       gpointer data);
+
+	gboolean     (* will_accept_folder)   (EStorage *storage,
+					       EFolder *new_parent,
+					       EFolder *source);
+
+	void         (* async_discover_shared_folder)  (EStorage *storage,
+							const gchar *owner,
+							const gchar *folder_name,
+							EStorageDiscoveryCallback callback,
+							gpointer data);
+	void         (* cancel_discover_shared_folder) (EStorage *storage,
+							const gchar *owner,
+							const gchar *folder_name);
+	void         (* async_remove_shared_folder)    (EStorage *storage,
+							const gchar *path,
+							EStorageResultCallback callback,
+							gpointer data);
+};
+
+GType       e_storage_get_type                (void);
+void        e_storage_construct               (EStorage   *storage,
+					       const gchar *name,
+					       EFolder    *root_folder);
+EStorage   *e_storage_new                     (const gchar *name,
+					       EFolder    *root_folder);
+
+gboolean    e_storage_path_is_relative        (const gchar *path);
+gboolean    e_storage_path_is_absolute        (const gchar *path);
+
+GList      *e_storage_get_subfolder_paths     (EStorage   *storage,
+					       const gchar *path);
+EFolder    *e_storage_get_folder              (EStorage   *storage,
+					       const gchar *path);
+
+const gchar *e_storage_get_name                (EStorage *storage);
+
+/* Folder operations.  */
+
+void  e_storage_async_create_folder  (EStorage               *storage,
+				      const gchar             *path,
+				      const gchar             *type,
+				      EStorageResultCallback  callback,
+				      void                   *data);
+void  e_storage_async_remove_folder  (EStorage               *storage,
+				      const gchar             *path,
+				      EStorageResultCallback  callback,
+				      void                   *data);
+void  e_storage_async_xfer_folder    (EStorage               *storage,
+				      const gchar             *source_path,
+				      const gchar             *destination_path,
+				      const gboolean          remove_source,
+				      EStorageResultCallback  callback,
+				      void                   *data);
+void  e_storage_async_open_folder    (EStorage                  *storage,
+				      const gchar                *path,
+				      EStorageDiscoveryCallback  callback,
+				      void                      *data);
+
+const gchar *e_storage_result_to_string   (EStorageResult  result);
+
+gboolean    e_storage_will_accept_folder (EStorage       *storage,
+					  EFolder        *new_parent,
+					  EFolder        *source);
+
+/* Shared folders.  */
+void        e_storage_async_discover_shared_folder  (EStorage                 *storage,
+						     const gchar               *owner,
+						     const gchar               *folder_name,
+						     EStorageDiscoveryCallback callback,
+						     void                     *data);
+void        e_storage_cancel_discover_shared_folder (EStorage                 *storage,
+						     const gchar               *owner,
+						     const gchar               *folder_name);
+void        e_storage_async_remove_shared_folder    (EStorage                 *storage,
+						     const gchar               *path,
+						     EStorageResultCallback    callback,
+						     void                     *data);
+
+/* Utility functions.  */
+
+gchar *e_storage_get_path_for_physical_uri  (EStorage   *storage,
+					    const gchar *physical_uri);
+
+/* FIXME: Need to rename these.  */
+
+gboolean e_storage_new_folder             (EStorage   *storage,
+					   const gchar *path,
+					   EFolder    *folder);
+gboolean e_storage_removed_folder         (EStorage   *storage,
+					   const gchar *path);
+
+gboolean e_storage_declare_has_subfolders (EStorage   *storage,
+					   const gchar *path,
+					   const gchar *message);
+gboolean e_storage_get_has_subfolders     (EStorage   *storage,
+					   const gchar *path);
+
+G_END_DECLS
+
+#endif /* _E_STORAGE_H_ */
diff --git a/server/storage/exchange-account.c b/server/storage/exchange-account.c
new file mode 100644
index 0000000..b4e4ec7
--- /dev/null
+++ b/server/storage/exchange-account.c
@@ -0,0 +1,2398 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* ExchangeAccount: Handles a single configured Connector account. This
+ * is strictly a model object. ExchangeStorage handles the view.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "exchange-account.h"
+#include "exchange-hierarchy-webdav.h"
+#include "exchange-hierarchy-favorites.h"
+#include "exchange-hierarchy-gal.h"
+#include "exchange-folder-size.h"
+#include "e-folder-exchange.h"
+#include "e2k-autoconfig.h"
+#include "e2k-kerberos.h"
+#include "e2k-propnames.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+#include "exchange-hierarchy-foreign.h"
+
+/* This is an ugly hack to avoid API break */
+/* Added for get_authtype */
+#include "exchange-esource.h"
+#include <libedataserverui/e-passwords.h>
+
+#include <gtk/gtk.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#define d(x)
+#define ADS_UF_DONT_EXPIRE_PASSWORD 0x10000
+#define ONE_HUNDRED_NANOSECOND 0.000000100
+#define SECONDS_IN_DAY 86400
+
+struct _ExchangeAccountPrivate {
+	E2kContext *ctx;
+	E2kGlobalCatalog *gc;
+	GHashTable *standard_uris;
+	ExchangeFolderSize *fsize;
+
+	GMutex *connect_lock;
+	gboolean connecting, connected;
+	gint account_online;
+
+	GPtrArray *hierarchies;
+	GHashTable *hierarchies_by_folder, *foreign_hierarchies;
+	ExchangeHierarchy *favorites_hierarchy;
+	GHashTable *folders;
+	GStaticRecMutex folders_lock;
+	gchar *uri_authority, *http_uri_schema;
+	gboolean uris_use_email, offline_sync;
+
+	gchar *identity_name, *identity_email, *source_uri, *password_key;
+	gchar *username, *password, *windows_domain, *nt_domain, *ad_server;
+	gchar *owa_url;
+	E2kAutoconfigAuthPref auth_pref;
+	gint ad_limit, passwd_exp_warn_period, quota_limit;
+	E2kAutoconfigGalAuthPref ad_auth;
+
+	EAccountList *account_list;
+	EAccount *account;
+
+	GMutex *discover_data_lock;
+	GList *discover_datas;
+};
+
+enum {
+	CONNECTED,
+	NEW_FOLDER,
+	REMOVED_FOLDER,
+	LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+static void dispose (GObject *);
+static void finalize (GObject *);
+static void remove_hierarchy (ExchangeAccount *account, ExchangeHierarchy *hier);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* virtual method override */
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
+
+	/* signals */
+	signals[CONNECTED] =
+		g_signal_new ("connected",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (ExchangeAccountClass, connected),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__OBJECT,
+			      G_TYPE_NONE, 1,
+			      E2K_TYPE_CONTEXT);
+	signals[NEW_FOLDER] =
+		g_signal_new ("new_folder",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (ExchangeAccountClass, new_folder),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_POINTER);
+	signals[REMOVED_FOLDER] =
+		g_signal_new ("removed_folder",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (ExchangeAccountClass, removed_folder),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_POINTER);
+}
+
+static void
+init (GObject *object)
+{
+	ExchangeAccount *account = EXCHANGE_ACCOUNT (object);
+
+	account->priv = g_new0 (ExchangeAccountPrivate, 1);
+	account->priv->connect_lock = g_mutex_new ();
+	account->priv->hierarchies = g_ptr_array_new ();
+	account->priv->hierarchies_by_folder = g_hash_table_new (NULL, NULL);
+	account->priv->foreign_hierarchies = g_hash_table_new (g_str_hash, g_str_equal);
+	account->priv->folders = g_hash_table_new (g_str_hash, g_str_equal);
+	g_static_rec_mutex_init (&account->priv->folders_lock);
+	account->priv->discover_data_lock = g_mutex_new ();
+	account->priv->account_online = UNSUPPORTED_MODE;
+	account->priv->nt_domain = NULL;
+	account->priv->fsize = exchange_folder_size_new ();
+}
+
+static void
+free_name (gpointer name, gpointer value, gpointer data)
+{
+	g_free (name);
+}
+
+static void
+free_folder (gpointer key, gpointer folder, gpointer data)
+{
+	g_object_unref (folder);
+}
+
+static void
+dispose (GObject *object)
+{
+	ExchangeAccount *account = EXCHANGE_ACCOUNT (object);
+	gint i;
+
+	if (account->priv->account) {
+		g_object_unref (account->priv->account);
+		account->priv->account = NULL;
+	}
+
+	if (account->priv->account_list) {
+		g_object_unref (account->priv->account_list);
+		account->priv->account_list = NULL;
+	}
+
+	if (account->priv->ctx) {
+		g_object_unref (account->priv->ctx);
+		account->priv->ctx = NULL;
+	}
+
+	if (account->priv->gc) {
+		g_object_unref (account->priv->gc);
+		account->priv->gc = NULL;
+	}
+
+	if (account->priv->hierarchies) {
+		for (i = 0; i < account->priv->hierarchies->len; i++)
+			g_object_unref (account->priv->hierarchies->pdata[i]);
+		g_ptr_array_free (account->priv->hierarchies, TRUE);
+		account->priv->hierarchies = NULL;
+	}
+
+	if (account->priv->foreign_hierarchies) {
+		g_hash_table_foreach (account->priv->foreign_hierarchies, free_name, NULL);
+		g_hash_table_destroy (account->priv->foreign_hierarchies);
+		account->priv->foreign_hierarchies = NULL;
+	}
+
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+
+	if (account->priv->hierarchies_by_folder) {
+		g_hash_table_destroy (account->priv->hierarchies_by_folder);
+		account->priv->hierarchies_by_folder = NULL;
+	}
+
+	if (account->priv->folders) {
+		g_hash_table_foreach (account->priv->folders, free_folder, NULL);
+		g_hash_table_destroy (account->priv->folders);
+		account->priv->folders = NULL;
+	}
+
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+free_uri (gpointer name, gpointer uri, gpointer data)
+{
+	g_free (name);
+	g_free (uri);
+}
+
+static void
+finalize (GObject *object)
+{
+	ExchangeAccount *account = EXCHANGE_ACCOUNT (object);
+
+	if (account->account_name)
+		g_free (account->account_name);
+	if (account->storage_dir)
+		g_free (account->storage_dir);
+	if (account->exchange_server)
+		g_free (account->exchange_server);
+	if (account->home_uri)
+		g_free (account->home_uri);
+	if (account->public_uri)
+		g_free (account->public_uri);
+	if (account->legacy_exchange_dn)
+		g_free (account->legacy_exchange_dn);
+	if (account->default_timezone)
+		g_free (account->default_timezone);
+
+	if (account->priv->standard_uris) {
+		g_hash_table_foreach (account->priv->standard_uris,
+				      free_uri, NULL);
+		g_hash_table_destroy (account->priv->standard_uris);
+	}
+
+	if (account->priv->uri_authority)
+		g_free (account->priv->uri_authority);
+	if (account->priv->http_uri_schema)
+		g_free (account->priv->http_uri_schema);
+
+	if (account->priv->identity_name)
+		g_free (account->priv->identity_name);
+	if (account->priv->identity_email)
+		g_free (account->priv->identity_email);
+	if (account->priv->source_uri)
+		g_free (account->priv->source_uri);
+	if (account->priv->password_key)
+		g_free (account->priv->password_key);
+
+	if (account->priv->username)
+		g_free (account->priv->username);
+	if (account->priv->password) {
+		memset (account->priv->password, 0,
+			strlen (account->priv->password));
+		g_free (account->priv->password);
+	}
+	if (account->priv->windows_domain)
+		g_free (account->priv->windows_domain);
+
+	if (account->priv->nt_domain)
+		g_free (account->priv->nt_domain);
+
+	if (account->priv->ad_server)
+		g_free (account->priv->ad_server);
+
+	if (account->priv->owa_url)
+		g_free (account->priv->owa_url);
+
+	if (account->priv->connect_lock)
+		g_mutex_free (account->priv->connect_lock);
+
+	if (account->priv->discover_data_lock)
+		g_mutex_free (account->priv->discover_data_lock);
+
+	g_static_rec_mutex_free (&account->priv->folders_lock);
+
+	g_free (account->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (exchange_account, ExchangeAccount, class_init, init, PARENT_TYPE)
+
+void
+exchange_account_rescan_tree (ExchangeAccount *account)
+{
+	gint i;
+	EFolder *toplevel;
+
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+
+	for (i = 0; i < account->priv->hierarchies->len; i++) {
+		/* First include the toplevel folder of the hierarchy as well */
+		toplevel = EXCHANGE_HIERARCHY (account->priv->hierarchies->pdata[i])->toplevel;
+
+		exchange_hierarchy_scan_subtree (account->priv->hierarchies->pdata[i],
+						toplevel, account->priv->account_online);
+		exchange_hierarchy_rescan (account->priv->hierarchies->pdata[i]);
+	}
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+}
+
+/*
+ * ExchangeHierarchy folder creation/deletion/xfer notifications
+ */
+
+static void
+hierarchy_new_folder (ExchangeHierarchy *hier, EFolder *folder,
+		      ExchangeAccount *account)
+{
+	gint table_updated = 0;
+	const gchar *permanent_uri =
+		e_folder_exchange_get_permanent_uri (folder);
+	gchar *key;
+
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+
+	/* This makes the cleanup easier. We just unref it each time
+	 * we find it in account->priv->folders.
+	 */
+	key = (gchar *) e_folder_exchange_get_path (folder);
+	if (!g_hash_table_lookup (account->priv->folders, key)) {
+		/* Avoid dupilcations since the user could add a folder as
+		  favorite even though it is already marked as favorite */
+		g_object_ref (folder);
+		g_hash_table_insert (account->priv->folders,
+				     key,
+				     folder);
+		table_updated = 1;
+	}
+
+	key = (gchar *) e_folder_get_physical_uri (folder);
+	if (!g_hash_table_lookup (account->priv->folders, key)) {
+		/* Avoid dupilcations since the user could add a folder as
+		  favorite even though it is already marked as favorite */
+		g_object_ref (folder);
+		g_hash_table_insert (account->priv->folders,
+				     key,
+				     folder);
+		table_updated = 1;
+	}
+
+	key = (gchar *) e_folder_exchange_get_internal_uri (folder);
+	if (!g_hash_table_lookup (account->priv->folders, key)) {
+		/* The internal_uri for public folders and favorites folder
+		   is same !!! Without this check the folder value could
+		   overwrite the previously added folder. */
+		g_object_ref (folder);
+		g_hash_table_insert (account->priv->folders,
+				     key,
+				     folder);
+		table_updated = 1;
+	}
+
+	if (permanent_uri && (!g_hash_table_lookup (account->priv->folders,
+					permanent_uri))) {
+		g_object_ref (folder);
+		g_hash_table_insert (account->priv->folders,
+				     (gchar *)permanent_uri,
+				     folder);
+		table_updated = 1;
+	}
+
+	if (table_updated)
+	{
+		g_hash_table_insert (account->priv->hierarchies_by_folder,
+					folder, hier);
+		g_static_rec_mutex_unlock (&account->priv->folders_lock);
+
+		g_signal_emit (account, signals[NEW_FOLDER], 0, folder);
+	} else {
+		g_static_rec_mutex_unlock (&account->priv->folders_lock);
+	}
+}
+
+static void
+hierarchy_removed_folder (ExchangeHierarchy *hier, EFolder *folder,
+			  ExchangeAccount *account)
+{
+	gint unref_count = 0;
+
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+	if (!g_hash_table_lookup (account->priv->folders,
+					e_folder_exchange_get_path (folder))) {
+		g_static_rec_mutex_unlock (&account->priv->folders_lock);
+		return;
+	}
+
+	if (g_hash_table_remove (account->priv->folders, e_folder_exchange_get_path (folder)))
+		unref_count++;
+
+	if (g_hash_table_remove (account->priv->folders, e_folder_get_physical_uri (folder)))
+		unref_count++;
+
+	/* Dont remove this for favorites, as the internal_uri is shared
+		by the public folder as well */
+	if (hier->type != EXCHANGE_HIERARCHY_FAVORITES) {
+		if (g_hash_table_remove (account->priv->folders, e_folder_exchange_get_internal_uri (folder)))
+			unref_count++;
+	}
+
+	g_hash_table_remove (account->priv->hierarchies_by_folder, folder);
+
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+	g_signal_emit (account, signals[REMOVED_FOLDER], 0, folder);
+
+	if (folder == hier->toplevel)
+		remove_hierarchy (account, hier);
+
+	/* unref only those we really removed */
+	while (unref_count > 0) {
+		g_object_unref (folder);
+		unref_count--;
+	}
+}
+
+static gboolean
+get_folder (ExchangeAccount *account, const gchar *path,
+	    EFolder **folder, ExchangeHierarchy **hier)
+{
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+	*folder = g_hash_table_lookup (account->priv->folders, path);
+	if (!*folder) {
+		g_static_rec_mutex_unlock (&account->priv->folders_lock);
+		return FALSE;
+	}
+	*hier = g_hash_table_lookup (account->priv->hierarchies_by_folder,
+				     *folder);
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+	if (!*hier)
+		return FALSE;
+	return TRUE;
+}
+
+static gboolean
+get_parent_and_name (ExchangeAccount *account, const gchar **path,
+		     EFolder **parent, ExchangeHierarchy **hier)
+{
+	gchar *name, *parent_path;
+
+	name = strrchr (*path + 1, '/');
+	if (!name)
+		return FALSE;
+
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+	parent_path = g_strndup (*path, name - *path);
+	*parent = g_hash_table_lookup (account->priv->folders, parent_path);
+	g_free (parent_path);
+
+	if (!*parent) {
+		g_static_rec_mutex_unlock (&account->priv->folders_lock);
+		return FALSE;
+	}
+
+	*hier = g_hash_table_lookup (account->priv->hierarchies_by_folder,
+				     *parent);
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+	if (!*hier)
+		return FALSE;
+
+	*path = name + 1;
+	return TRUE;
+}
+
+ExchangeAccountFolderResult
+exchange_account_create_folder (ExchangeAccount *account,
+				const gchar *path, const gchar *type)
+{
+	ExchangeHierarchy *hier;
+	EFolder *parent;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account),
+				EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	if (!get_parent_and_name (account, &path, &parent, &hier))
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+
+	return exchange_hierarchy_create_folder (hier, parent, path, type);
+}
+
+static gboolean
+check_if_sf (gpointer key, gpointer value, gpointer user_data)
+{
+	gchar *sf_href = (gchar *)value;
+	gchar *int_uri = (gchar *)user_data;
+
+	if (!strcmp (sf_href, int_uri))
+		return TRUE; /* Quit calling the callback */
+
+	return FALSE; /* Continue calling the callback till end of table */
+}
+
+ExchangeAccountFolderResult
+exchange_account_remove_folder (ExchangeAccount *account, const gchar *path)
+{
+	ExchangeHierarchy *hier;
+	EFolder *folder;
+	const gchar *int_uri;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account),
+				EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	d(g_print ("exchange_account_remove_folder: path=[%s]\n", path));
+
+	if (!get_folder (account, path, &folder, &hier))
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+
+	int_uri = e_folder_exchange_get_internal_uri (folder);
+
+	if (g_hash_table_find (account->priv->standard_uris,
+					check_if_sf, (gchar *)int_uri)) {
+		return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION;
+	}
+
+	return exchange_hierarchy_remove_folder (hier, folder);
+}
+
+ExchangeAccountFolderResult
+exchange_account_xfer_folder (ExchangeAccount *account,
+			      const gchar *source_path,
+			      const gchar *dest_path,
+			      gboolean remove_source)
+{
+	EFolder *source, *dest_parent;
+	ExchangeHierarchy *source_hier, *dest_hier;
+	const gchar *name;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account),
+				EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	if (!get_folder (account, source_path, &source, &source_hier) ||
+	    !get_parent_and_name (account, &dest_path, &dest_parent, &dest_hier)) {
+		/* Source or dest seems to not exist */
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+	}
+	if (source_hier != dest_hier) {
+		/* Can't move something between hierarchies */
+		return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
+	}
+	if (remove_source) {
+		name = e_folder_get_name (source);
+		if (exchange_account_get_standard_uri (account, name))
+			return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION;
+
+	}
+
+	return exchange_hierarchy_xfer_folder (source_hier, source,
+					       dest_parent, dest_path,
+					       remove_source);
+}
+
+static void
+remove_hierarchy (ExchangeAccount *account, ExchangeHierarchy *hier)
+{
+	gint i;
+
+	for (i = 0; i < account->priv->hierarchies->len; i++) {
+		if (account->priv->hierarchies->pdata[i] == hier) {
+			g_ptr_array_remove_index_fast (account->priv->hierarchies, i);
+			break;
+		}
+	}
+	g_hash_table_remove (account->priv->foreign_hierarchies,
+			     hier->owner_email);
+	g_signal_handlers_disconnect_by_func (hier, hierarchy_new_folder, account);
+	g_signal_handlers_disconnect_by_func (hier, hierarchy_removed_folder, account);
+	g_object_unref (hier);
+}
+
+static void
+setup_hierarchy (ExchangeAccount *account, ExchangeHierarchy *hier)
+{
+	g_ptr_array_add (account->priv->hierarchies, hier);
+
+	g_signal_connect (hier, "new_folder",
+			  G_CALLBACK (hierarchy_new_folder), account);
+	g_signal_connect (hier, "removed_folder",
+			  G_CALLBACK (hierarchy_removed_folder), account);
+
+	exchange_hierarchy_add_to_storage (hier);
+}
+
+static void
+setup_hierarchy_foreign (ExchangeAccount *account, ExchangeHierarchy *hier)
+{
+	g_hash_table_insert (account->priv->foreign_hierarchies,
+			     (gchar *)hier->owner_email, hier);
+	setup_hierarchy (account, hier);
+}
+
+struct discover_data {
+	const gchar *user, *folder_name;
+	E2kOperation op;
+};
+
+static ExchangeHierarchy *
+get_hierarchy_for (ExchangeAccount *account, E2kGlobalCatalogEntry *entry)
+{
+	ExchangeHierarchy *hier;
+	gchar *hierarchy_name, *source;
+	gchar *physical_uri_prefix, *internal_uri_prefix;
+
+	hier = g_hash_table_lookup (account->priv->foreign_hierarchies,
+				    entry->email);
+	if (hier)
+		return hier;
+
+	/* i18n: This is the title of an "other user's folders"
+	   hierarchy. Eg, "John Doe's Folders". */
+	hierarchy_name = g_strdup_printf (_("%s's Folders"),
+					  entry->display_name);
+	source = g_strdup_printf ("exchange://%s %s/", entry->mailbox,
+				  account->exchange_server);
+	physical_uri_prefix = g_strdup_printf ("exchange://%s/;%s",
+					       account->priv->uri_authority,
+					       entry->email);
+	internal_uri_prefix = exchange_account_get_foreign_uri (account, entry,
+								NULL);
+
+	hier = exchange_hierarchy_foreign_new (account, hierarchy_name,
+					       physical_uri_prefix,
+					       internal_uri_prefix,
+					       entry->display_name,
+					       entry->email, source);
+	g_free (hierarchy_name);
+	g_free (physical_uri_prefix);
+	g_free (internal_uri_prefix);
+	g_free (source);
+
+	setup_hierarchy_foreign (account, hier);
+	return hier;
+}
+
+ExchangeAccountFolderResult
+exchange_account_discover_shared_folder (ExchangeAccount *account,
+					 const gchar *user,
+					 const gchar *folder_name,
+					 EFolder **folder)
+{
+	struct discover_data dd;
+	ExchangeHierarchy *hier;
+	gchar *email;
+	E2kGlobalCatalogStatus status;
+	E2kGlobalCatalogEntry *entry;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account),
+				EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	if (!account->priv->gc)
+		return EXCHANGE_ACCOUNT_FOLDER_GC_NOTREACHABLE;
+
+	email = strchr (user, '<');
+	if (email)
+		email = g_strndup (email + 1, strcspn (email + 1, ">"));
+	else
+		email = g_strdup (user);
+	hier = g_hash_table_lookup (account->priv->foreign_hierarchies, email);
+	if (hier) {
+		g_free (email);
+		return exchange_hierarchy_foreign_add_folder (hier, folder_name, folder);
+	}
+
+	dd.user = user;
+	dd.folder_name = folder_name;
+	e2k_operation_init (&dd.op);
+
+	g_mutex_lock (account->priv->discover_data_lock);
+	account->priv->discover_datas =
+		g_list_prepend (account->priv->discover_datas, &dd);
+	g_mutex_unlock (account->priv->discover_data_lock);
+
+	status = e2k_global_catalog_lookup (account->priv->gc, &dd.op,
+					    E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL,
+					    email,
+					    E2K_GLOBAL_CATALOG_LOOKUP_EMAIL |
+					    E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX,
+					    &entry);
+	g_free (email);
+	e2k_operation_free (&dd.op);
+
+	g_mutex_lock (account->priv->discover_data_lock);
+	account->priv->discover_datas =
+		g_list_remove (account->priv->discover_datas, &dd);
+	g_mutex_unlock (account->priv->discover_data_lock);
+
+	if (status != E2K_GLOBAL_CATALOG_OK) {
+		if (status == E2K_GLOBAL_CATALOG_ERROR)
+			return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
+		if (status == E2K_GLOBAL_CATALOG_NO_SUCH_USER)
+			return EXCHANGE_ACCOUNT_FOLDER_NO_SUCH_USER;
+		else
+			return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+	}
+
+	hier = get_hierarchy_for (account, entry);
+	return exchange_hierarchy_foreign_add_folder (hier, folder_name, folder);
+}
+
+void
+exchange_account_cancel_discover_shared_folder (ExchangeAccount *account,
+						const gchar *user,
+						const gchar *folder_name)
+{
+	struct discover_data *dd;
+	GList *dds;
+
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+	g_mutex_lock (account->priv->discover_data_lock);
+	for (dds = account->priv->discover_datas; dds; dds = dds->next) {
+		dd = dds->data;
+		if (!strcmp (dd->user, user) &&
+		    !strcmp (dd->folder_name, folder_name))
+			break;
+	}
+	if (!dds) {
+		g_mutex_unlock (account->priv->discover_data_lock);
+		return;
+	}
+
+	e2k_operation_cancel (&dd->op);
+	g_mutex_unlock (account->priv->discover_data_lock);
+
+#ifdef FIXME
+	/* We can't actually cancel the hierarchy's attempt to get
+	 * the folder, but we can remove the hierarchy if appropriate.
+	 */
+	if (dd->hier && exchange_hierarchy_is_empty (dd->hier))
+		hierarchy_removed_folder (dd->hier, dd->hier->toplevel, account);
+#endif
+}
+
+ExchangeAccountFolderResult
+exchange_account_remove_shared_folder (ExchangeAccount *account,
+				       const gchar *path)
+{
+	ExchangeHierarchy *hier;
+	EFolder *folder;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account),
+				EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	if (!get_folder (account, path, &folder, &hier))
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+	if (!EXCHANGE_IS_HIERARCHY_FOREIGN (hier))
+		return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION;
+
+	return exchange_hierarchy_remove_folder (hier, folder);
+}
+
+ExchangeAccountFolderResult
+exchange_account_open_folder (ExchangeAccount *account, const gchar *path)
+{
+	ExchangeHierarchy *hier;
+	EFolder *folder;
+	gint mode;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account),
+				EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	d(g_print ("exchange_account_remove_folder: path=[%s]\n", path));
+
+	if (!get_folder (account, path, &folder, &hier))
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+
+	exchange_account_is_offline (account, &mode);
+	if (mode == ONLINE_MODE && !account->priv->connected &&
+	    hier == (ExchangeHierarchy *)account->priv->hierarchies->pdata[0] &&
+	    folder == hier->toplevel) {
+		/* The shell is asking us to open the personal folders
+		 * hierarchy, but we're already planning to do that
+		 * anyway. So just ignore the request for now.
+		 */
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+	}
+
+	return exchange_hierarchy_scan_subtree (hier, folder, mode);
+}
+
+ExchangeAccountFolderResult
+exchange_account_add_favorite (ExchangeAccount *account,
+			       EFolder         *folder)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	return exchange_hierarchy_favorites_add_folder (
+		account->priv->favorites_hierarchy,
+		folder);
+}
+
+ExchangeAccountFolderResult
+exchange_account_remove_favorite (ExchangeAccount *account,
+				  EFolder         *folder)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	return exchange_hierarchy_remove_folder (
+		EXCHANGE_HIERARCHY (account->priv->favorites_hierarchy),
+		folder);
+}
+
+gboolean
+exchange_account_is_favorite_folder (ExchangeAccount *account,
+					EFolder         *folder)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	return exchange_hierarchy_favorites_is_added (
+		EXCHANGE_HIERARCHY (account->priv->favorites_hierarchy),
+		folder);
+}
+
+static void
+context_redirect (E2kContext *ctx, E2kHTTPStatus status,
+		  const gchar *old_uri, const gchar *new_uri,
+		  ExchangeAccount *account)
+{
+	EFolder *folder;
+
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+	folder = g_hash_table_lookup (account->priv->folders, old_uri);
+	if (!folder) {
+		g_static_rec_mutex_unlock (&account->priv->folders_lock);
+		return;
+	}
+
+	g_hash_table_remove (account->priv->folders, old_uri);
+	e_folder_exchange_set_internal_uri (folder, new_uri);
+	g_hash_table_insert (account->priv->folders,
+			     (gchar *)e_folder_exchange_get_internal_uri (folder),
+			     folder);
+
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+}
+
+static void
+set_sf_prop (const gchar *propname, E2kPropType type,
+	     gpointer phref, gpointer user_data)
+{
+	ExchangeAccount *account = user_data;
+	const gchar *href = (const gchar *)phref;
+	gchar *tmp;
+
+	propname = strrchr (propname, ':');
+	if (!propname++ || !href || !*href)
+		return;
+
+	tmp = e2k_strdup_with_trailing_slash (href);
+	if (!tmp) {
+		g_warning ("Failed to add propname '%s' for href '%s'\n", propname, href);
+		return;
+	}
+
+	g_hash_table_insert (account->priv->standard_uris,
+			     g_strdup (propname),
+			     tmp);
+}
+
+static const gchar *mailbox_info_props[] = {
+	E2K_PR_STD_FOLDER_CALENDAR,
+	E2K_PR_STD_FOLDER_CONTACTS,
+	E2K_PR_STD_FOLDER_DELETED_ITEMS,
+	E2K_PR_STD_FOLDER_DRAFTS,
+	E2K_PR_STD_FOLDER_INBOX,
+	E2K_PR_STD_FOLDER_JOURNAL,
+	E2K_PR_STD_FOLDER_NOTES,
+	E2K_PR_STD_FOLDER_OUTBOX,
+	E2K_PR_STD_FOLDER_SENT_ITEMS,
+	E2K_PR_STD_FOLDER_TASKS,
+	E2K_PR_STD_FOLDER_ROOT,
+	E2K_PR_STD_FOLDER_SENDMSG,
+
+	PR_STORE_ENTRYID,
+	E2K_PR_EXCHANGE_TIMEZONE
+};
+static const gint n_mailbox_info_props = G_N_ELEMENTS (mailbox_info_props);
+
+static gboolean
+account_moved (ExchangeAccount *account, E2kAutoconfig *ac)
+{
+	E2kAutoconfigResult result;
+	EAccount *eaccount;
+
+	result = e2k_autoconfig_check_exchange (ac, NULL);
+	if (result != E2K_AUTOCONFIG_OK)
+		return FALSE;
+	result = e2k_autoconfig_check_global_catalog (ac, NULL);
+	if (result != E2K_AUTOCONFIG_OK)
+		return FALSE;
+
+	eaccount = account->priv->account;
+
+	if (eaccount->source->url && eaccount->transport->url &&
+	    !strcmp (eaccount->source->url, eaccount->transport->url)) {
+		g_free (eaccount->transport->url);
+		eaccount->transport->url = g_strdup (ac->account_uri);
+	}
+	g_free (eaccount->source->url);
+	eaccount->source->url = g_strdup (ac->account_uri);
+
+	e_account_list_change (account->priv->account_list, eaccount);
+	e_account_list_save (account->priv->account_list);
+	return TRUE;
+}
+
+#if 0
+static gboolean
+get_password (ExchangeAccount *account, E2kAutoconfig *ac, ExchangeAccountResult error)
+{
+	gchar *password;
+
+	if (error != EXCHANGE_ACCOUNT_CONNECT_SUCCESS)
+		e_passwords_forget_password ("Exchange", account->priv->password_key);
+
+	password = e_passwords_get_password ("Exchange", account->priv->password_key);
+#if 0
+	if (exchange_component_is_interactive (global_exchange_component)) {
+		gboolean remember, oldremember;
+		if (!password) {
+			gchar *prompt;
+
+			prompt = g_strdup_printf (_("Enter password for %s"),
+						  account->account_name);
+			oldremember = remember =
+					account->priv->account->source->save_passwd;
+			password = e_passwords_ask_password (
+					_("Enter password"),
+					"Exchange",
+					account->priv->password_key,
+					prompt,
+					E_PASSWORDS_REMEMBER_FOREVER|E_PASSWORDS_SECRET,
+					&remember,
+					NULL);
+			if (remember != oldremember) {
+				account->priv->account->source->save_passwd = remember;
+			}
+			g_free (prompt);
+		}
+		else if (!account->priv->account->source->save_passwd) {
+			/* get_password returns the password cached but user has not
+			 * selected remember password option, forget this password
+			 * whis is stored temporarily by e2k_validate_user()
+			 */
+			e_passwords_forget_password ("Exchange", account->priv->password_key);
+		}
+	}
+#endif
+	if (!password) {
+	}
+	else if (!account->priv->account->source->save_passwd) {
+		/* get_password returns the password cached but user has not
+		 * selected remember password option, forget this password
+		 * whis is stored temporarily by e2k_validate_user()
+		 */
+		e_passwords_forget_password ("Exchange", account->priv->password_key);
+	}
+
+	if (password) {
+		e2k_autoconfig_set_password (ac, password);
+		memset (password, 0, strlen (password));
+		g_free (password);
+		return TRUE;
+	} else
+		return FALSE;
+}
+#endif
+
+/* This uses the kerberos calls to check if the authentication failure
+ * was due to the password getting expired. If the password has expired
+ * this returns TRUE, else it returns FALSE.
+ */
+#ifdef HAVE_KRB5
+static gboolean
+is_password_expired (ExchangeAccount *account, E2kAutoconfig *ac)
+{
+	gchar *domain;
+	E2kKerberosResult result;
+
+	if (!ac->password)
+		return FALSE;
+
+	domain = ac->w2k_domain;
+	if (!domain) {
+		domain = strchr (account->priv->identity_email, '@');
+		if (domain)
+			domain++;
+	}
+	if (!domain)
+		return FALSE;
+
+	result = e2k_kerberos_check_password (ac->username, domain,
+					      ac->password);
+	if (result != E2K_KERBEROS_OK &&
+	    result != E2K_KERBEROS_PASSWORD_EXPIRED) {
+		/* try again with nt domain */
+		domain = ac->nt_domain;
+		if (domain)
+			result = e2k_kerberos_check_password (ac->username,
+							      domain,
+							      ac->password);
+	}
+
+	return (result == E2K_KERBEROS_PASSWORD_EXPIRED);
+}
+#endif
+
+static gint
+find_passwd_exp_period (ExchangeAccount *account, E2kGlobalCatalogEntry *entry)
+{
+	gdouble max_pwd_age = 0;
+	gint max_pwd_age_days;
+	E2kOperation gcop;
+	E2kGlobalCatalogStatus gcstatus;
+
+	/* If user has not selected password expiry warning option, return */
+	if (account->priv->passwd_exp_warn_period == -1)
+		return -1;
+
+	/* Check for password expiry period */
+	/* This needs to be invoked after is_password_expired(), i.e.,
+	   only if password is not expired */
+
+	/* Check for account control value for a user */
+
+	e2k_operation_init (&gcop);
+	gcstatus = e2k_global_catalog_lookup (account->priv->gc,
+					      &gcop,
+					      E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL,
+					      account->priv->identity_email,
+					      E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL,
+					      &entry);
+	e2k_operation_free (&gcop);
+	if (gcstatus != E2K_GLOBAL_CATALOG_OK)
+		return -1;
+
+	if (entry->user_account_control & ADS_UF_DONT_EXPIRE_PASSWORD) {
+		return -1;         /* Password is not set to expire */
+	}
+
+	/* Here we don't check not setting the password and expired password */
+	/* Check for the maximum password age set */
+
+	e2k_operation_init (&gcop);
+	max_pwd_age = lookup_passwd_max_age (account->priv->gc, &gcop);
+	e2k_operation_free (&gcop);
+
+	if (max_pwd_age > 0) {
+		/* Calculate password expiry period */
+		max_pwd_age_days =
+		( max_pwd_age * ONE_HUNDRED_NANOSECOND ) / SECONDS_IN_DAY;
+
+		if (max_pwd_age_days <= account->priv->passwd_exp_warn_period) {
+			return max_pwd_age_days;
+		}
+	}
+	return -1;
+}
+
+gchar *
+exchange_account_get_password (ExchangeAccount *account)
+{
+	return e_passwords_get_password ("Exchange", account->priv->password_key);
+}
+
+void
+exchange_account_forget_password (ExchangeAccount *account)
+{
+	e_passwords_forget_password ("Exchange", account->priv->password_key);
+}
+
+ExchangeAccountResult
+exchange_account_set_password (ExchangeAccount *account, gchar *old_pass, gchar *new_pass)
+{
+#ifdef HAVE_KRB5
+	E2kKerberosResult result;
+	gchar *domain;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED);
+	g_return_val_if_fail (old_pass != NULL, EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED);
+	g_return_val_if_fail (new_pass != NULL, EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED);
+
+	domain = account->priv->gc ? account->priv->gc->domain : NULL;
+	if (!domain) {
+		domain = strchr (account->priv->identity_email, '@');
+		if (domain)
+			domain++;
+	}
+	if (!domain) {
+		/* email id is not proper, we return instead of trying nt_domain */
+		return EXCHANGE_ACCOUNT_CONFIG_ERROR;
+	}
+
+	result = e2k_kerberos_change_password (account->priv->username, domain,
+					       old_pass, new_pass);
+	if (result != E2K_KERBEROS_OK && result != E2K_KERBEROS_PASSWORD_TOO_WEAK) {
+		/* try with nt_domain */
+		domain = account->priv->nt_domain;
+		if (domain)
+			result = e2k_kerberos_change_password (account->priv->username,
+							       domain, old_pass,
+							       new_pass);
+	}
+	switch (result) {
+	case E2K_KERBEROS_OK:
+		e_passwords_forget_password ("Exchange", account->priv->password_key);
+		e_passwords_add_password (account->priv->password_key, new_pass);
+		if (account->priv->account->source->save_passwd)
+			e_passwords_remember_password ("Exchange", account->priv->password_key);
+		break;
+
+	case E2K_KERBEROS_PASSWORD_TOO_WEAK:
+		return EXCHANGE_ACCOUNT_PASSWORD_WEAK_ERROR;
+
+	case E2K_KERBEROS_FAILED:
+	default:
+		return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED;
+	}
+
+	return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_SUCCESS;
+#else
+	g_warning ("exchange_account_set_password: Not implemented (no KRB5)");
+	return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED;
+#endif
+}
+
+void
+exchange_account_set_save_password (ExchangeAccount *account, gboolean save_password)
+{
+	account->priv->account->source->save_passwd = save_password;
+}
+
+gboolean
+exchange_account_is_save_password (ExchangeAccount *account)
+{
+	return account->priv->account->source->save_passwd;
+}
+
+/**
+ * exchange_account_set_offline:
+ * @account: an #ExchangeAccount
+ *
+ * This nullifies the connection and sets the account as offline.
+ * The caller should take care that the required data is fetched
+ * before calling this method.
+ *
+ * Return value: Returns TRUE is successfully sets the account to
+ * offline or FALSE if failed
+ **/
+gboolean
+exchange_account_set_offline (ExchangeAccount *account)
+{
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), FALSE);
+
+	g_mutex_lock (account->priv->connect_lock);
+	if (account->priv->ctx) {
+		g_object_unref (account->priv->ctx);
+		account->priv->ctx = NULL;
+	}
+
+	account->priv->account_online = OFFLINE_MODE;
+	g_mutex_unlock (account->priv->connect_lock);
+	return TRUE;
+}
+
+/**
+ * exchange_account_set_online:
+ * @account: an #ExchangeAccount
+ *
+ * This nullifies the connection and sets the account as offline.
+ * The caller should take care that the required data is fetched
+ * before calling this method.
+ *
+ * Return value: Returns TRUE is successfully sets the account to
+ * offline or FALSE if failed
+ **/
+gboolean
+exchange_account_set_online (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), FALSE);
+
+	g_mutex_lock (account->priv->connect_lock);
+	account->priv->account_online = ONLINE_MODE;
+	g_mutex_unlock (account->priv->connect_lock);
+
+	return TRUE;
+}
+
+/**
+ * exchange_account_is_offline:
+ * @account: an #ExchangeAccount
+ *
+ * Return value: Returns TRUE if account is offline
+ **/
+void
+exchange_account_is_offline (ExchangeAccount *account, gint *state)
+{
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+	*state = account->priv->account_online;
+}
+
+static gboolean
+setup_account_hierarchies (ExchangeAccount *account)
+{
+	ExchangeHierarchy *hier, *personal_hier;
+	ExchangeAccountFolderResult fresult;
+	gchar *phys_uri_prefix, *dir;
+	GDir *d;
+	const gchar *dent;
+	gint mode;
+
+	exchange_account_is_offline (account, &mode);
+
+	if (mode == UNSUPPORTED_MODE)
+		return FALSE;
+
+	/* Check if folder hierarchies are already setup. */
+	if (account->priv->hierarchies->len > 0)
+		goto hierarchies_created;
+
+	/* Set up Personal Folders hierarchy */
+	phys_uri_prefix = g_strdup_printf ("exchange://%s/;personal",
+					   account->priv->uri_authority);
+	hier = exchange_hierarchy_webdav_new (account,
+					      EXCHANGE_HIERARCHY_PERSONAL,
+					      _("Personal Folders"),
+					      phys_uri_prefix,
+					      account->home_uri,
+					      account->priv->identity_name,
+					      account->priv->identity_email,
+					      account->priv->source_uri,
+					      TRUE);
+
+	setup_hierarchy (account, hier);
+	g_free (phys_uri_prefix);
+
+	/* Favorite Public Folders */
+	phys_uri_prefix = g_strdup_printf ("exchange://%s/;favorites",
+					   account->priv->uri_authority);
+	hier = exchange_hierarchy_favorites_new (account,
+						 _("Favorite Public Folders"),
+						 phys_uri_prefix,
+						 account->home_uri,
+						 account->public_uri,
+						 account->priv->identity_name,
+						 account->priv->identity_email,
+						 account->priv->source_uri);
+	setup_hierarchy (account, hier);
+	g_free (phys_uri_prefix);
+	account->priv->favorites_hierarchy = hier;
+
+	/* Public Folders */
+	phys_uri_prefix = g_strdup_printf ("exchange://%s/;public",
+					   account->priv->uri_authority);
+	hier = exchange_hierarchy_webdav_new (account,
+					      EXCHANGE_HIERARCHY_PUBLIC,
+					      /* i18n: Outlookism */
+					      _("All Public Folders"),
+					      phys_uri_prefix,
+					      account->public_uri,
+					      account->priv->identity_name,
+					      account->priv->identity_email,
+					      account->priv->source_uri,
+					      FALSE);
+	setup_hierarchy (account, hier);
+	g_free (phys_uri_prefix);
+
+	/* Global Address List */
+	phys_uri_prefix = g_strdup_printf ("gal://%s/gal",
+					   account->priv->uri_authority);
+						     /* i18n: Outlookism */
+	hier = exchange_hierarchy_gal_new (account, _("Global Address List"),
+					   phys_uri_prefix);
+	setup_hierarchy (account, hier);
+	g_free (phys_uri_prefix);
+
+	/* Other users' folders */
+	d = g_dir_open (account->storage_dir, 0, NULL);
+	if (d) {
+		while ((dent = g_dir_read_name (d))) {
+			if (!strchr (dent, '@'))
+				continue;
+			dir = g_strdup_printf ("%s/%s", account->storage_dir, dent);
+			hier = exchange_hierarchy_foreign_new_from_dir (account, dir);
+			g_free (dir);
+			if (!hier)
+				continue;
+
+			setup_hierarchy_foreign (account, hier);
+		}
+		g_dir_close (d);
+	}
+
+hierarchies_created:
+
+	/* Scan the personal and favorite folders so we can resolve references
+	 * to the Calendar, Contacts, etc even if the tree isn't
+	 * opened.
+	 */
+
+	/* Assuming the first element being personal hierarchy. */
+	personal_hier = account->priv->hierarchies->pdata[0];
+
+	fresult = exchange_hierarchy_scan_subtree (personal_hier,
+						   personal_hier->toplevel,
+						   mode);
+	if (fresult != EXCHANGE_ACCOUNT_FOLDER_OK) {
+		account->priv->connecting = FALSE;
+		return FALSE;
+	}
+
+	account->mbox_size = exchange_hierarchy_webdav_get_total_folder_size (
+					EXCHANGE_HIERARCHY_WEBDAV (personal_hier));
+
+	fresult = exchange_hierarchy_scan_subtree (
+		account->priv->favorites_hierarchy,
+		account->priv->favorites_hierarchy->toplevel,
+		mode);
+	if (fresult != EXCHANGE_ACCOUNT_FOLDER_OK &&
+	    fresult != EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST) {
+		account->priv->connecting = FALSE;
+		return FALSE;
+	}
+	return TRUE;
+}
+
+/**
+ * exchange_account_connect:
+ * @account: an #ExchangeAccount
+ *
+ * This attempts to connect to @account. If the shell has enabled user
+ * interaction, then it will prompt for a password if needed, and put
+ * up an error message if the connection attempt failed.
+ *
+ * Return value: an #E2kContext, or %NULL if the connection attempt
+ * failed.
+ **/
+E2kContext *
+exchange_account_connect (ExchangeAccount *account, const gchar *pword,
+			  ExchangeAccountResult *info_result)
+{
+	E2kAutoconfig *ac;
+	E2kAutoconfigResult result;
+	E2kHTTPStatus status;
+	gboolean redirected = FALSE;
+	E2kResult *results;
+	gint nresults = 0, mode;
+	GByteArray *entryid;
+	const gchar *tz;
+	E2kGlobalCatalogStatus gcstatus;
+	E2kGlobalCatalogEntry *entry;
+	E2kOperation gcop;
+	gchar *user_name = NULL;
+
+	*info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR;
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	*info_result = EXCHANGE_ACCOUNT_CONNECT_SUCCESS;
+	exchange_account_is_offline (account, &mode);
+
+	g_mutex_lock (account->priv->connect_lock);
+
+	if (mode == UNSUPPORTED_MODE) {
+		*info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR;
+		account->priv->connecting = FALSE;
+		g_mutex_unlock (account->priv->connect_lock);
+		return NULL;
+	}
+
+	if (account->priv->connecting || mode == OFFLINE_MODE) {
+		g_mutex_unlock (account->priv->connect_lock);
+		if (mode == OFFLINE_MODE) {
+			setup_account_hierarchies (account);
+			*info_result = EXCHANGE_ACCOUNT_OFFLINE;
+		}
+		else {
+			*info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR;
+		}
+		return NULL;
+	} else if (account->priv->ctx) {
+		g_mutex_unlock (account->priv->connect_lock);
+		return account->priv->ctx;
+	}
+
+	account->priv->connecting = TRUE;
+
+	if (account->priv->windows_domain)
+		user_name = g_strdup_printf ("%s\\%s", account->priv->windows_domain, account->priv->username);
+	else
+		user_name = g_strdup (account->priv->username);
+
+	ac = e2k_autoconfig_new (account->home_uri,
+				 user_name,
+				 NULL,
+				 account->priv->auth_pref);
+	g_free (user_name);
+
+	e2k_autoconfig_set_gc_server (ac, account->priv->ad_server,
+				      account->priv->ad_limit, account->priv->ad_auth);
+
+	if (!pword) {
+		account->priv->connecting = FALSE;
+		g_mutex_unlock (account->priv->connect_lock);
+		*info_result = EXCHANGE_ACCOUNT_PASSWORD_INCORRECT;
+		e2k_autoconfig_free (ac);
+		return NULL;
+	}
+
+	e2k_autoconfig_set_password (ac, pword);
+
+ try_connect_again:
+	account->priv->ctx = e2k_autoconfig_get_context (ac, NULL, &result);
+
+	if (!account->priv->nt_domain && ac->nt_domain)
+		account->priv->nt_domain = g_strdup (ac->nt_domain);
+	else
+		account->priv->nt_domain = NULL;
+
+	if (result != E2K_AUTOCONFIG_OK) {
+#ifdef HAVE_KRB5
+		if ( is_password_expired (account, ac)) {
+			*info_result = EXCHANGE_ACCOUNT_PASSWORD_EXPIRED;
+			account->priv->connecting = FALSE;
+			g_mutex_unlock (account->priv->connect_lock);
+			e2k_autoconfig_free (ac);
+			return NULL;
+		}
+#endif
+		switch (result) {
+
+		case E2K_AUTOCONFIG_AUTH_ERROR:
+			*info_result = EXCHANGE_ACCOUNT_PASSWORD_INCORRECT;
+			e2k_autoconfig_free (ac);
+			account->priv->connecting = FALSE;
+			g_mutex_unlock (account->priv->connect_lock);
+			return NULL;
+
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN:
+			*info_result = EXCHANGE_ACCOUNT_DOMAIN_ERROR;
+			e2k_autoconfig_free (ac);
+			account->priv->connecting = FALSE;
+			g_mutex_unlock (account->priv->connect_lock);
+			return NULL;
+
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM:
+			ac->use_ntlm = 1;
+			goto try_connect_again;
+
+		case E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC:
+			ac->use_ntlm = 0;
+			goto try_connect_again;
+
+		case E2K_AUTOCONFIG_REDIRECT:
+			if (!redirected && account_moved (account, ac))
+				goto try_connect_again;
+			break;
+
+		case E2K_AUTOCONFIG_TRY_SSL:
+			if (account_moved (account, ac))
+				goto try_connect_again;
+			break;
+
+		default:
+			break;
+		}
+
+		e2k_autoconfig_free (ac);
+		account->priv->connecting = FALSE;
+		account->priv->account_online = OFFLINE_MODE; /* correct? */
+
+		switch (result) {
+		case E2K_AUTOCONFIG_REDIRECT:
+		case E2K_AUTOCONFIG_TRY_SSL:
+			*info_result = EXCHANGE_ACCOUNT_MAILBOX_NA;
+			break;
+		case E2K_AUTOCONFIG_EXCHANGE_5_5:
+			*info_result = EXCHANGE_ACCOUNT_VERSION_ERROR;
+			break;
+		case E2K_AUTOCONFIG_NOT_EXCHANGE:
+		case E2K_AUTOCONFIG_NO_OWA:
+			*info_result = EXCHANGE_ACCOUNT_WSS_ERROR;
+			break;
+		case E2K_AUTOCONFIG_NO_MAILBOX:
+			*info_result = EXCHANGE_ACCOUNT_NO_MAILBOX;
+			break;
+		case E2K_AUTOCONFIG_CANT_RESOLVE:
+			*info_result = EXCHANGE_ACCOUNT_RESOLVE_ERROR;
+			break;
+		case E2K_AUTOCONFIG_CANT_CONNECT:
+			*info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR;
+			break;
+		case E2K_AUTOCONFIG_CANCELLED:
+			break;
+		default:
+			*info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR;
+			break;
+		}
+
+		g_mutex_unlock (account->priv->connect_lock);
+		return NULL;
+	}
+
+	account->priv->gc = e2k_autoconfig_get_global_catalog (ac, NULL);
+	e2k_autoconfig_free (ac);
+
+	status = e2k_context_propfind (account->priv->ctx, NULL,
+				       account->home_uri,
+				       mailbox_info_props,
+				       n_mailbox_info_props,
+				       &results, &nresults);
+
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		account->priv->connecting = FALSE;
+		*info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR;
+		g_mutex_unlock (account->priv->connect_lock);
+		return NULL; /* FIXME: what error has happened? */
+	}
+
+	if (nresults) {
+		account->priv->standard_uris =
+			g_hash_table_new (e2k_ascii_strcase_hash,
+					  e2k_ascii_strcase_equal);
+		e2k_properties_foreach (results[0].props, set_sf_prop, account);
+
+		/* FIXME: we should get these from the autoconfig */
+		entryid = e2k_properties_get_prop (results[0].props, PR_STORE_ENTRYID);
+		if (entryid)
+			account->legacy_exchange_dn = g_strdup (e2k_entryid_to_dn (entryid));
+
+		tz = e2k_properties_get_prop (results[0].props, E2K_PR_EXCHANGE_TIMEZONE);
+		if (tz)
+			account->default_timezone = g_strdup (tz);
+	}
+
+	if (!setup_account_hierarchies (account)) {
+		*info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR;
+		g_mutex_unlock (account->priv->connect_lock);
+		if (nresults)
+			e2k_results_free (results, nresults);
+		return NULL; /* FIXME: what error has happened? */
+	}
+
+	account->priv->account_online = ONLINE_MODE;
+	account->priv->connecting = FALSE;
+	account->priv->connected = TRUE;
+
+	if (!account->priv->gc)
+		goto skip_quota;
+	/* Check for quota usage */
+	e2k_operation_init (&gcop);
+	gcstatus = e2k_global_catalog_lookup (account->priv->gc, &gcop,
+                                            E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL,
+                                            account->priv->identity_email,
+					    E2K_GLOBAL_CATALOG_LOOKUP_QUOTA,
+                                            &entry);
+	e2k_operation_free (&gcop);
+
+	/* FIXME: warning message should have quota limit value
+	 */
+	if (gcstatus == E2K_GLOBAL_CATALOG_OK) {
+
+		if (entry->quota_norecv &&
+			account->mbox_size >= entry->quota_norecv) {
+				*info_result = EXCHANGE_ACCOUNT_QUOTA_RECIEVE_ERROR;
+				account->priv->quota_limit = entry->quota_norecv;
+		} else if (entry->quota_nosend &&
+				account->mbox_size >= entry->quota_nosend) {
+					*info_result = EXCHANGE_ACCOUNT_QUOTA_SEND_ERROR;
+					account->priv->quota_limit = entry->quota_nosend;
+		} else if (entry->quota_warn &&
+				account->mbox_size >= entry->quota_warn) {
+					*info_result = EXCHANGE_ACCOUNT_QUOTA_WARN;
+					account->priv->quota_limit = entry->quota_warn;
+		}
+	}
+
+skip_quota:
+	g_signal_connect (account->priv->ctx, "redirect",
+			  G_CALLBACK (context_redirect), account);
+
+	g_signal_emit (account, signals[CONNECTED], 0, account->priv->ctx);
+	g_mutex_unlock (account->priv->connect_lock);
+	if (nresults)
+		e2k_results_free (results, nresults);
+	return account->priv->ctx;
+}
+
+/**
+ * exchange_account_is_offline_sync_set:
+ * @account: an #ExchangeAccount
+ *
+ * Return value: TRUE if offline_sync is set for @account and FALSE if not.
+ */
+void
+exchange_account_is_offline_sync_set (ExchangeAccount *account, gint *mode)
+{
+	*mode = UNSUPPORTED_MODE;
+
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+	if (account->priv->offline_sync)
+		*mode = OFFLINE_MODE;
+	else
+		*mode = ONLINE_MODE;
+}
+
+/**
+ * exchange_account_get_context:
+ * @account: an #ExchangeAccount
+ *
+ * Return value: @account's #E2kContext, if it is connected and
+ * online, or %NULL if not.
+ **/
+E2kContext *
+exchange_account_get_context (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	return account->priv->ctx;
+}
+
+/**
+ * exchange_account_get_global_catalog:
+ * @account: an #ExchangeAccount
+ *
+ * Return value: @account's #E2kGlobalCatalog, if it is connected and
+ * online, or %NULL if not.
+ **/
+E2kGlobalCatalog *
+exchange_account_get_global_catalog (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	return account->priv->gc;
+}
+
+/**
+ * exchange_account_fetch:
+ * @acct: an #ExchangeAccount
+ *
+ * Return value: @account's #EAccount, if it is connected and
+ * online, or %NULL if not.
+ **/
+EAccount *
+exchange_account_fetch (ExchangeAccount *acct)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (acct), NULL);
+
+	return acct->priv->account;
+}
+
+/**
+ * exchange_account_get_account_uri_param:
+ * @acct: and #ExchangeAccount
+ * @param: uri param name to get
+ *
+ * Reads the parameter #param from the source url of the underlying EAccount.
+ * Returns the value or NULL. Returned value should be freed with g_free.
+ **/
+gchar *
+exchange_account_get_account_uri_param (ExchangeAccount *acct, const gchar *param)
+{
+	EAccount *account;
+	E2kUri *uri;
+	gchar *res;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (acct), NULL);
+	g_return_val_if_fail (param != NULL, NULL);
+
+	account = exchange_account_fetch (acct);
+	g_return_val_if_fail (account != NULL, NULL);
+
+	uri = e2k_uri_new (e_account_get_string (account, E_ACCOUNT_SOURCE_URL));
+	g_return_val_if_fail (uri != NULL, NULL);
+
+	res = g_strdup (e2k_uri_get_param (uri, param));
+
+	e2k_uri_free (uri);
+
+	return res;
+}
+
+/**
+ * exchange_account_get_standard_uri:
+ * @account: an #ExchangeAccount
+ * @item: the short name of the standard URI
+ *
+ * Looks up the value of one of the standard URIs on @account.
+ * Supported values for @item are:
+ *   "calendar", "contacts", "deleteditems", "drafts", "inbox",
+ *   "journal", "notes", "outbox", "sentitems", "tasks", and
+ *   "sendmsg" (the special mail submission URI)
+ *
+ * Return value: the value of the standard URI, or %NULL if the
+ * account is not connected or the property is invalid or not
+ * defined on @account.
+ **/
+const gchar *
+exchange_account_get_standard_uri (ExchangeAccount *account, const gchar *item)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	if (!account->priv->standard_uris)
+		return NULL;
+
+	return g_hash_table_lookup (account->priv->standard_uris, item);
+}
+
+/**
+ * exchange_account_get_standard_uri_for:
+ * @account: an #ExchangeAccount
+ * @home_uri: the home URI of a user
+ * @std_uri_prop: the %E2K_PR_STD_FOLDER property to look up
+ *
+ * Looks up the URI of a folder in another user's mailbox.
+ *
+ * Return value: the URI of the folder, or %NULL if either the folder
+ * doesn't exist or the user doesn't have permission to access it.
+ **/
+gchar *
+exchange_account_get_standard_uri_for (ExchangeAccount *account,
+				       const gchar *home_uri,
+				       const gchar *std_uri_prop)
+{
+	gchar *foreign_uri, *prop;
+	E2kHTTPStatus status;
+	E2kResult *results;
+	gint nresults = 0;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	foreign_uri = e2k_uri_concat (home_uri, "NON_IPM_SUBTREE");
+	status = e2k_context_propfind (account->priv->ctx, NULL, foreign_uri,
+				       &std_uri_prop, 1,
+				       &results, &nresults);
+	g_free (foreign_uri);
+
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status) || nresults == 0)
+		return NULL;
+
+	prop = e2k_properties_get_prop (results[0].props, std_uri_prop);
+	if (prop)
+		foreign_uri = e2k_strdup_with_trailing_slash (prop);
+	else
+		foreign_uri = NULL;
+	e2k_results_free (results, nresults);
+
+	return foreign_uri;
+}
+
+/**
+ * exchange_account_get_foreign_uri:
+ * @account: an #ExchangeAccount
+ * @entry: an #E2kGlobalCatalogEntry with mailbox data
+ * @std_uri_prop: the %E2K_PR_STD_FOLDER property to look up, or %NULL
+ *
+ * Looks up the URI of a folder in another user's mailbox. If
+ * @std_uri_prop is %NULL, the URI for the top level of the user's
+ * mailbox is returned.
+ *
+ * Return value: the URI of the folder, or %NULL if either the folder
+ * doesn't exist or the user doesn't have permission to access it.
+ **/
+gchar *
+exchange_account_get_foreign_uri (ExchangeAccount *account,
+				  E2kGlobalCatalogEntry *entry,
+				  const gchar *std_uri_prop)
+{
+	gchar *home_uri, *foreign_uri;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	if (account->priv->uris_use_email) {
+		gchar *mailbox;
+
+		mailbox = g_strndup (entry->email, strcspn (entry->email, "@"));
+		home_uri = g_strdup_printf (account->priv->http_uri_schema,
+					    entry->exchange_server, mailbox);
+		g_free (mailbox);
+	} else {
+		home_uri = g_strdup_printf (account->priv->http_uri_schema,
+					    entry->exchange_server,
+					    entry->mailbox);
+	}
+	if (!std_uri_prop)
+		return home_uri;
+
+	foreign_uri = exchange_account_get_standard_uri_for (account,
+							     home_uri,
+							     std_uri_prop);
+	g_free (home_uri);
+
+	return foreign_uri;
+}
+
+/* Scans the subscribed users folders. */
+/*FIXME This function is not really required if the syncronization
+  problem between exchange and evolution is fixed. Exchange does not get to know
+ if an user's folder is subscribed from evolution */
+void
+exchange_account_scan_foreign_hierarchy (ExchangeAccount *account, const gchar *user_email)
+{
+	gchar *dir;
+	ExchangeHierarchy *hier;
+	gint mode;
+
+	hier = g_hash_table_lookup (account->priv->foreign_hierarchies, user_email);
+	if (hier) {
+		exchange_hierarchy_rescan (hier);
+		return;
+	}
+
+	dir = g_strdup_printf ("%s/%s", account->storage_dir, user_email);
+	if (g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
+		hier = exchange_hierarchy_foreign_new_from_dir (account, dir);
+		g_free (dir);
+		if (hier) {
+			exchange_account_is_offline (account, &mode);
+			setup_hierarchy_foreign (account, hier);
+			exchange_hierarchy_scan_subtree (hier, hier->toplevel, mode);
+		}
+	}
+}
+
+/**
+ * exchange_account_get_hierarchy_by_email:
+ * @account: an #ExchangeAccount
+ * @email: email id of the foreign user
+ *
+ * If the hierarchy is present just return it back. Else try to get it
+ * from the filesystem and return it.
+ *
+ * Return value: Returns the ExchangeHierarchy of the foreign user's folder.
+ **/
+
+ExchangeHierarchy *
+exchange_account_get_hierarchy_by_email (ExchangeAccount *account, const gchar *email)
+{
+	gchar *dir;
+	ExchangeHierarchy *hier = NULL;
+	gint mode;
+
+	g_return_val_if_fail (email != NULL, NULL);
+
+	hier = g_hash_table_lookup (account->priv->foreign_hierarchies, email);
+	if (!hier) {
+		dir = g_strdup_printf ("%s/%s", account->storage_dir, email);
+		if (g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
+			hier = exchange_hierarchy_foreign_new_from_dir (account, dir);
+			g_free (dir);
+			if (hier) {
+				exchange_account_is_offline (account, &mode);
+				setup_hierarchy_foreign (account, hier);
+			}
+		}
+	}
+
+	return hier;
+ }
+
+/**
+ * exchange_account_get_folder:
+ * @account: an #ExchangeAccount
+ * @path_or_uri: the shell path or URI referring to the folder
+ *
+ * Return value: an #EFolder corresponding to the indicated
+ * folder.
+ **/
+EFolder *
+exchange_account_get_folder (ExchangeAccount *account,
+			     const gchar *path_or_uri)
+{
+	EFolder *folder;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	if (!path_or_uri)
+		return NULL;
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+	folder = g_hash_table_lookup (account->priv->folders, path_or_uri);
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+
+	return folder;
+}
+
+static gint
+folder_comparator (gconstpointer a, gconstpointer b)
+{
+	EFolder **fa = (EFolder **)a;
+	EFolder **fb = (EFolder **)b;
+
+	return strcmp (e_folder_exchange_get_path (*fa),
+		       e_folder_exchange_get_path (*fb));
+}
+
+struct _folders_tree {
+	gchar *path;
+	GPtrArray *folders;
+};
+
+static void
+add_folder (gpointer key, gpointer value, gpointer folders)
+{
+	EFolder *folder = value;
+
+	d(g_print ("%s:%s: key=[%s]\t folder-path=[%s]\n", G_STRLOC, G_STRFUNC,
+		   key, e_folder_exchange_get_path (folder)));
+
+	/* Each folder appears under three different keys, but
+	 * we only want to add it to the results array once. So
+	 * we only add when we see the "path" key.
+	 */
+	if (!strcmp (key, e_folder_exchange_get_path (folder)))
+		g_ptr_array_add (folders, folder);
+}
+
+static void
+add_folder_tree (gpointer key, gpointer value, gpointer folders)
+{
+	EFolder *folder = value;
+	struct _folders_tree *fld_tree = (struct _folders_tree *) folders;
+
+	if (!fld_tree || !fld_tree->path)
+		return;
+
+	if (g_str_has_prefix (key, fld_tree->path))
+		add_folder (key, folder, fld_tree->folders);
+}
+
+/**
+ * exchange_account_get_folders:
+ * @account: an #ExchangeAccount
+ *
+ * Return an array of folders (sorted such that parents will occur
+ * before children). If the caller wants to keep up to date with the
+ * list of folders, he should also listen to %new_folder and
+ * %removed_folder signals.
+ *
+ * Return value: an array of folders. The array should be freed with
+ * g_ptr_array_free().
+ **/
+GPtrArray *
+exchange_account_get_folders (ExchangeAccount *account)
+{
+	GPtrArray *folders;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	folders = g_ptr_array_new ();
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+	g_hash_table_foreach (account->priv->folders, add_folder, folders);
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+
+	qsort (folders->pdata, folders->len,
+	       sizeof (EFolder *), folder_comparator);
+
+	return folders;
+}
+
+/**
+ * exchange_account_get_folder_tree:
+ * @account: an #ExchangeAccount
+ *
+ * Return an array of folders (sorted such that parents will occur
+ * before children). If the caller wants to keep up to date with the
+ * list of folders, he should also listen to %new_folder and
+ * %removed_folder signals.
+ *
+ * Return value: an array of folders. The array should be freed with
+ * g_ptr_array_free().
+ **/
+GPtrArray *
+exchange_account_get_folder_tree (ExchangeAccount *account, gchar * path)
+{
+	GPtrArray *folders = NULL;
+	EFolder *folder = NULL;
+	ExchangeHierarchy *hier = NULL;
+
+	struct _folders_tree *fld_tree = NULL;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	if (!get_folder (account, path, &folder, &hier))
+		return folders;
+
+	exchange_hierarchy_scan_subtree (hier, folder, account->priv->account_online);
+
+	folders = g_ptr_array_new ();
+	fld_tree = g_new0 (struct _folders_tree, 1);
+	fld_tree->path = path;
+	fld_tree->folders = folders;
+
+	g_static_rec_mutex_lock (&account->priv->folders_lock);
+	g_hash_table_foreach (account->priv->folders, add_folder_tree, fld_tree);
+	g_static_rec_mutex_unlock (&account->priv->folders_lock);
+
+	qsort (folders->pdata, folders->len,
+	       sizeof (EFolder *), folder_comparator);
+
+	g_free (fld_tree);
+
+	return folders;
+}
+
+/**
+ * exchange_account_get_quota_limit:
+ * @account: an #ExchangeAccount
+ *
+ * Return the value of the quota limit reached.
+ *
+ * Return value: an gint
+ **/
+gint
+exchange_account_get_quota_limit (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 0);
+
+	return account->priv->quota_limit;
+}
+
+gint
+exchange_account_check_password_expiry (ExchangeAccount *account)
+{
+	E2kGlobalCatalogEntry *entry=NULL; /* This is never set before it's used! */
+	gint max_pwd_age_days = -1;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 0);
+
+	max_pwd_age_days = find_passwd_exp_period (account, entry);
+	return max_pwd_age_days;
+}
+
+gchar *
+exchange_account_get_username (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	return account->priv->username;
+}
+
+/**
+  * exchange_account_get_windows_domain :
+  * @account : #ExchangeAccount
+  *
+  * Returns the Windows domain
+  *
+  * Return value : Windows domain
+  **/
+gchar *
+exchange_account_get_windows_domain (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	return account->priv->windows_domain;
+}
+
+/**
+  * exchange_account_get_email_id :
+  * @account : #ExchangeAccount
+  *
+  * Retunrs user's e-mail id.
+  *
+  * Return value : e-mail id string.
+  **/
+gchar *
+exchange_account_get_email_id (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	return account->priv->identity_email;
+}
+
+/**
+  * exchange_account_folder_size_add :
+  * @account : #ExchangeAccount
+  * @folder_name :
+  * @size : Size of @folder_name
+  *
+  * Updates the #ExchangeFolderSize object with the @size of @folder_name
+  *
+  * Return value : void
+  **/
+void
+exchange_account_folder_size_add (ExchangeAccount *account,
+				     const gchar *folder_name,
+				     gdouble size)
+{
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+	exchange_folder_size_update (account->priv->fsize, folder_name, size);
+}
+
+/**
+  * exchange_account_folder_size_remove :
+  * @account : #ExchangeAccount
+  * @folder_name :
+  *
+  * Removes the entry for @folder_name in #ExchangeFolderSize object
+  *
+  * Return value : void
+  **/
+void
+exchange_account_folder_size_remove (ExchangeAccount *account,
+					const gchar *folder_name)
+{
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+	exchange_folder_size_remove (account->priv->fsize, folder_name);
+}
+
+/**
+  * exchange_account_folder_size_rename :
+  * @account : #ExchangeAccount
+  * @old_name : Old name of the folder
+  * @new_name : New name of the folder
+  *
+  * Removes the entry for @old_name in #ExchangeFolderSize object and adds
+  * a new entry for @new_name with the same folder size
+  *
+  * Return value : void
+  **/
+void
+exchange_account_folder_size_rename (ExchangeAccount *account,
+					const gchar *old_name,
+					const gchar *new_name)
+{
+	gdouble cached_size;
+
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+	cached_size = exchange_folder_size_get (account->priv->fsize,
+					old_name);
+	if (cached_size >= 0) {
+		exchange_folder_size_remove (account->priv->fsize, old_name);
+		exchange_folder_size_update (account->priv->fsize,
+						new_name, cached_size);
+	}
+
+}
+
+/**
+  * exchange_account_folder_size_get_model :
+  * @account : #ExchangeAccount
+  *
+  * Returns the model store of #ExchangeFolderSize object
+  *
+  * Return value : The model store. A GtkListStore
+  **/
+GtkListStore *
+exchange_account_folder_size_get_model (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	return exchange_folder_size_get_model (account->priv->fsize);
+}
+
+gchar *
+exchange_account_get_authtype (ExchangeAccount *account)
+{
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	if (account->priv->auth_pref == E2K_AUTOCONFIG_USE_BASIC)
+		return g_strdup ("Basic");
+	else if (account->priv->auth_pref == E2K_AUTOCONFIG_USE_NTLM)
+		return g_strdup ("NTLM");
+
+	return NULL;
+}
+
+/**
+ * exchange_account_new:
+ * @adata: an #EAccount
+ *
+ * An #ExchangeAccount is essentially an #E2kContext with
+ * associated configuration.
+ *
+ * Return value: a new #ExchangeAccount corresponding to @adata
+ **/
+ExchangeAccount *
+exchange_account_new (EAccountList *account_list, EAccount *adata)
+{
+	ExchangeAccount *account;
+	gchar *enc_user, *mailbox;
+	const gchar *param, *proto="http", *owa_path, *pf_server, *owa_url;
+	const gchar *passwd_exp_warn_period, *offline_sync;
+	E2kUri *uri;
+
+	uri = e2k_uri_new (adata->source->url);
+	if (!uri) {
+		g_warning ("Could not parse exchange uri '%s'",
+			   adata->source->url);
+		return NULL;
+	}
+
+	account = g_object_new (EXCHANGE_TYPE_ACCOUNT, NULL);
+	if (!account)
+		return NULL;
+	account->priv->account_list = account_list;
+	g_object_ref (account_list);
+	account->priv->account = adata;
+	g_object_ref (adata);
+
+	account->account_name = g_strdup (adata->name);
+
+	account->storage_dir = g_strdup_printf ("%s/.evolution/exchange/%s %s",
+						g_get_home_dir (),
+						uri->user, uri->host);
+	/*account->account_filename = strrchr (account->storage_dir, '/') + 1;
+	e_filename_make_safe (account->account_filename); */
+
+	/* Identity info */
+	account->priv->identity_name = g_strdup (adata->id->name);
+	account->priv->identity_email = g_strdup (adata->id->address);
+
+	/* URI, etc, info */
+	enc_user = e2k_uri_encode (uri->user, FALSE, "@/;:");
+
+	if (uri->authmech)
+		account->priv->uri_authority = g_strdup_printf ("%s;auth=%s %s", enc_user,
+								uri->authmech, uri->host);
+	else
+		account->priv->uri_authority = g_strdup_printf ("%s %s", enc_user,
+								uri->host);
+	g_free (enc_user);
+
+	account->account_filename = account->priv->uri_authority;
+
+	account->priv->source_uri = g_strdup_printf ("exchange://%s/", account->priv->uri_authority);
+
+	/* Backword compatibility; FIXME, we should just migrate the
+	 * password from this to source_uri.
+	 * old_uri_authority = g_strdup_printf ("%s %s", enc_user,
+	 *					uri->host);
+	 * old_uri_authority needs to be used in the key for migrating
+	 * passwords remembered.
+	 */
+	account->priv->password_key = g_strdup_printf ("exchange://%s/",
+							account->priv->uri_authority);
+
+	account->priv->username = g_strdup (uri->user);
+	if (uri->domain)
+		account->priv->windows_domain = g_strdup (uri->domain);
+	else
+		account->priv->windows_domain = NULL;
+	account->exchange_server = g_strdup (uri->host);
+	if (uri->authmech && !strcmp (uri->authmech, "Basic"))
+		account->priv->auth_pref = E2K_AUTOCONFIG_USE_BASIC;
+	else
+		account->priv->auth_pref = E2K_AUTOCONFIG_USE_NTLM;
+	param = e2k_uri_get_param (uri, "ad_server");
+	if (param && *param) {
+		account->priv->ad_server = g_strdup (param);
+		param = e2k_uri_get_param (uri, "ad_limit");
+		if (param)
+			account->priv->ad_limit = atoi (param);
+		param = e2k_uri_get_param (uri, "ad_auth");
+		if (!param || g_ascii_strcasecmp (param, "default") == 0)
+			account->priv->ad_auth = E2K_AUTOCONFIG_USE_GAL_DEFAULT;
+		else if (g_ascii_strcasecmp (param, "basic") == 0)
+			account->priv->ad_auth = E2K_AUTOCONFIG_USE_GAL_BASIC;
+		else if (g_ascii_strcasecmp (param, "ntlm") == 0)
+			account->priv->ad_auth = E2K_AUTOCONFIG_USE_GAL_NTLM;
+		else
+			account->priv->ad_auth = E2K_AUTOCONFIG_USE_GAL_DEFAULT;
+	}
+
+	passwd_exp_warn_period = e2k_uri_get_param (uri, "passwd_exp_warn_period");
+	if (!passwd_exp_warn_period || !*passwd_exp_warn_period)
+		account->priv->passwd_exp_warn_period = -1;
+	else
+		account->priv->passwd_exp_warn_period = atoi (passwd_exp_warn_period);
+
+	offline_sync = e2k_uri_get_param (uri, "offline_sync");
+	if (!offline_sync)
+		account->priv->offline_sync = FALSE;
+	else
+		account->priv->offline_sync = TRUE;
+
+	owa_path = e2k_uri_get_param (uri, "owa_path");
+	if (!owa_path || !*owa_path)
+		owa_path = "exchange";
+	else if (*owa_path == '/')
+		owa_path++;
+
+	pf_server = e2k_uri_get_param (uri, "pf_server");
+	if (!pf_server || !*pf_server)
+		pf_server = uri->host;
+
+	/* We set protocol reading owa_url, instead of having use_ssl parameter
+	 * because we don't have SSL section anymore in the account creation
+	 * druid and account editor
+	 */
+	/* proto = e2k_uri_get_param (uri, "use_ssl") ? "https" : "http"; */
+
+	owa_url = e2k_uri_get_param (uri, "owa_url");
+	if (owa_url) {
+		account->priv->owa_url = g_strdup (owa_url);
+		if (!strncmp (owa_url, "https:", 6))
+			proto = "https";
+	}
+
+	if (uri->port != 0) {
+		account->priv->http_uri_schema =
+			g_strdup_printf ("%s://%%s:%d/%s/%%s/",
+					 proto, uri->port, owa_path);
+		account->public_uri =
+			g_strdup_printf ("%s://%s:%d/public",
+					 proto, pf_server, uri->port);
+	} else {
+		account->priv->http_uri_schema =
+			g_strdup_printf ("%s://%%s/%s/%%s/", proto, owa_path);
+		account->public_uri =
+			g_strdup_printf ("%s://%s/public", proto, pf_server);
+	}
+
+	param = e2k_uri_get_param (uri, "mailbox");
+	if (!param || !*param)
+		param = uri->user;
+	else if (!g_ascii_strncasecmp (param, account->priv->identity_email, strlen (param)))
+		account->priv->uris_use_email = TRUE;
+	mailbox = e2k_uri_encode (param, TRUE, "/");
+	account->home_uri = g_strdup_printf (account->priv->http_uri_schema,
+					     uri->host, mailbox);
+	g_free (mailbox);
+
+	param = e2k_uri_get_param (uri, "filter");
+	if (param)
+		account->filter_inbox = TRUE;
+	param = e2k_uri_get_param (uri, "filter_junk");
+	if (param)
+		account->filter_junk = TRUE;
+	param = e2k_uri_get_param (uri, "filter_junk_inbox");
+	if (param)
+		account->filter_junk_inbox_only = TRUE;
+
+	e2k_uri_free (uri);
+
+	return account;
+}
+
+/**
+ * exchange_account_get_hierarchy_by_type:
+ * @account: an #ExchangeAccount
+ * @type: Hierarchy type
+ *
+ * Returns the non-foreign hierarchy pointer for the requested type
+ *
+ * Return value: Returns the hierarchy pointer for the requested type
+ **/
+
+ExchangeHierarchy*
+exchange_account_get_hierarchy_by_type (ExchangeAccount* acct,
+					ExchangeHierarchyType type)
+{
+	gint i;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (acct), NULL);
+	g_return_val_if_fail (type != EXCHANGE_HIERARCHY_FOREIGN, NULL);
+
+	for (i = 0; i < acct->priv->hierarchies->len; i++) {
+		if (EXCHANGE_HIERARCHY (acct->priv->hierarchies->pdata[i])->type == type)
+			return EXCHANGE_HIERARCHY (acct->priv->hierarchies->pdata[i]);
+	}
+	return NULL;
+}
diff --git a/server/storage/exchange-account.h b/server/storage/exchange-account.h
new file mode 100644
index 0000000..364d6fa
--- /dev/null
+++ b/server/storage/exchange-account.h
@@ -0,0 +1,202 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_ACCOUNT_H__
+#define __EXCHANGE_ACCOUNT_H__
+
+#include <exchange-types.h>
+#include <exchange-constants.h>
+#include "e2k-autoconfig.h"
+#include "e2k-context.h"
+#include "e2k-global-catalog.h"
+#include "e2k-security-descriptor.h"
+#include "e-folder.h"
+#include <libedataserver/e-account-list.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EXCHANGE_TYPE_ACCOUNT            (exchange_account_get_type ())
+#define EXCHANGE_ACCOUNT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_ACCOUNT, ExchangeAccount))
+#define EXCHANGE_ACCOUNT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_ACCOUNT, ExchangeAccountClass))
+#define EXCHANGE_IS_ACCOUNT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_ACCOUNT))
+#define EXCHANGE_IS_ACCOUNT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_ACCOUNT))
+
+struct _ExchangeAccount {
+	GObject parent;
+
+	ExchangeAccountPrivate *priv;
+
+	/* account_name is the user-specified UTF8 display name.
+	 * account_filename is "username hostname" run through
+	 * e_filename_make_safe.
+	 */
+	gchar *account_name, *account_filename, *storage_dir;
+	gchar *exchange_server, *home_uri, *public_uri;
+	gchar *legacy_exchange_dn, *default_timezone;
+
+	gboolean filter_inbox, filter_junk, filter_junk_inbox_only;
+	gdouble mbox_size;
+};
+
+struct _ExchangeAccountClass {
+	GObjectClass parent_class;
+
+	/* signals */
+	void (*connected) (ExchangeAccount *, E2kContext *);
+
+	void (*new_folder) (ExchangeAccount *, EFolder *);
+	void (*removed_folder) (ExchangeAccount *, EFolder *);
+};
+
+typedef enum {
+	EXCHANGE_ACCOUNT_CONFIG_ERROR,
+	EXCHANGE_ACCOUNT_PASSWORD_WEAK_ERROR,
+	EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED,
+	EXCHANGE_ACCOUNT_PASSWORD_CHANGE_SUCCESS,
+	EXCHANGE_ACCOUNT_OFFLINE,
+	EXCHANGE_ACCOUNT_PASSWORD_INCORRECT,
+	EXCHANGE_ACCOUNT_DOMAIN_ERROR,
+	EXCHANGE_ACCOUNT_MAILBOX_NA,
+	EXCHANGE_ACCOUNT_VERSION_ERROR,
+	EXCHANGE_ACCOUNT_WSS_ERROR,
+	EXCHANGE_ACCOUNT_NO_MAILBOX,
+	EXCHANGE_ACCOUNT_RESOLVE_ERROR,
+	EXCHANGE_ACCOUNT_CONNECT_ERROR,
+	EXCHANGE_ACCOUNT_PASSWORD_EXPIRED,
+	EXCHANGE_ACCOUNT_UNKNOWN_ERROR,
+	EXCHANGE_ACCOUNT_QUOTA_RECIEVE_ERROR,
+	EXCHANGE_ACCOUNT_QUOTA_SEND_ERROR,
+	EXCHANGE_ACCOUNT_QUOTA_WARN,
+	EXCHANGE_ACCOUNT_CONNECT_SUCCESS
+} ExchangeAccountResult;
+
+GType                  exchange_account_get_type             (void);
+ExchangeAccount       *exchange_account_new                  (EAccountList *account_list,
+							      EAccount     *adata);
+E2kContext            *exchange_account_get_context          (ExchangeAccount  *acct);
+E2kGlobalCatalog      *exchange_account_get_global_catalog   (ExchangeAccount  *acct);
+
+EAccount	      *exchange_account_fetch		     (ExchangeAccount *acct);
+gchar                  *exchange_account_get_account_uri_param (ExchangeAccount *acct, const gchar *param);
+
+const gchar            *exchange_account_get_standard_uri     (ExchangeAccount  *acct,
+							      const gchar       *item);
+
+gchar                  *exchange_account_get_standard_uri_for (ExchangeAccount  *acct,
+							      const gchar       *home_uri,
+							      const gchar       *std_uri_prop);
+gchar                  *exchange_account_get_foreign_uri      (ExchangeAccount  *acct,
+							      E2kGlobalCatalogEntry *entry,
+							      const gchar       *std_uri_prop);
+ExchangeHierarchy     *exchange_account_get_hierarchy_by_email (ExchangeAccount *account, const gchar *email);
+
+gchar		      *exchange_account_get_authtype	     (ExchangeAccount *account);
+
+E2kContext            *exchange_account_connect              (ExchangeAccount  *acct,
+							      const gchar *pword,
+							      ExchangeAccountResult *result);
+
+EFolder               *exchange_account_get_folder           (ExchangeAccount  *acct,
+							      const gchar       *path_or_uri);
+GPtrArray             *exchange_account_get_folders          (ExchangeAccount  *acct);
+
+GPtrArray	       *exchange_account_get_folder_tree      (ExchangeAccount *account, gchar * path);
+
+ExchangeHierarchy     *exchange_account_get_hierarchy_by_type	      (ExchangeAccount *acct,
+							       ExchangeHierarchyType type);
+
+void                   exchange_account_rescan_tree          (ExchangeAccount  *acct);
+
+gchar		      *exchange_account_get_password	     (ExchangeAccount  *acct);
+
+ExchangeAccountResult exchange_account_set_password	     (ExchangeAccount  *acct,
+							      gchar             *old_password,
+							      gchar             *new_password);
+
+void		       exchange_account_forget_password       (ExchangeAccount  *acct);
+
+void		       exchange_account_set_save_password    (ExchangeAccount *account,
+							      gboolean save_password);
+
+gboolean	       exchange_account_is_save_password     (ExchangeAccount *account);
+
+gboolean	       exchange_account_set_offline          (ExchangeAccount  *account);
+
+gboolean	       exchange_account_set_online           (ExchangeAccount  *account);
+
+void		       exchange_account_is_offline           (ExchangeAccount  *account,
+							      gint              *mode);
+
+void		       exchange_account_is_offline_sync_set  (ExchangeAccount *account,
+							      gint             *mode);
+
+typedef enum {
+	EXCHANGE_ACCOUNT_FOLDER_OK,
+	EXCHANGE_ACCOUNT_FOLDER_ALREADY_EXISTS,
+	EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST,
+	EXCHANGE_ACCOUNT_FOLDER_UNKNOWN_TYPE,
+	EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED,
+	EXCHANGE_ACCOUNT_FOLDER_OFFLINE,
+	EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION,
+	EXCHANGE_ACCOUNT_FOLDER_GC_NOTREACHABLE,
+	EXCHANGE_ACCOUNT_FOLDER_NO_SUCH_USER,
+	EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR
+} ExchangeAccountFolderResult;
+
+ExchangeAccountFolderResult exchange_account_create_folder (ExchangeAccount *account,
+							    const gchar      *path,
+							    const gchar      *type);
+ExchangeAccountFolderResult exchange_account_remove_folder (ExchangeAccount *account,
+							    const gchar      *path);
+ExchangeAccountFolderResult exchange_account_xfer_folder   (ExchangeAccount *account,
+							    const gchar      *source_path,
+							    const gchar      *dest_path,
+							    gboolean         remove_source);
+ExchangeAccountFolderResult exchange_account_open_folder   (ExchangeAccount *account,
+							    const gchar      *path);
+
+ExchangeAccountFolderResult exchange_account_discover_shared_folder  (ExchangeAccount *account,
+								      const gchar      *user,
+								      const gchar      *folder_name,
+								      EFolder        **folder);
+void       exchange_account_cancel_discover_shared_folder (ExchangeAccount *account,
+							      const gchar      *user,
+							      const gchar      *folder);
+ExchangeAccountFolderResult exchange_account_remove_shared_folder    (ExchangeAccount *account,
+								      const gchar      *path);
+
+ExchangeAccountFolderResult exchange_account_add_favorite (ExchangeAccount *account,
+							   EFolder         *folder);
+ExchangeAccountFolderResult exchange_account_remove_favorite (ExchangeAccount *account,
+							      EFolder         *folder);
+
+gboolean exchange_account_is_favorite_folder              (ExchangeAccount *account,
+							   EFolder         *folder);
+
+gchar * exchange_account_get_username			  (ExchangeAccount *account);
+
+gchar * exchange_account_get_windows_domain		  (ExchangeAccount *account);
+
+gchar * exchange_account_get_email_id			  (ExchangeAccount *account);
+
+gint exchange_account_get_quota_limit			  (ExchangeAccount *account);
+
+gint exchange_account_check_password_expiry		  (ExchangeAccount *account);
+
+/* Folder Size methods */
+void			exchange_account_folder_size_add   (ExchangeAccount *account,
+							     const gchar *folder_name,
+							     gdouble size);
+void			exchange_account_folder_size_remove (ExchangeAccount *account,
+							     const gchar *folder_name);
+void			exchange_account_folder_size_rename (ExchangeAccount *account,
+							     const gchar *old_name,
+							     const gchar *new_name);
+GtkListStore	       *exchange_account_folder_size_get_model (ExchangeAccount *account);
+void			exchange_account_scan_foreign_hierarchy (ExchangeAccount *account,
+							      const gchar *user_email);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_ACCOUNT_H__ */
diff --git a/server/storage/exchange-constants.h b/server/storage/exchange-constants.h
new file mode 100644
index 0000000..b9dd618
--- /dev/null
+++ b/server/storage/exchange-constants.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_CONSTANTS_H__
+#define __EXCHANGE_CONSTANTS_H__
+
+enum {
+	UNSUPPORTED_MODE = 0,
+        OFFLINE_MODE,
+        ONLINE_MODE
+};
+
+typedef enum {
+	EXCHANGE_CALENDAR_FOLDER,
+	EXCHANGE_TASKS_FOLDER,
+	EXCHANGE_CONTACTS_FOLDER
+}FolderType;
+
+/* This flag indicates that its other user's folder. We encode this flag
+   with the FolderType to identify the same. We are doing this to
+   avoid ABI/API break. */
+#define FORIEGN_FOLDER_FLAG 0x0100
+
+#endif
diff --git a/server/storage/exchange-esource.c b/server/storage/exchange-esource.c
new file mode 100644
index 0000000..009656d
--- /dev/null
+++ b/server/storage/exchange-esource.c
@@ -0,0 +1,422 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "exchange-esource.h"
+
+#include <libedataserver/e-source.h>
+#include <libedataserver/e-source-list.h>
+#include <libedataserver/e-source-group.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+static gboolean is_offline (void);
+
+static ESourceGroup *
+find_account_group (ESourceList *source_list, ExchangeAccount *exa)
+{
+	ESourceGroup *group;
+	EAccount *account;
+
+	g_return_val_if_fail (exa != NULL, NULL);
+	g_return_val_if_fail (exa->account_name != NULL, NULL);
+	g_return_val_if_fail (source_list != NULL, NULL);
+
+	account = exchange_account_fetch (exa);
+	g_return_val_if_fail (account != NULL, NULL);
+	g_return_val_if_fail (account->uid != NULL, NULL);
+
+	group = e_source_list_peek_group_by_properties (source_list, "account-uid", account->uid, NULL);
+	if (!group) {
+		/* check whether is stored only with an account name - the old style */
+		GSList *g;
+
+		for (g = e_source_list_peek_groups (source_list); g != NULL; g = g->next) {
+			group = E_SOURCE_GROUP (g->data);
+
+			if (strcmp (e_source_group_peek_name (group), exa->account_name) == 0)
+				break;
+
+			group = NULL;
+		}
+
+		if (group)
+			e_source_group_set_property (group, "account-uid", account->uid);
+	}
+
+	return group;
+}
+
+void
+add_folder_esource (ExchangeAccount *account,
+		    FolderType folder_type,
+		    const gchar *folder_name,
+		    const gchar *physical_uri)
+{
+	ESource *source = NULL;
+	ESourceGroup *source_group = NULL;
+	gchar *relative_uri = NULL;
+	GSList *ids;
+	GConfClient *client;
+	gboolean is_contacts_folder = TRUE, group_new = FALSE, source_new = FALSE;
+	const gchar *offline = NULL;
+	gchar *username, *windows_domain, *useremail, *authtype = NULL;
+	gint mode;
+	ESourceList *source_list = NULL;
+	gboolean offline_flag, update_selection = TRUE, foriegn_folder;
+
+	client = gconf_client_get_default ();
+
+	/* decode the flag */
+	foriegn_folder = folder_type & FORIEGN_FOLDER_FLAG;
+
+	/* Unset the flag */
+	folder_type = folder_type & ~FORIEGN_FOLDER_FLAG;
+
+	if (folder_type == EXCHANGE_CONTACTS_FOLDER) {
+		source_list = e_source_list_new_for_gconf ( client,
+							CONF_KEY_CONTACTS);
+		/* Modify the URI handling of Contacts to the same way as calendar and tasks */
+		if (!g_str_has_prefix (physical_uri, "gal://")) {
+			relative_uri = g_strdup (physical_uri + strlen (EXCHANGE_URI_PREFIX));
+		}
+	}
+	else if (folder_type == EXCHANGE_CALENDAR_FOLDER) {
+		source_list = e_source_list_new_for_gconf ( client,
+							CONF_KEY_CAL);
+		relative_uri = g_strdup (physical_uri + strlen (EXCHANGE_URI_PREFIX));
+		is_contacts_folder = FALSE;
+	}
+	else if (folder_type == EXCHANGE_TASKS_FOLDER) {
+		source_list = e_source_list_new_for_gconf ( client,
+							CONF_KEY_TASKS);
+		relative_uri = g_strdup (physical_uri + strlen (EXCHANGE_URI_PREFIX));
+		is_contacts_folder = FALSE;
+	}
+
+	exchange_account_is_offline_sync_set (account, &mode);
+
+	windows_domain =  exchange_account_get_windows_domain (account);
+	if (windows_domain)
+		username = g_strdup_printf ("%s\\%s", windows_domain,
+					    exchange_account_get_username (account));
+	else
+		username = g_strdup (exchange_account_get_username (account));
+
+	useremail = exchange_account_get_email_id (account);
+	authtype = exchange_account_get_authtype (account);
+
+        if ((source_group = find_account_group (source_list, account)) == NULL) {
+		source_group = e_source_group_new (account->account_name,
+						   EXCHANGE_URI_PREFIX);
+		if (!e_source_list_add_group (source_list, source_group, -1)) {
+			g_object_unref (source_list);
+			g_object_unref (source_group);
+			g_object_unref (client);
+			g_free (relative_uri);
+			g_free (username);
+			if (authtype)
+				g_free (authtype);
+			return;
+		}
+		e_source_group_set_property (source_group, "account-uid", exchange_account_fetch (account)->uid);
+
+		if (is_contacts_folder && g_str_has_prefix (physical_uri, "gal://")) {
+			gchar *browse = exchange_account_get_account_uri_param (account, "ad_browse");
+
+			source = e_source_new_with_absolute_uri (folder_name,
+								 physical_uri);
+			e_source_set_property (source, "completion", "true");
+			e_source_set_property (source, "can-browse", browse ? "1" : NULL);
+			g_free (browse);
+		}
+		else {
+			source = e_source_new (folder_name, relative_uri);
+		}
+
+		if (mode == OFFLINE_MODE) {
+			/* If account is marked for offline sync during account
+			 * creation, mark all the folders for offline sync
+			 */
+			e_source_set_property (source, "offline_sync", "1");
+		}
+
+		if (foriegn_folder && (folder_type != EXCHANGE_CONTACTS_FOLDER)) {
+			e_source_set_property (source, "alarm", "false");
+			e_source_set_property (source, "foreign", "1");
+			e_source_set_property (source, "subscriber", useremail);
+		}
+
+		e_source_set_property (source, "username", username);
+		e_source_set_property (source, "auth-domain", "Exchange");
+		if (authtype)
+			e_source_set_property (source, "auth-type", authtype);
+		if (is_contacts_folder)
+			e_source_set_property (source, "auth", "plain/password");
+		else
+			e_source_set_property (source, "auth", "1");
+		e_source_group_add_source (source_group, source, -1);
+		e_source_list_sync (source_list, NULL);
+		group_new = source_new = TRUE;
+	}
+	else {
+                /* source group already exists*/
+		if ((source = e_source_group_peek_source_by_name (source_group,
+							folder_name)) == NULL) {
+			if (is_contacts_folder && g_str_has_prefix (physical_uri, "gal://")) {
+				gchar *browse = exchange_account_get_account_uri_param (account, "ad_browse");
+
+				source = e_source_new_with_absolute_uri (
+						folder_name, physical_uri);
+				e_source_set_property (source, "completion", "true");
+				e_source_set_property (source, "can-browse", browse ? "1" : NULL);
+				g_free (browse);
+			}
+			else {
+				source = e_source_new (folder_name, relative_uri);
+			}
+
+			if (mode == OFFLINE_MODE)
+				e_source_set_property (source, "offline_sync", "1");
+
+			e_source_set_property (source, "username", username);
+			e_source_set_property (source, "auth-domain", "Exchange");
+			if (authtype)
+				e_source_set_property (source, "auth-type", authtype);
+			if (is_contacts_folder)
+				e_source_set_property (source, "auth", "plain/password");
+			else
+				e_source_set_property (source, "auth", "1");
+
+			if (foriegn_folder && (folder_type != EXCHANGE_CONTACTS_FOLDER)) {
+				e_source_set_property (source, "alarm", "false");
+				e_source_set_property (source, "foreign", "1");
+				e_source_set_property (source, "subscriber", useremail);
+			}
+
+			e_source_group_add_source (source_group, source, -1);
+			source_new = TRUE;
+			e_source_list_sync (source_list, NULL);
+		} else {
+			update_selection = FALSE;
+			/* source group and source both already exist */
+			offline = e_source_get_property (source, "offline_sync");
+			if (!offline) {
+				/* Folder doesn't have any offline property set */
+				if (mode == OFFLINE_MODE) {
+					e_source_set_property (source, "offline_sync", "1");
+					e_source_list_sync (source_list, NULL);
+				}
+			}
+
+			if (is_contacts_folder && g_str_has_prefix (physical_uri, "gal://")) {
+				gchar *browse = exchange_account_get_account_uri_param (account, "ad_browse");
+				const gchar *old_browse = e_source_get_property (source, "can-browse");
+
+				if ((old_browse || browse) && (!old_browse || !browse)) {
+					e_source_set_property (source, "can-browse", browse ? "1" : NULL);
+				}
+
+				g_free (browse);
+			}
+		}
+	}
+
+	offline_flag = is_offline ();
+	if (source && !is_contacts_folder && update_selection) {
+
+		/* Select the folder created */
+		if (folder_type == EXCHANGE_CALENDAR_FOLDER && !offline_flag) {
+			ids = gconf_client_get_list (client,
+					     CONF_KEY_SELECTED_CAL_SOURCES,
+					     GCONF_VALUE_STRING, NULL);
+			ids = g_slist_append (ids,
+					g_strdup (e_source_peek_uid (source)));
+			gconf_client_set_list (client,
+				       CONF_KEY_SELECTED_CAL_SOURCES,
+				       GCONF_VALUE_STRING, ids, NULL);
+			g_slist_foreach (ids, (GFunc) g_free, NULL);
+			g_slist_free (ids);
+		}
+		else if (folder_type == EXCHANGE_TASKS_FOLDER && !offline_flag) {
+			ids = gconf_client_get_list (client,
+					     CONF_KEY_SELECTED_TASKS_SOURCES,
+					     GCONF_VALUE_STRING, NULL);
+
+			ids = g_slist_append (ids,
+					g_strdup (e_source_peek_uid (source)));
+			gconf_client_set_list (client,
+				       CONF_KEY_SELECTED_TASKS_SOURCES,
+				       GCONF_VALUE_STRING, ids, NULL);
+			g_slist_foreach (ids, (GFunc) g_free, NULL);
+			g_slist_free (ids);
+		}
+	}
+
+	g_free (relative_uri);
+	g_free (username);
+	if (authtype)
+		g_free (authtype);
+
+	if (source_new)
+		g_object_unref (source);
+	if (group_new)
+		g_object_unref (source_group);
+	g_object_unref (source_list);
+	g_object_unref (client);
+}
+
+void
+remove_folder_esource (ExchangeAccount *account,
+		       FolderType folder_type,
+		       const gchar *physical_uri)
+{
+	ESourceGroup *group;
+	ESource *source;
+	GSList *groups;
+	GSList *sources;
+	gboolean found_group, is_contacts_folder = TRUE;
+	gchar *read_uri = NULL;
+	const gchar *source_uid;
+	GSList *ids, *temp_ids, *node_to_be_deleted;
+	GConfClient *client;
+	ESourceList *source_list = NULL;
+
+	client = gconf_client_get_default ();
+
+	/* Remove ESource for a given folder */
+	if (folder_type == EXCHANGE_CONTACTS_FOLDER) {
+		source_list = e_source_list_new_for_gconf ( client,
+							CONF_KEY_CONTACTS);
+	}
+	else if (folder_type == EXCHANGE_CALENDAR_FOLDER) {
+		source_list = e_source_list_new_for_gconf ( client,
+							CONF_KEY_CAL);
+		is_contacts_folder = FALSE;
+	}
+	else if (folder_type == EXCHANGE_TASKS_FOLDER) {
+		source_list = e_source_list_new_for_gconf ( client,
+							CONF_KEY_TASKS);
+		is_contacts_folder = FALSE;
+	}
+
+	groups = e_source_list_peek_groups (source_list);
+	found_group = FALSE;
+
+	for (; groups != NULL && !found_group; groups = g_slist_next (groups)) {
+		group = E_SOURCE_GROUP (groups->data);
+
+		if (strcmp (e_source_group_peek_name (group), account->account_name) == 0
+                    &&
+                   strcmp (e_source_group_peek_base_uri (group), EXCHANGE_URI_PREFIX) == 0) {
+
+			sources = e_source_group_peek_sources (group);
+
+			for (; sources != NULL; sources = g_slist_next (sources)) {
+
+				source = E_SOURCE (sources->data);
+				read_uri = e_source_get_uri (source);
+
+				if (strcmp (read_uri, physical_uri) == 0) {
+
+					source_uid = e_source_peek_uid (source);
+					/* Folder Deleted - Remove only the source */
+					/*
+					e_source_group_remove_source_by_uid (
+								group,
+								source_uid);
+					*/
+					e_source_group_remove_source (
+								group,
+								source);
+					e_source_list_sync (source_list, NULL);
+					if (!is_contacts_folder) {
+						/* Remove from the selected folders */
+						if (folder_type == EXCHANGE_CALENDAR_FOLDER) {
+							ids = gconf_client_get_list (
+									client,
+									CONF_KEY_SELECTED_CAL_SOURCES,
+									GCONF_VALUE_STRING, NULL);
+							if (ids) {
+								node_to_be_deleted = g_slist_find_custom (ids,
+											source_uid,
+											(GCompareFunc) strcmp);
+								if (node_to_be_deleted) {
+									g_free (node_to_be_deleted->data);
+									ids = g_slist_delete_link (ids,
+											node_to_be_deleted);
+								}
+							}
+							temp_ids  = ids;
+							for (; temp_ids != NULL; temp_ids = g_slist_next (temp_ids))
+							g_free (temp_ids->data);
+							g_slist_free (ids);
+						}
+						else if (folder_type == EXCHANGE_TASKS_FOLDER) {
+							ids = gconf_client_get_list (client,
+									CONF_KEY_SELECTED_TASKS_SOURCES,
+									GCONF_VALUE_STRING, NULL);
+							if (ids) {
+								node_to_be_deleted = g_slist_find_custom (ids,
+											source_uid,
+											(GCompareFunc) strcmp);
+								if (node_to_be_deleted) {
+									g_free (node_to_be_deleted->data);
+									ids = g_slist_delete_link (ids,
+											node_to_be_deleted);
+								}
+							}
+							temp_ids  = ids;
+							for (; temp_ids != NULL; temp_ids = g_slist_next (temp_ids))
+								g_free (temp_ids->data);
+							g_slist_free (ids);
+						}
+					}
+                                        found_group = TRUE;
+                                        break;
+                                }
+				g_free (read_uri);
+                        }
+                }
+        }
+	g_object_unref (source_list);
+	g_object_unref (client);
+}
+
+static gboolean
+is_offline (void)
+{
+	GConfClient *client;
+	GConfValue *value;
+	gboolean offline = FALSE;
+
+	client = gconf_client_get_default ();
+	value = gconf_client_get (client,
+					"/apps/evolution/shell/start_offline", NULL);
+	if (value)
+		offline = gconf_value_get_bool (value);
+
+	g_object_unref (client);
+	gconf_value_free (value);
+	return offline;
+}
diff --git a/server/storage/exchange-esource.h b/server/storage/exchange-esource.h
new file mode 100644
index 0000000..755a057
--- /dev/null
+++ b/server/storage/exchange-esource.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_ESOURCE_H__
+#define __EXCHANGE_ESOURCE_H__
+
+#include "exchange-constants.h"
+#include "exchange-account.h"
+
+G_BEGIN_DECLS
+
+#define CONF_KEY_SELECTED_CAL_SOURCES "/apps/evolution/calendar/display/selected_calendars"
+#define CONF_KEY_SELECTED_TASKS_SOURCES "/apps/evolution/calendar/tasks/selected_tasks"
+#define CONF_KEY_CAL "/apps/evolution/calendar/sources"
+#define CONF_KEY_TASKS "/apps/evolution/tasks/sources"
+#define CONF_KEY_CONTACTS "/apps/evolution/addressbook/sources"
+#define EXCHANGE_URI_PREFIX "exchange://"
+
+void			add_folder_esource (ExchangeAccount *account, FolderType folder_type, const gchar *folder_name, const gchar *physical_uri);
+void			remove_folder_esource (ExchangeAccount *account, FolderType folder_type, const gchar *physical_uri);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_ESOURCE_H__ */
diff --git a/server/storage/exchange-folder-size.c b/server/storage/exchange-folder-size.c
new file mode 100644
index 0000000..c041e7d
--- /dev/null
+++ b/server/storage/exchange-folder-size.c
@@ -0,0 +1,251 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* ExchangeFolderSize: Display the folder tree with the folder sizes */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "exchange-hierarchy-webdav.h"
+#include "e-folder-exchange.h"
+#include "exchange-folder-size.h"
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (ExchangeFolderSize, exchange_folder_size, G_TYPE_OBJECT)
+
+typedef struct {
+        gchar *folder_name;
+        gdouble folder_size;
+} folder_info;
+
+struct _ExchangeFolderSizePrivate {
+
+	GHashTable *table;
+	GtkListStore *model;
+	GHashTable *row_refs;
+};
+
+enum {
+        COLUMN_NAME,
+        COLUMN_SIZE,
+        NUM_COLUMNS
+};
+
+static gboolean
+free_fgsizeable (gpointer key, gpointer value, gpointer data)
+{
+	folder_info *f_info = (folder_info *) value;
+
+	g_free (key);
+	g_free (f_info->folder_name);
+	g_free (f_info);
+	return TRUE;
+}
+
+static gboolean
+free_row_refs (gpointer key, gpointer value, gpointer user_data)
+{
+	g_free (key);
+	gtk_tree_row_reference_free (value);
+	return TRUE;
+}
+
+static void
+finalize (GObject *object)
+{
+	ExchangeFolderSize *fsize = EXCHANGE_FOLDER_SIZE (object);
+
+	g_hash_table_foreach_remove (fsize->priv->table, free_fgsizeable, NULL);
+	g_hash_table_destroy (fsize->priv->table);
+	g_hash_table_foreach_remove (fsize->priv->row_refs, free_row_refs, NULL);
+	g_hash_table_destroy (fsize->priv->row_refs);
+	if (fsize->priv->model)
+		g_object_unref (fsize->priv->model);
+	g_free (fsize->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+dispose (GObject *object)
+{
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+exchange_folder_size_class_init (ExchangeFolderSizeClass *class)
+{
+	GObjectClass *object_class;
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	object_class = G_OBJECT_CLASS (class);
+
+	/* override virtual methods */
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
+
+}
+
+static void
+exchange_folder_size_init (ExchangeFolderSize *fsize)
+{
+	fsize->priv = g_new0 (ExchangeFolderSizePrivate, 1);
+	fsize->priv->table = g_hash_table_new (g_str_hash, g_str_equal);
+        fsize->priv->model = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_DOUBLE);
+	fsize->priv->row_refs = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+/**
+ * exchange_folder_size_new:
+ *
+ * Return value: a foldersize object with the table initialized
+ **/
+ExchangeFolderSize *
+exchange_folder_size_new (void)
+{
+	ExchangeFolderSize *fsize;
+
+	fsize = g_object_new (EXCHANGE_TYPE_FOLDER_SIZE, NULL);
+
+	return fsize;
+}
+
+void
+exchange_folder_size_update (ExchangeFolderSize *fsize,
+				const gchar *folder_name,
+				gdouble folder_size)
+{
+	folder_info *f_info, *cached_info;
+	ExchangeFolderSizePrivate *priv;
+	GHashTable *folder_gsizeable;
+	GtkTreeRowReference *row;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+
+	g_return_if_fail (EXCHANGE_IS_FOLDER_SIZE (fsize));
+
+	priv = fsize->priv;
+	folder_gsizeable = priv->table;
+
+	cached_info = g_hash_table_lookup (folder_gsizeable, folder_name);
+	if (cached_info) {
+		if (cached_info->folder_size == folder_size) {
+			return;
+		} else {
+			cached_info->folder_size = folder_size;
+			row = g_hash_table_lookup (priv->row_refs, folder_name);
+			path = gtk_tree_row_reference_get_path (row);
+			if (gtk_tree_model_get_iter (GTK_TREE_MODEL (fsize->priv->model), &iter, path)) {
+				gtk_list_store_set (fsize->priv->model, &iter,
+						      COLUMN_NAME, cached_info->folder_name,
+						      COLUMN_SIZE, cached_info->folder_size,
+						      -1);
+			}
+			gtk_tree_path_free (path);
+			return;
+		}
+	} else {
+		f_info = g_new0(folder_info, 1);
+		f_info->folder_name = g_strdup (folder_name);
+		f_info->folder_size = folder_size;
+		g_hash_table_insert (folder_gsizeable, f_info->folder_name, f_info);
+
+		gtk_list_store_append (fsize->priv->model, &iter);
+		gtk_list_store_set (fsize->priv->model, &iter,
+				      COLUMN_NAME, f_info->folder_name,
+				      COLUMN_SIZE, f_info->folder_size,
+				      -1);
+
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (fsize->priv->model), &iter);
+		row = gtk_tree_row_reference_new (GTK_TREE_MODEL (fsize->priv->model), path);
+		gtk_tree_path_free (path);
+
+		g_hash_table_insert (fsize->priv->row_refs, g_strdup (folder_name), row);
+	}
+}
+
+void
+exchange_folder_size_remove (ExchangeFolderSize *fsize,
+				const gchar *folder_name)
+{
+	ExchangeFolderSizePrivate *priv;
+	GHashTable *folder_gsizeable;
+	folder_info *cached_info;
+	GtkTreeRowReference *row;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+
+	g_return_if_fail (EXCHANGE_IS_FOLDER_SIZE (fsize));
+	g_return_if_fail (folder_name != NULL);
+
+	priv = fsize->priv;
+	folder_gsizeable = priv->table;
+
+	cached_info = g_hash_table_lookup (folder_gsizeable, folder_name);
+	if (cached_info)  {
+		row = g_hash_table_lookup (priv->row_refs, folder_name);
+		path = gtk_tree_row_reference_get_path (row);
+		g_hash_table_remove (folder_gsizeable, folder_name);
+		if (gtk_tree_model_get_iter (GTK_TREE_MODEL (fsize->priv->model), &iter, path)) {
+			gtk_list_store_remove (fsize->priv->model, &iter);
+		}
+		g_hash_table_remove (priv->row_refs, row);
+		gtk_tree_path_free (path);
+	}
+}
+
+gdouble
+exchange_folder_size_get (ExchangeFolderSize *fsize,
+			  const gchar *folder_name)
+{
+	ExchangeFolderSizePrivate *priv;
+	GHashTable *folder_gsizeable;
+	folder_info *cached_info;
+
+	g_return_val_if_fail (EXCHANGE_IS_FOLDER_SIZE (fsize), -1);
+
+	priv = fsize->priv;
+	folder_gsizeable = priv->table;
+
+	cached_info = g_hash_table_lookup (folder_gsizeable, folder_name);
+	if (cached_info)  {
+		return cached_info->folder_size;
+	}
+	return -1;
+}
+
+GtkListStore *
+exchange_folder_size_get_model (ExchangeFolderSize *fsize)
+{
+        ExchangeFolderSizePrivate *priv;
+
+	priv = fsize->priv;
+
+	if (!g_hash_table_size (priv->table))
+		return NULL;
+
+	return priv->model;
+}
diff --git a/server/storage/exchange-folder-size.h b/server/storage/exchange-folder-size.h
new file mode 100644
index 0000000..2d5abd4
--- /dev/null
+++ b/server/storage/exchange-folder-size.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2002-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_FOLDER_SIZE_H__
+#define __EXCHANGE_FOLDER_SIZE_H__
+
+#include "exchange-types.h"
+#include "e2k-security-descriptor.h"
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EXCHANGE_TYPE_FOLDER_SIZE			(exchange_folder_size_get_type ())
+#define EXCHANGE_FOLDER_SIZE(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_FOLDER_SIZE, ExchangeFolderSize))
+#define EXCHANGE_FOLDER_SIZE_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_FOLDER_SIZE, ExchangeFolderSizeClass))
+#define EXCHANGE_IS_FOLDER_SIZE(obj)			(G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_FOLDER_SIZE))
+#define EXCHANGE_IS_FOLDER_SIZE_CLASS(klass)		(G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_FOLDER_SIZE))
+
+typedef struct _ExchangeFolderSize        ExchangeFolderSize;
+typedef struct _ExchangeFolderSizePrivate ExchangeFolderSizePrivate;
+typedef struct _ExchangeFolderSizeClass   ExchangeFolderSizeClass;
+
+struct _ExchangeFolderSize {
+	GObject parent;
+
+	ExchangeFolderSizePrivate *priv;
+};
+
+struct _ExchangeFolderSizeClass {
+	GObjectClass parent_class;
+
+};
+
+GType    exchange_folder_size_get_type (void);
+
+ExchangeFolderSize *exchange_folder_size_new (void);
+
+void exchange_folder_size_update (ExchangeFolderSize *fsize,
+						const gchar *folder_name,
+						gdouble folder_size);
+void exchange_folder_size_remove (ExchangeFolderSize *fsize, const gchar *folder_name);
+
+gdouble exchange_folder_size_get (ExchangeFolderSize *fsize, const gchar *folder_name);
+
+GtkListStore *exchange_folder_size_get_model (ExchangeFolderSize *fsize);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_FOLDER_SIZE_H__ */
diff --git a/server/storage/exchange-hierarchy-favorites.c b/server/storage/exchange-hierarchy-favorites.c
new file mode 100644
index 0000000..f4cf5be
--- /dev/null
+++ b/server/storage/exchange-hierarchy-favorites.c
@@ -0,0 +1,341 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* ExchangeHierarchyFavorites: class for the "Favorites" Public Folders
+ * hierarchy (and favorites-handling code).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "exchange-hierarchy-favorites.h"
+#include "exchange-account.h"
+#include "e-folder-exchange.h"
+#include "e2k-propnames.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+#include "exchange-esource.h"
+
+#include <libedataserver/e-source-list.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct _ExchangeHierarchyFavoritesPrivate {
+	gchar *public_uri, *shortcuts_uri;
+	GHashTable *shortcuts;
+};
+
+#define PARENT_TYPE EXCHANGE_TYPE_HIERARCHY_SOMEDAV
+static ExchangeHierarchySomeDAVClass *parent_class = NULL;
+
+static GPtrArray *get_hrefs (ExchangeHierarchySomeDAV *hsd);
+static ExchangeAccountFolderResult remove_folder (ExchangeHierarchy *hier,
+						  EFolder *folder);
+static void finalize (GObject *object);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	ExchangeHierarchySomeDAVClass *somedav_class =
+		EXCHANGE_HIERARCHY_SOMEDAV_CLASS (object_class);
+	ExchangeHierarchyClass *hier_class =
+		EXCHANGE_HIERARCHY_CLASS (object_class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* virtual method override */
+	object_class->finalize = finalize;
+	hier_class->remove_folder = remove_folder;
+	somedav_class->get_hrefs = get_hrefs;
+}
+
+static void
+init (GObject *object)
+{
+	ExchangeHierarchyFavorites *hfav = EXCHANGE_HIERARCHY_FAVORITES (object);
+
+	hfav->priv = g_new0 (ExchangeHierarchyFavoritesPrivate, 1);
+	hfav->priv->shortcuts = g_hash_table_new_full (g_str_hash, g_str_equal,
+						       g_free, g_free);
+}
+
+static void
+finalize (GObject *object)
+{
+	ExchangeHierarchyFavorites *hfav = EXCHANGE_HIERARCHY_FAVORITES (object);
+
+	g_hash_table_destroy (hfav->priv->shortcuts);
+	g_free (hfav->priv->public_uri);
+	g_free (hfav->priv->shortcuts_uri);
+	g_free (hfav->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (exchange_hierarchy_favorites, ExchangeHierarchyFavorites, class_init, init, PARENT_TYPE)
+
+static void
+add_hrefs (ExchangeHierarchy *hier, EFolder *folder, gpointer hrefs)
+{
+	g_ptr_array_add (hrefs, (gchar *)e2k_uri_path (e_folder_exchange_get_internal_uri (folder)));
+}
+
+static const gchar *shortcuts_props[] = {
+	PR_FAV_DISPLAY_NAME,		/* PR_DISPLAY_NAME of referent */
+	PR_FAV_DISPLAY_ALIAS,		/* if set, user-chosen display name */
+	PR_FAV_PUBLIC_SOURCE_KEY,	/* PR_SOURCE_KEY of referent */
+	PR_FAV_PARENT_SOURCE_KEY,	/* PR_FAV_PUBLIC_SOURCE_KEY of parent */
+	PR_FAV_LEVEL_MASK		/* depth in hierarchy (first level is 1) */
+};
+static const gint n_shortcuts_props = G_N_ELEMENTS (shortcuts_props);
+
+static GPtrArray *
+get_hrefs (ExchangeHierarchySomeDAV *hsd)
+{
+	ExchangeHierarchy *hier = EXCHANGE_HIERARCHY (hsd);
+	ExchangeHierarchyFavorites *hfav = EXCHANGE_HIERARCHY_FAVORITES (hsd);
+	E2kContext *ctx = exchange_account_get_context (hier->account);
+	GPtrArray *hrefs;
+	E2kResultIter *iter;
+	E2kResult *result, *results;
+	E2kHTTPStatus status;
+	GByteArray *source_key;
+	const gchar *prop = E2K_PR_DAV_HREF, *shortcut_uri;
+	gchar *perm_url, *folder_uri;
+	gint i, nresults = 0, mode;
+
+	hrefs = g_ptr_array_new ();
+
+	exchange_account_is_offline (hier->account, &mode);
+	if (mode != ONLINE_MODE) {
+		exchange_hierarchy_webdav_offline_scan_subtree (EXCHANGE_HIERARCHY (hfav), add_hrefs, hrefs);
+		return hrefs;
+	}
+	/* Scan the shortcut links and use PROPFIND to resolve the
+	 * permanent_urls. Unfortunately it doesn't seem to be possible
+	 * to BPROPFIND a group of permanent_urls.
+	 */
+	iter = e2k_context_search_start (ctx, NULL, hfav->priv->shortcuts_uri,
+					 shortcuts_props, n_shortcuts_props,
+					 NULL, NULL, TRUE);
+	while ((result = e2k_result_iter_next (iter))) {
+		shortcut_uri = result->href;
+		source_key = e2k_properties_get_prop (result->props, PR_FAV_PUBLIC_SOURCE_KEY);
+		if (!source_key)
+			continue;
+
+		perm_url = e2k_entryid_to_permanenturl (source_key, hfav->priv->public_uri);
+
+		status = e2k_context_propfind (ctx, NULL, perm_url,
+					       &prop, 1, &results, &nresults);
+		if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status) && nresults) {
+			folder_uri = g_strdup (results[0].href);
+			g_ptr_array_add (hrefs, folder_uri);
+			g_hash_table_insert (hfav->priv->shortcuts,
+					     g_strdup (folder_uri),
+					     g_strdup (shortcut_uri));
+			e2k_results_free (results, nresults);
+		}
+
+		g_free (perm_url);
+	}
+
+	status = e2k_result_iter_free (iter);
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		/* FIXME: need to be able to return an error */
+		for (i = 0; i < hrefs->len; i++)
+			g_free (hrefs->pdata[i]);
+		g_ptr_array_free (hrefs, TRUE);
+		hrefs = NULL;
+	}
+
+	return hrefs;
+}
+/**
+ * exchange_hierarchy_favorites_is_added:
+ * @hier: the hierarchy
+ * @folder: the Public Folder to check in the favorites tree
+ *
+ * Checks if @folder is present in the favorites hierarchy
+ *
+ * Return value: TRUE if @folder is already added as a favorite.
+ **/
+
+gboolean
+exchange_hierarchy_favorites_is_added (ExchangeHierarchy *hier, EFolder *folder)
+{
+	ExchangeHierarchyFavorites *hfav =
+		EXCHANGE_HIERARCHY_FAVORITES (hier);
+	const gchar *folder_uri, *shortcut_uri;
+
+	folder_uri = e_folder_exchange_get_internal_uri (folder);
+	shortcut_uri = g_hash_table_lookup (hfav->priv->shortcuts, folder_uri);
+
+	return shortcut_uri ? TRUE : FALSE;
+}
+
+static ExchangeAccountFolderResult
+remove_folder (ExchangeHierarchy *hier, EFolder *folder)
+{
+	ExchangeHierarchyFavorites *hfav =
+		EXCHANGE_HIERARCHY_FAVORITES (hier);
+	const gchar *folder_uri, *shortcut_uri;
+	E2kHTTPStatus status;
+	const gchar *folder_type, *physical_uri;
+
+	folder_uri = e_folder_exchange_get_internal_uri (folder);
+	shortcut_uri = g_hash_table_lookup (hfav->priv->shortcuts, folder_uri);
+	if (!shortcut_uri)
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+
+	status = e2k_context_delete (
+		exchange_account_get_context (hier->account), NULL,
+		shortcut_uri);
+
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		g_hash_table_remove (hfav->priv->shortcuts, folder_uri);
+		exchange_hierarchy_removed_folder (hier, folder);
+
+		/* Temp Fix for remove fav folders. see #59168 */
+		/* remove ESources */
+		folder_type = e_folder_get_type_string (folder);
+		physical_uri = e_folder_get_physical_uri (folder);
+
+		if (strcmp (folder_type, "calendar") == 0) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_CALENDAR_FOLDER,
+					       physical_uri);
+		}
+		else if (strcmp (folder_type, "tasks") == 0) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_TASKS_FOLDER,
+					       physical_uri);
+		}
+		else if (strcmp (folder_type, "contacts") == 0) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_CONTACTS_FOLDER,
+					       physical_uri);
+		}
+	}
+
+	return exchange_hierarchy_webdav_status_to_folder_result (status);
+}
+
+/**
+ * exchange_hierarchy_favorites_add_folder:
+ * @hier: the hierarchy
+ * @folder: the Public Folder to add to the favorites tree
+ *
+ * Adds a new folder to @hier.
+ *
+ * Return value: the folder result.
+ **/
+ExchangeAccountFolderResult
+exchange_hierarchy_favorites_add_folder (ExchangeHierarchy *hier,
+					 EFolder *folder)
+{
+	ExchangeHierarchyFavorites *hfav;
+	E2kProperties *props;
+	E2kHTTPStatus status;
+	const gchar *folder_uri, *permanent_uri;
+	gchar *shortcut_uri;
+
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY (hier), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (e_folder_exchange_get_hierarchy (folder)->type == EXCHANGE_HIERARCHY_PUBLIC, EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	hfav = EXCHANGE_HIERARCHY_FAVORITES (hier);
+	permanent_uri = e_folder_exchange_get_permanent_uri (folder);
+
+	props = e2k_properties_new ();
+	e2k_properties_set_string (props, PR_FAV_DISPLAY_NAME,
+				   g_strdup (e_folder_get_name (folder)));
+	if (permanent_uri)
+		e2k_properties_set_binary (props, PR_FAV_PUBLIC_SOURCE_KEY,
+				   e2k_permanenturl_to_entryid (permanent_uri));
+	e2k_properties_set_int (props, PR_FAV_LEVEL_MASK, 1);
+
+	status = e2k_context_proppatch_new (
+		exchange_account_get_context (hier->account), NULL,
+		hfav->priv->shortcuts_uri,
+		e_folder_get_name (folder), NULL, NULL,
+		props, &shortcut_uri, NULL);
+	e2k_properties_free (props);
+
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		folder_uri = e_folder_exchange_get_internal_uri (folder);
+
+		g_hash_table_insert (hfav->priv->shortcuts,
+				     g_strdup (folder_uri), shortcut_uri);
+		return exchange_hierarchy_somedav_add_folder (EXCHANGE_HIERARCHY_SOMEDAV (hier),
+							      folder_uri);
+	} else
+		return exchange_hierarchy_webdav_status_to_folder_result (status);
+}
+
+/**
+ * exchange_hierarchy_favorites_new:
+ * @account: an #ExchangeAccount
+ * @hierarchy_name: the name of the hierarchy
+ * @physical_uri_prefix: prefix for physical URIs in this hierarchy
+ * @home_uri: the home URI of the owner's mailbox
+ * @public_uri: the URI of the public folder tree
+ * @owner_name: display name of the owner of the hierarchy
+ * @owner_email: email address of the owner of the hierarchy
+ * @source_uri: account source URI for folders in this hierarchy
+ *
+ * Creates a new Favorites hierarchy
+ *
+ * Return value: the new hierarchy.
+ **/
+ExchangeHierarchy *
+exchange_hierarchy_favorites_new (ExchangeAccount *account,
+				  const gchar *hierarchy_name,
+				  const gchar *physical_uri_prefix,
+				  const gchar *home_uri,
+				  const gchar *public_uri,
+				  const gchar *owner_name,
+				  const gchar *owner_email,
+				  const gchar *source_uri)
+{
+	ExchangeHierarchy *hier;
+	ExchangeHierarchyFavorites *hfav;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	hier = g_object_new (EXCHANGE_TYPE_HIERARCHY_FAVORITES, NULL);
+
+	hfav = (ExchangeHierarchyFavorites *)hier;
+	hfav->priv->public_uri = g_strdup (public_uri);
+	hfav->priv->shortcuts_uri = e2k_uri_concat (home_uri, "NON_IPM_SUBTREE/Shortcuts");
+
+	exchange_hierarchy_webdav_construct (EXCHANGE_HIERARCHY_WEBDAV (hier),
+					     account,
+					     EXCHANGE_HIERARCHY_FAVORITES,
+					     hierarchy_name,
+					     physical_uri_prefix,
+					     public_uri,
+					     owner_name, owner_email,
+					     source_uri,
+					     TRUE);
+	return hier;
+}
diff --git a/server/storage/exchange-hierarchy-favorites.h b/server/storage/exchange-hierarchy-favorites.h
new file mode 100644
index 0000000..798f287
--- /dev/null
+++ b/server/storage/exchange-hierarchy-favorites.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_HIERARCHY_FAVORITES_H__
+#define __EXCHANGE_HIERARCHY_FAVORITES_H__
+
+#include "exchange-hierarchy-somedav.h"
+
+G_BEGIN_DECLS
+
+#define EXCHANGE_TYPE_HIERARCHY_FAVORITES            (exchange_hierarchy_favorites_get_type ())
+#define EXCHANGE_HIERARCHY_FAVORITES(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_HIERARCHY_FAVORITES, ExchangeHierarchyFavorites))
+#define EXCHANGE_HIERARCHY_FAVORITES_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_HIERARCHY_FAVORITES, ExchangeHierarchyFavoritesClass))
+#define EXCHANGE_IS_HIERARCHY_FAVORITES(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_FAVORITES))
+#define EXCHANGE_IS_HIERARCHY_FAVORITES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_FAVORITES))
+
+struct _ExchangeHierarchyFavorites {
+	ExchangeHierarchySomeDAV parent;
+
+	ExchangeHierarchyFavoritesPrivate *priv;
+};
+
+struct _ExchangeHierarchyFavoritesClass {
+	ExchangeHierarchySomeDAVClass parent_class;
+
+};
+
+GType              exchange_hierarchy_favorites_get_type (void);
+
+ExchangeHierarchy *exchange_hierarchy_favorites_new (ExchangeAccount *account,
+						     const gchar *hierarchy_name,
+						     const gchar *physical_uri_prefix,
+						     const gchar *home_uri,
+						     const gchar *public_uri,
+						     const gchar *owner_name,
+						     const gchar *owner_email,
+						     const gchar *source_uri);
+
+ExchangeAccountFolderResult  exchange_hierarchy_favorites_add_folder (ExchangeHierarchy *hier,
+								      EFolder *folder);
+
+gboolean exchange_hierarchy_favorites_is_added (ExchangeHierarchy *hier,
+						EFolder *folder);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_HIERARCHY_FAVORITES_H__ */
diff --git a/server/storage/exchange-hierarchy-foreign.c b/server/storage/exchange-hierarchy-foreign.c
new file mode 100644
index 0000000..69bf785
--- /dev/null
+++ b/server/storage/exchange-hierarchy-foreign.c
@@ -0,0 +1,598 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* ExchangeHierarchyForeign: class for a hierarchy consisting of a
+ * selected subset of folders from another user's mailbox.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "libedataserver/e-source-list.h"
+#include "libedataserver/e-xml-hash-utils.h"
+#include "libedataserver/e-xml-utils.h"
+
+#include "e-folder-exchange.h"
+#include "e2k-propnames.h"
+#include "e2k-types.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+#include "exchange-account.h"
+#include "exchange-esource.h"
+#include "exchange-hierarchy-foreign.h"
+#include "exchange-types.h"
+
+struct _ExchangeHierarchyForeignPrivate {
+	GMutex *hide_private_lock;
+	gboolean checked_hide_private;
+};
+
+extern const gchar *exchange_localfreebusy_path;
+
+#define PARENT_TYPE EXCHANGE_TYPE_HIERARCHY_SOMEDAV
+static ExchangeHierarchySomeDAVClass *parent_class = NULL;
+
+static GPtrArray *get_hrefs (ExchangeHierarchySomeDAV *hsd);
+static ExchangeAccountFolderResult create_folder (ExchangeHierarchy *hier,
+						  EFolder *parent,
+						  const gchar *name,
+						  const gchar *type);
+static ExchangeAccountFolderResult remove_folder (ExchangeHierarchy *hier,
+						  EFolder *folder);
+static ExchangeAccountFolderResult scan_subtree (ExchangeHierarchy *hier,
+						 EFolder *folder,
+						 gint mode);
+static void finalize (GObject *object);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	ExchangeHierarchyClass *hierarchy_class =
+		EXCHANGE_HIERARCHY_CLASS (object_class);
+	ExchangeHierarchySomeDAVClass *somedav_class =
+		EXCHANGE_HIERARCHY_SOMEDAV_CLASS (object_class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* virtual method override */
+	object_class->finalize = finalize;
+
+	hierarchy_class->create_folder  = create_folder;
+	hierarchy_class->remove_folder  = remove_folder;
+	hierarchy_class->scan_subtree   = scan_subtree;
+
+	somedav_class->get_hrefs        = get_hrefs;
+}
+
+static void
+init (GObject *object)
+{
+	ExchangeHierarchyForeign *hfor = EXCHANGE_HIERARCHY_FOREIGN (object);
+	ExchangeHierarchy *hier = EXCHANGE_HIERARCHY (object);
+
+	hfor->priv = g_new0 (ExchangeHierarchyForeignPrivate, 1);
+	hfor->priv->hide_private_lock = g_mutex_new ();
+	hier->hide_private_items = TRUE;
+}
+
+static void
+finalize (GObject *object)
+{
+	ExchangeHierarchyForeign *hfor = EXCHANGE_HIERARCHY_FOREIGN (object);
+
+	g_mutex_free (hfor->priv->hide_private_lock);
+	g_free (hfor->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (exchange_hierarchy_foreign, ExchangeHierarchyForeign, class_init, init, PARENT_TYPE)
+
+static const gchar *privacy_props[] = {
+	PR_DELEGATES_ENTRYIDS,
+	PR_DELEGATES_SEE_PRIVATE,
+};
+static const gint n_privacy_props = sizeof (privacy_props) / sizeof (privacy_props[0]);
+
+static void
+check_hide_private (ExchangeHierarchy *hier)
+{
+	ExchangeHierarchyForeign *hfor = EXCHANGE_HIERARCHY_FOREIGN (hier);
+	E2kContext *ctx;
+	E2kHTTPStatus status;
+	E2kResult *results;
+	gint nresults = 0, i;
+	GPtrArray *entryids, *privflags;
+	GByteArray *entryid;
+	const gchar *my_dn, *delegate_dn;
+	gchar *uri;
+
+	g_mutex_lock (hfor->priv->hide_private_lock);
+
+	if (hfor->priv->checked_hide_private) {
+		g_mutex_unlock (hfor->priv->hide_private_lock);
+		return;
+	}
+
+	uri = e2k_uri_concat (hier->account->home_uri,
+			      "NON_IPM_SUBTREE/Freebusy%20Data/LocalFreebusy.EML");
+	ctx = exchange_account_get_context (hier->account);
+
+	status = e2k_context_propfind (ctx, NULL, uri,
+				       privacy_props, n_privacy_props,
+				       &results, &nresults);
+	g_free (uri);
+
+	hfor->priv->checked_hide_private = TRUE;
+
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status) || nresults == 0) {
+		g_mutex_unlock (hfor->priv->hide_private_lock);
+		return;
+	}
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (results[0].status) ||
+	    !results[0].props || nresults > 1) {
+		e2k_results_free (results, nresults);
+		g_mutex_unlock (hfor->priv->hide_private_lock);
+		return;
+	}
+
+	entryids  = e2k_properties_get_prop (results[0].props,
+					     PR_DELEGATES_ENTRYIDS);
+	privflags = e2k_properties_get_prop (results[0].props,
+					     PR_DELEGATES_SEE_PRIVATE);
+	if (entryids && privflags) {
+		my_dn = hier->account->legacy_exchange_dn;
+		for (i = 0; i < entryids->len && i < privflags->len; i++) {
+			entryid = entryids->pdata[i];
+			delegate_dn = e2k_entryid_to_dn (entryid);
+
+			if (delegate_dn &&
+			    !g_ascii_strcasecmp (delegate_dn, my_dn) &&
+			    privflags->pdata[i] &&
+			    atoi (privflags->pdata[i]))
+				hier->hide_private_items = FALSE;
+			break;
+		}
+	}
+
+	e2k_results_free (results, nresults);
+	g_mutex_unlock (hfor->priv->hide_private_lock);
+}
+
+static void
+remove_all_cb (ExchangeHierarchy *hier, EFolder *folder, gpointer user_data)
+{
+	exchange_hierarchy_removed_folder (hier, folder);
+}
+
+static void
+hierarchy_foreign_cleanup (ExchangeHierarchy *hier)
+{
+	gchar *mf_path;
+
+	exchange_hierarchy_webdav_offline_scan_subtree (hier, remove_all_cb,
+							NULL);
+
+	mf_path = e_folder_exchange_get_storage_file (hier->toplevel, "hierarchy.xml");
+	g_unlink (mf_path);
+	g_free (mf_path);
+
+	exchange_hierarchy_removed_folder (hier, hier->toplevel);
+}
+
+static const gchar *folder_props[] = {
+	E2K_PR_EXCHANGE_FOLDER_CLASS,
+	E2K_PR_HTTPMAIL_UNREAD_COUNT,
+	E2K_PR_DAV_DISPLAY_NAME,
+	PR_ACCESS
+};
+static const gint n_folder_props = sizeof (folder_props) / sizeof (folder_props[0]);
+
+static ExchangeAccountFolderResult
+find_folder (ExchangeHierarchy *hier, const gchar *uri, EFolder **folder_out)
+{
+	ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
+	E2kContext *ctx = exchange_account_get_context (hier->account);
+	E2kHTTPStatus status;
+	E2kResult *results;
+	gint nresults = 0;
+	EFolder *folder;
+	const gchar *access;
+
+	status = e2k_context_propfind (ctx, NULL, uri,
+				       folder_props, n_folder_props,
+				       &results, &nresults);
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status))
+		return exchange_hierarchy_webdav_status_to_folder_result (status);
+
+	if (nresults == 0)
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+
+	access = e2k_properties_get_prop (results[0].props, PR_ACCESS);
+	if (!access || !atoi (access)) {
+		e2k_results_free (results, nresults);
+		return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+	}
+
+	folder = exchange_hierarchy_webdav_parse_folder (hwd, hier->toplevel,
+							 &results[0]);
+	e2k_results_free (results, nresults);
+
+	if (!folder)
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+
+	exchange_hierarchy_new_folder (hier, folder);
+
+	if (folder_out)
+		*folder_out = folder;
+	else
+		g_object_unref (folder);
+	return EXCHANGE_ACCOUNT_FOLDER_OK;
+}
+
+static struct {
+	const gchar *name, *prop;
+} std_folders[] = {
+	{ N_("Calendar"),	E2K_PR_STD_FOLDER_CALENDAR },
+	{ N_("Contacts"),	E2K_PR_STD_FOLDER_CONTACTS },
+	{ N_("Deleted Items"),	E2K_PR_STD_FOLDER_DELETED_ITEMS },
+	{ N_("Drafts"),		E2K_PR_STD_FOLDER_DRAFTS },
+	{ N_("Inbox"),		E2K_PR_STD_FOLDER_INBOX },
+	{ N_("Journal"),	E2K_PR_STD_FOLDER_JOURNAL },
+	{ N_("Notes"),		E2K_PR_STD_FOLDER_NOTES },
+	{ N_("Outbox"),		E2K_PR_STD_FOLDER_OUTBOX },
+	{ N_("Sent Items"),	E2K_PR_STD_FOLDER_SENT_ITEMS },
+	{ N_("Tasks"),		E2K_PR_STD_FOLDER_TASKS }
+};
+static ExchangeAccountFolderResult
+create_internal (ExchangeHierarchy *hier, EFolder *parent,
+		 const gchar *name, const gchar *type, EFolder **folder_out)
+{
+	ExchangeAccountFolderResult result;
+	gchar *literal_uri = NULL, *standard_uri = NULL;
+	const gchar *home_uri;
+	gint i;
+
+	/* For now, no nesting */
+	if (parent != hier->toplevel || strchr (name + 1, '/'))
+		return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
+
+	check_hide_private (hier);
+
+	home_uri = e_folder_exchange_get_internal_uri (hier->toplevel);
+	literal_uri = e2k_uri_concat (home_uri, name);
+	if (exchange_account_get_folder (hier->account, literal_uri)) {
+		g_free (literal_uri);
+		if (exchange_hierarchy_is_empty (hier))
+			hierarchy_foreign_cleanup (hier);
+		return EXCHANGE_ACCOUNT_FOLDER_ALREADY_EXISTS;
+	}
+
+	for (i = 0; i < G_N_ELEMENTS (std_folders); i++) {
+		if (g_ascii_strcasecmp (std_folders[i].name, name) != 0 &&
+		    g_utf8_collate (_(std_folders[i].name), name) != 0)
+			continue;
+
+		standard_uri = exchange_account_get_standard_uri_for (
+			hier->account, home_uri, std_folders[i].prop);
+		if (!standard_uri)
+			break;
+		if (!strcmp (literal_uri, standard_uri)) {
+			g_free (standard_uri);
+			standard_uri = NULL;
+			break;
+		}
+
+		if (exchange_account_get_folder (hier->account, standard_uri)) {
+			g_free (standard_uri);
+			g_free (literal_uri);
+			if (exchange_hierarchy_is_empty (hier))
+				hierarchy_foreign_cleanup (hier);
+			return EXCHANGE_ACCOUNT_FOLDER_ALREADY_EXISTS;
+		}
+
+		break;
+	}
+
+	result = find_folder (hier, literal_uri, folder_out);
+	if (result == EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST && standard_uri)
+		result = find_folder (hier, standard_uri, folder_out);
+
+	g_free (literal_uri);
+	g_free (standard_uri);
+
+	/* If the hierarchy is now empty, then we must have just been
+	 * created but then the add failed. So remove it again.
+	 */
+	if (exchange_hierarchy_is_empty (hier))
+		hierarchy_foreign_cleanup (hier);
+
+	return result;
+}
+
+static ExchangeAccountFolderResult
+create_folder (ExchangeHierarchy *hier, EFolder *parent,
+	       const gchar *name, const gchar *type)
+{
+	return create_internal (hier, parent, name, type, NULL);
+}
+
+static ExchangeAccountFolderResult
+remove_folder (ExchangeHierarchy *hier, EFolder *folder)
+{
+	const gchar *folder_type, *physical_uri;
+
+	/* Temp Fix for remove fav folders. see #59168 */
+        /* remove ESources */
+        folder_type = e_folder_get_type_string (folder);
+        physical_uri = e_folder_get_physical_uri (folder);
+
+        if (strcmp (folder_type, "calendar") == 0) {
+                remove_folder_esource (hier->account,
+				       EXCHANGE_CALENDAR_FOLDER,
+				       physical_uri);
+        }
+        else if (strcmp (folder_type, "tasks") == 0) {
+                remove_folder_esource (hier->account,
+				       EXCHANGE_TASKS_FOLDER,
+				       physical_uri);
+        }
+        else if (strcmp (folder_type, "contacts") == 0) {
+                remove_folder_esource (hier->account,
+				       EXCHANGE_CONTACTS_FOLDER,
+				       physical_uri);
+	}
+	if (folder != hier->toplevel)
+		exchange_hierarchy_removed_folder (hier, folder);
+
+	if (folder == hier->toplevel || exchange_hierarchy_is_empty (hier))
+		hierarchy_foreign_cleanup (hier);
+
+	return EXCHANGE_ACCOUNT_FOLDER_OK;
+}
+
+static ExchangeAccountFolderResult
+scan_subtree (ExchangeHierarchy *hier, EFolder *folder, gint mode)
+{
+	ExchangeAccountFolderResult folder_result;
+
+	check_hide_private (hier);
+
+	folder_result = EXCHANGE_HIERARCHY_CLASS (parent_class)->scan_subtree (hier, folder, mode);
+
+	if (exchange_hierarchy_is_empty (hier))
+		hierarchy_foreign_cleanup (hier);
+
+	return folder_result;
+}
+
+static void
+add_href (ExchangeHierarchy *hier, EFolder *folder, gpointer hrefs)
+{
+	gchar *uri = g_strdup (e_folder_exchange_get_internal_uri (folder));
+
+	g_ptr_array_add (hrefs, (gpointer) uri);
+}
+
+static GPtrArray *
+get_hrefs (ExchangeHierarchySomeDAV *hsd)
+{
+	GPtrArray *hrefs;
+
+	hrefs = g_ptr_array_new ();
+	exchange_hierarchy_webdav_offline_scan_subtree (EXCHANGE_HIERARCHY (hsd), add_href, hrefs);
+	return hrefs;
+}
+
+/**
+ * exchange_hierarchy_foreign_add_folder:
+ * @hier: the hierarchy
+ * @folder_name: the name of the folder to add
+ * @folder: on successful return, the created folder
+ *
+ * Adds a new folder to @hier.
+ *
+ * Return value: the folder result.
+ **/
+ExchangeAccountFolderResult
+exchange_hierarchy_foreign_add_folder (ExchangeHierarchy *hier,
+				       const gchar *folder_name,
+				       EFolder **folder)
+{
+	ExchangeAccountFolderResult result;
+	const gchar *folder_type = NULL;
+	const gchar *physical_uri = NULL;
+	gchar *new_folder_name;
+	guint folder_mask = 0;
+
+	result =  create_internal (hier, hier->toplevel, folder_name, NULL, folder);
+	if (result == EXCHANGE_ACCOUNT_FOLDER_OK) {
+		/* Add the esources */
+		folder_type = e_folder_get_type_string (*folder);
+		physical_uri = e_folder_get_physical_uri (*folder);
+		new_folder_name = g_strdup_printf("%s's %s",
+					hier->owner_name, folder_name);
+
+		if (!(strcmp (folder_type, "calendar")) ||
+		!(strcmp (folder_type, "calendar/public"))) {
+			folder_mask = EXCHANGE_CALENDAR_FOLDER | FORIEGN_FOLDER_FLAG;
+			add_folder_esource (hier->account,
+					    folder_mask,
+					    new_folder_name,
+					    physical_uri);
+		}
+		else if (!(strcmp (folder_type, "tasks")) ||
+			 !(strcmp (folder_type, "tasks/public"))) {
+			folder_mask = EXCHANGE_TASKS_FOLDER | FORIEGN_FOLDER_FLAG;
+				add_folder_esource (hier->account,
+						    folder_mask,
+						    new_folder_name,
+						    physical_uri);
+		}
+		else if (!(strcmp (folder_type, "contacts")) ||
+			 !(strcmp (folder_type, "contacts/public")) ||
+			 !(strcmp (folder_type, "contacts/ldap"))) {
+				folder_mask = EXCHANGE_CONTACTS_FOLDER | FORIEGN_FOLDER_FLAG;
+				add_folder_esource (hier->account,
+						    folder_mask,
+						    new_folder_name,
+						    physical_uri);
+		}
+		g_free (new_folder_name);
+	}
+	return result;
+}
+
+static ExchangeHierarchy *
+hierarchy_foreign_new (ExchangeAccount *account,
+		       const gchar *hierarchy_name,
+		       const gchar *physical_uri_prefix,
+		       const gchar *internal_uri_prefix,
+		       const gchar *owner_name,
+		       const gchar *owner_email,
+		       const gchar *source_uri)
+{
+	ExchangeHierarchyForeign *hfor;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	hfor = g_object_new (EXCHANGE_TYPE_HIERARCHY_FOREIGN, NULL);
+
+	exchange_hierarchy_webdav_construct (EXCHANGE_HIERARCHY_WEBDAV (hfor),
+					     account,
+					     EXCHANGE_HIERARCHY_FOREIGN,
+					     hierarchy_name,
+					     physical_uri_prefix,
+					     internal_uri_prefix,
+					     owner_name, owner_email,
+					     source_uri,
+					     FALSE);
+
+	return EXCHANGE_HIERARCHY (hfor);
+}
+
+/**
+ * exchange_hierarchy_foreign_new:
+ * @account: an #ExchangeAccount
+ * @hierarchy_name: the name of the hierarchy
+ * @physical_uri_prefix: the prefix of physical URIs in this hierarchy
+ * @internal_uri_prefix: the prefix of internal (http) URIs in this hierarchy
+ * @owner_name: display name of the owner of the hierarchy
+ * @owner_email: email address of the owner of the hierarchy
+ * @source_uri: evolution-mail source uri for the hierarchy
+ *
+ * Creates a new (initially empty) hierarchy for another user's
+ * folders.
+ *
+ * Return value: the new hierarchy.
+ **/
+ExchangeHierarchy *
+exchange_hierarchy_foreign_new (ExchangeAccount *account,
+				const gchar *hierarchy_name,
+				const gchar *physical_uri_prefix,
+				const gchar *internal_uri_prefix,
+				const gchar *owner_name,
+				const gchar *owner_email,
+				const gchar *source_uri)
+{
+	ExchangeHierarchy *hier;
+	gchar *mf_path;
+	GHashTable *props;
+	xmlDoc *doc;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	hier = hierarchy_foreign_new (account, hierarchy_name,
+				      physical_uri_prefix,
+				      internal_uri_prefix,
+				      owner_name, owner_email,
+				      source_uri);
+
+	props = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (props, (gpointer) "name", (gchar *)hierarchy_name);
+	g_hash_table_insert (props, (gpointer) "physical_uri_prefix",
+			     (gchar *)physical_uri_prefix);
+	g_hash_table_insert (props, (gpointer) "internal_uri_prefix",
+			     (gchar *)internal_uri_prefix);
+	g_hash_table_insert (props, (gpointer) "owner_name", (gchar *)owner_name);
+	g_hash_table_insert (props, (gpointer) "owner_email", (gchar *)owner_email);
+	g_hash_table_insert (props, (gpointer) "source_uri", (gchar *)source_uri);
+
+	mf_path = e_folder_exchange_get_storage_file (hier->toplevel, "hierarchy.xml");
+	doc = e_xml_from_hash (props, E_XML_HASH_TYPE_PROPERTY,
+			       "foreign-hierarchy");
+	e_xml_save_file (mf_path, doc);
+	g_hash_table_destroy (props);
+	g_free (mf_path);
+	xmlFreeDoc (doc);
+
+	return hier;
+}
+
+/**
+ * exchange_hierarchy_foreign_new_from_dir:
+ * @account: an #ExchangeAccount
+ * @folder_path: pathname to a directory containing a hierarchy.xml file
+ *
+ * Recreates a new hierarchy from saved values.
+ *
+ * Return value: the new hierarchy.
+ **/
+ExchangeHierarchy *
+exchange_hierarchy_foreign_new_from_dir (ExchangeAccount *account,
+					 const gchar *folder_path)
+{
+	ExchangeHierarchy *hier;
+	gchar *mf_path;
+	GHashTable *props;
+	xmlDoc *doc;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+	g_return_val_if_fail (folder_path != NULL, NULL);
+
+	mf_path = g_build_filename (folder_path, "hierarchy.xml", NULL);
+
+	doc = e_xml_parse_file (mf_path);
+	g_free (mf_path);
+
+	if (!doc)
+		return NULL;
+
+	props = e_xml_to_hash (doc, E_XML_HASH_TYPE_PROPERTY);
+	xmlFreeDoc (doc);
+
+	hier = hierarchy_foreign_new (account,
+				      g_hash_table_lookup (props, "name"),
+				      g_hash_table_lookup (props, "physical_uri_prefix"),
+				      g_hash_table_lookup (props, "internal_uri_prefix"),
+				      g_hash_table_lookup (props, "owner_name"),
+				      g_hash_table_lookup (props, "owner_email"),
+				      g_hash_table_lookup (props, "source_uri"));
+
+	e_xml_destroy_hash (props);
+	return hier;
+}
diff --git a/server/storage/exchange-hierarchy-foreign.h b/server/storage/exchange-hierarchy-foreign.h
new file mode 100644
index 0000000..eee2ee9
--- /dev/null
+++ b/server/storage/exchange-hierarchy-foreign.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_HIERARCHY_FOREIGN_H__
+#define __EXCHANGE_HIERARCHY_FOREIGN_H__
+
+#include "exchange-hierarchy-somedav.h"
+
+G_BEGIN_DECLS
+
+#define EXCHANGE_TYPE_HIERARCHY_FOREIGN            (exchange_hierarchy_foreign_get_type ())
+#define EXCHANGE_HIERARCHY_FOREIGN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_HIERARCHY_FOREIGN, ExchangeHierarchyForeign))
+#define EXCHANGE_HIERARCHY_FOREIGN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_HIERARCHY_FOREIGN, ExchangeHierarchyForeignClass))
+#define EXCHANGE_IS_HIERARCHY_FOREIGN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_FOREIGN))
+#define EXCHANGE_IS_HIERARCHY_FOREIGN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_FOREIGN))
+
+typedef struct _ExchangeHierarchyForeignPrivate  ExchangeHierarchyForeignPrivate;
+
+struct _ExchangeHierarchyForeign {
+	ExchangeHierarchySomeDAV parent;
+
+	ExchangeHierarchyForeignPrivate *priv;
+};
+
+struct _ExchangeHierarchyForeignClass {
+	ExchangeHierarchySomeDAVClass parent_class;
+
+};
+
+typedef struct _ExchangeHierarchyForeignClass ExchangeHierarchyForeignClass;
+typedef struct _ExchangeHierarchyForeign ExchangeHierarchyForeign;
+
+GType              exchange_hierarchy_foreign_get_type (void);
+
+ExchangeHierarchy *exchange_hierarchy_foreign_new          (ExchangeAccount *account,
+							    const gchar *hierarchy_name,
+							    const gchar *physical_uri_prefix,
+							    const gchar *internal_uri_prefix,
+							    const gchar *owner_name,
+							    const gchar *owner_email,
+							    const gchar *source_uri);
+ExchangeHierarchy *exchange_hierarchy_foreign_new_from_dir (ExchangeAccount *account,
+							    const gchar *folder_path);
+
+ExchangeAccountFolderResult  exchange_hierarchy_foreign_add_folder (ExchangeHierarchy *hier,
+								    const gchar *folder_name,
+								    EFolder **folder);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_HIERARCHY_FOREIGN_H__ */
diff --git a/server/storage/exchange-hierarchy-gal.c b/server/storage/exchange-hierarchy-gal.c
new file mode 100644
index 0000000..32b8443
--- /dev/null
+++ b/server/storage/exchange-hierarchy-gal.c
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* ExchangeHierarchyGAL: class for the Global Address List hierarchy
+ * of an Exchange storage. (Currently the "hierarchy" only contains
+ * a single folder, but see bugzilla #21029.)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "exchange-hierarchy-gal.h"
+#include "exchange-account.h"
+#include "e-folder-exchange.h"
+#include "exchange-esource.h"
+
+#include <libedataserver/e-source-list.h>
+
+#define PARENT_TYPE EXCHANGE_TYPE_HIERARCHY
+
+E2K_MAKE_TYPE (exchange_hierarchy_gal, ExchangeHierarchyGAL, NULL, NULL, PARENT_TYPE)
+
+ExchangeHierarchy *
+exchange_hierarchy_gal_new (ExchangeAccount *account,
+			    const gchar *hierarchy_name,
+			    const gchar *physical_uri_prefix)
+{
+	ExchangeHierarchy *hier;
+	EFolder *toplevel;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+	g_return_val_if_fail (hierarchy_name != NULL, NULL);
+	g_return_val_if_fail (physical_uri_prefix != NULL, NULL);
+
+	hier = g_object_new (EXCHANGE_TYPE_HIERARCHY_GAL, NULL);
+
+	toplevel = e_folder_exchange_new (hier, hierarchy_name,
+					  "contacts/ldap", NULL,
+					  physical_uri_prefix,
+					  physical_uri_prefix);
+	exchange_hierarchy_construct (hier, account,
+				      EXCHANGE_HIERARCHY_GAL, toplevel,
+				      NULL, NULL, NULL);
+	/* Add ESource */
+	add_folder_esource (hier->account, EXCHANGE_CONTACTS_FOLDER,
+			    hierarchy_name, physical_uri_prefix);
+
+	g_object_unref (toplevel);
+
+	return hier;
+}
diff --git a/server/storage/exchange-hierarchy-gal.h b/server/storage/exchange-hierarchy-gal.h
new file mode 100644
index 0000000..bcc2fe9
--- /dev/null
+++ b/server/storage/exchange-hierarchy-gal.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_HIERARCHY_GAL_H__
+#define __EXCHANGE_HIERARCHY_GAL_H__
+
+#include "exchange-hierarchy.h"
+
+G_BEGIN_DECLS
+
+#define EXCHANGE_TYPE_HIERARCHY_GAL            (exchange_hierarchy_gal_get_type ())
+#define EXCHANGE_HIERARCHY_GAL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_HIERARCHY_GAL, ExchangeHierarchyGAL))
+#define EXCHANGE_HIERARCHY_GAL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_HIERARCHY_GAL, ExchangeHierarchyGALClass))
+#define EXCHANGE_IS_HIERARCHY_GAL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_GAL))
+#define EXCHANGE_IS_HIERARCHY_GAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_GAL))
+
+struct _ExchangeHierarchyGAL {
+	ExchangeHierarchy parent;
+
+};
+
+struct _ExchangeHierarchyGALClass {
+	ExchangeHierarchyClass parent_class;
+
+};
+
+GType                 exchange_hierarchy_gal_get_type (void);
+
+ExchangeHierarchy    *exchange_hierarchy_gal_new      (ExchangeAccount *account,
+						       const gchar *hierarchy_name,
+						       const gchar *physical_uri_prefix);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_HIERARCHY_GAL_H__ */
diff --git a/server/storage/exchange-hierarchy-somedav.c b/server/storage/exchange-hierarchy-somedav.c
new file mode 100644
index 0000000..8b93336
--- /dev/null
+++ b/server/storage/exchange-hierarchy-somedav.c
@@ -0,0 +1,259 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* ExchangeHierarchySomeDAV: class for a hierarchy consisting of a
+ * specific group of WebDAV folders
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "exchange-hierarchy-somedav.h"
+#include "exchange-account.h"
+#include "e-folder-exchange.h"
+#include "e2k-propnames.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+
+#include <libedataserver/e-xml-hash-utils.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct _ExchangeHierarchySomeDAVPrivate {
+	gboolean scanned;
+};
+
+enum {
+	HREF_UNREADABLE,
+	LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+#define PARENT_TYPE EXCHANGE_TYPE_HIERARCHY_WEBDAV
+static ExchangeHierarchyWebDAVClass *parent_class = NULL;
+
+static ExchangeAccountFolderResult scan_subtree (ExchangeHierarchy *hier,
+						 EFolder *folder,
+						 gint mode);
+static void finalize (GObject *object);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	ExchangeHierarchyClass *exchange_hierarchy_class =
+		EXCHANGE_HIERARCHY_CLASS (object_class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* virtual method override */
+	object_class->finalize = finalize;
+
+	exchange_hierarchy_class->scan_subtree   = scan_subtree;
+
+	/* signals */
+	signals[HREF_UNREADABLE] =
+		g_signal_new ("href_unreadable",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (ExchangeHierarchySomeDAVClass, href_unreadable),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_STRING);
+}
+
+static void
+init (GObject *object)
+{
+	ExchangeHierarchySomeDAV *hsd = EXCHANGE_HIERARCHY_SOMEDAV (object);
+
+	hsd->priv = g_new0 (ExchangeHierarchySomeDAVPrivate, 1);
+}
+
+static void
+finalize (GObject *object)
+{
+	ExchangeHierarchySomeDAV *hsd = EXCHANGE_HIERARCHY_SOMEDAV (object);
+
+	g_free (hsd->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (exchange_hierarchy_somedav, ExchangeHierarchySomeDAV, class_init, init, PARENT_TYPE)
+
+static inline gboolean
+folder_is_unreadable (E2kProperties *props)
+{
+	gchar *access;
+
+	access = e2k_properties_get_prop (props, PR_ACCESS);
+	return !access || !atoi (access);
+}
+
+static const gchar *folder_props[] = {
+	E2K_PR_EXCHANGE_FOLDER_CLASS,
+	E2K_PR_HTTPMAIL_UNREAD_COUNT,
+	E2K_PR_DAV_DISPLAY_NAME,
+	PR_ACCESS
+};
+static const gint n_folder_props = sizeof (folder_props) / sizeof (folder_props[0]);
+
+static ExchangeAccountFolderResult
+scan_subtree (ExchangeHierarchy *hier, EFolder *folder, gint mode)
+{
+	ExchangeHierarchySomeDAV *hsd = EXCHANGE_HIERARCHY_SOMEDAV (hier);
+	GPtrArray *hrefs;
+	E2kResultIter *iter;
+	E2kResult *result;
+	gint folders_returned=0, folders_added=0, i;
+	E2kHTTPStatus status;
+	ExchangeAccountFolderResult folder_result;
+	EFolder *iter_folder = NULL;
+
+	/* FIXME : Temporarily allow a rescan of the hierarchy. The proper fix
+	is to handle the folder list either in ExchangeAccount or in the
+	plugins/exchange backend separately by listening to signals.
+	if (hsd->priv->scanned || folder != hier->toplevel) */
+	if (folder != hier->toplevel)
+		return EXCHANGE_ACCOUNT_FOLDER_OK;
+	hsd->priv->scanned = TRUE;
+
+	if (mode == OFFLINE_MODE)
+		return EXCHANGE_ACCOUNT_FOLDER_OK;
+
+	hrefs = exchange_hierarchy_somedav_get_hrefs (hsd);
+	if (!hrefs)
+		return EXCHANGE_ACCOUNT_FOLDER_OK;
+	if (!hrefs->len) {
+		g_ptr_array_free (hrefs, TRUE);
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+	}
+
+	iter = e_folder_exchange_bpropfind_start (hier->toplevel, NULL,
+						  (const gchar **)hrefs->pdata,
+						  hrefs->len,
+						  folder_props,
+						  n_folder_props);
+
+	while ((result = e2k_result_iter_next (iter))) {
+		folders_returned++;
+
+		/* If you have "folder visible" permission but nothing
+		 * else, you'll be able to fetch properties, but not
+		 * see anything in the folder. In that case, PR_ACCESS
+		 * will be 0, and we ignore the folder.
+		 */
+		if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (result->status) ||
+		    folder_is_unreadable (result->props)) {
+			exchange_hierarchy_somedav_href_unreadable (hsd, result->href);
+			continue;
+		}
+
+		folders_added++;
+		iter_folder = exchange_hierarchy_webdav_parse_folder (
+			EXCHANGE_HIERARCHY_WEBDAV (hier),
+			hier->toplevel, result);
+		exchange_hierarchy_new_folder (hier, iter_folder);
+		g_object_unref (iter_folder);
+	}
+	status = e2k_result_iter_free (iter);
+
+	if (folders_returned == 0)
+		folder_result = EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+	else if (folders_added == 0)
+		folder_result = EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+	else
+		folder_result = exchange_hierarchy_webdav_status_to_folder_result (status);
+
+	for (i = 0; i < hrefs->len; i++)
+		g_free (hrefs->pdata[i]);
+	g_ptr_array_free (hrefs, TRUE);
+
+	return folder_result;
+}
+
+GPtrArray *
+exchange_hierarchy_somedav_get_hrefs (ExchangeHierarchySomeDAV *hsd)
+{
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY_SOMEDAV (hsd), NULL);
+
+	return EXCHANGE_GET_HIERARCHY_SOMEDAV_CLASS (hsd)->get_hrefs (hsd);
+}
+
+void
+exchange_hierarchy_somedav_href_unreadable (ExchangeHierarchySomeDAV *hsd,
+					    const gchar *href)
+{
+	g_return_if_fail (EXCHANGE_IS_HIERARCHY_SOMEDAV (hsd));
+	g_return_if_fail (href != NULL);
+
+	g_signal_emit (hsd, signals[HREF_UNREADABLE], 0, href);
+}
+
+ExchangeAccountFolderResult
+exchange_hierarchy_somedav_add_folder (ExchangeHierarchySomeDAV *hsd,
+				       const gchar *uri)
+{
+	ExchangeHierarchyWebDAV *hwd;
+	ExchangeHierarchy *hier;
+	E2kContext *ctx;
+	E2kHTTPStatus status;
+	E2kResult *results;
+	gint nresults = 0;
+	EFolder *folder;
+
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY_SOMEDAV (hsd),
+				EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (uri != NULL,
+				EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	hwd = EXCHANGE_HIERARCHY_WEBDAV (hsd);
+	hier = EXCHANGE_HIERARCHY (hsd);
+	ctx = exchange_account_get_context (hier->account);
+
+	status = e2k_context_propfind (ctx, NULL, uri,
+				       folder_props, n_folder_props,
+				       &results, &nresults);
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status))
+		return exchange_hierarchy_webdav_status_to_folder_result (status);
+
+	if (nresults == 0)
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+
+	if (folder_is_unreadable (results[0].props)) {
+		e2k_results_free (results, nresults);
+		return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+	}
+
+	folder = exchange_hierarchy_webdav_parse_folder (hwd, hier->toplevel,
+							 &results[0]);
+	e2k_results_free (results, nresults);
+
+	if (!folder)
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+
+	exchange_hierarchy_new_folder (hier, folder);
+	g_object_unref (folder);
+	return EXCHANGE_ACCOUNT_FOLDER_OK;
+}
diff --git a/server/storage/exchange-hierarchy-somedav.h b/server/storage/exchange-hierarchy-somedav.h
new file mode 100644
index 0000000..d2b292e
--- /dev/null
+++ b/server/storage/exchange-hierarchy-somedav.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_HIERARCHY_SOMEDAV_H__
+#define __EXCHANGE_HIERARCHY_SOMEDAV_H__
+
+#include "exchange-hierarchy-webdav.h"
+
+G_BEGIN_DECLS
+
+#define EXCHANGE_TYPE_HIERARCHY_SOMEDAV            (exchange_hierarchy_somedav_get_type ())
+#define EXCHANGE_HIERARCHY_SOMEDAV(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_HIERARCHY_SOMEDAV, ExchangeHierarchySomeDAV))
+#define EXCHANGE_HIERARCHY_SOMEDAV_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_HIERARCHY_SOMEDAV, ExchangeHierarchySomeDAVClass))
+#define EXCHANGE_IS_HIERARCHY_SOMEDAV(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_SOMEDAV))
+#define EXCHANGE_IS_HIERARCHY_SOMEDAV_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_SOMEDAV))
+#define EXCHANGE_GET_HIERARCHY_SOMEDAV_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EXCHANGE_TYPE_HIERARCHY_SOMEDAV, ExchangeHierarchySomeDAVClass))
+
+struct _ExchangeHierarchySomeDAV {
+	ExchangeHierarchyWebDAV parent;
+
+	ExchangeHierarchySomeDAVPrivate *priv;
+};
+
+struct _ExchangeHierarchySomeDAVClass {
+	ExchangeHierarchyWebDAVClass parent_class;
+
+	/* signals */
+	void (*href_unreadable) (ExchangeHierarchySomeDAV *hsd, const gchar *href);
+
+	/* methods */
+	GPtrArray *(*get_hrefs) (ExchangeHierarchySomeDAV *hsd);
+};
+
+GType exchange_hierarchy_somedav_get_type (void);
+
+GPtrArray *exchange_hierarchy_somedav_get_hrefs (ExchangeHierarchySomeDAV *hsd);
+ExchangeAccountFolderResult exchange_hierarchy_somedav_add_folder (ExchangeHierarchySomeDAV *hsd,
+								   const gchar *uri);
+
+/* signal emitter */
+void exchange_hierarchy_somedav_href_unreadable (ExchangeHierarchySomeDAV *hsd,
+						 const gchar *href);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_HIERARCHY_SOMEDAV_H__ */
diff --git a/server/storage/exchange-hierarchy-webdav.c b/server/storage/exchange-hierarchy-webdav.c
new file mode 100644
index 0000000..30315d5
--- /dev/null
+++ b/server/storage/exchange-hierarchy-webdav.c
@@ -0,0 +1,944 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* ExchangeHierarchyWebDAV: class for a normal WebDAV folder hierarchy
+ * in the Exchange storage. Eg, the "Personal Folders" and "Public
+ * Folders" hierarchies.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "libedataserverui/e-passwords.h"
+#include "libedataserver/e-source-list.h"
+
+#include "exchange-hierarchy-webdav.h"
+#include "exchange-account.h"
+#include "e-folder-exchange.h"
+#include "e2k-context.h"
+#include "e2k-path.h"
+#include "e2k-propnames.h"
+#include "e2k-restriction.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+#include "exchange-folder-size.h"
+#include "exchange-esource.h"
+
+#define d(x)
+
+#define URI_ENCODE_CHARS "@;:/?=."
+
+struct _ExchangeHierarchyWebDAVPrivate {
+	GHashTable *folders_by_internal_path;
+	gboolean deep_searchable;
+	gchar *trash_path;
+	gdouble total_folder_size;
+};
+
+#define PARENT_TYPE EXCHANGE_TYPE_HIERARCHY
+static ExchangeHierarchyClass *parent_class = NULL;
+
+static void folder_type_map_init (void);
+
+static void dispose (GObject *object);
+static void finalize (GObject *object);
+static gboolean is_empty (ExchangeHierarchy *hier);
+static void rescan (ExchangeHierarchy *hier);
+static ExchangeAccountFolderResult scan_subtree  (ExchangeHierarchy *hier,
+						  EFolder *folder,
+						  gint mode);
+static ExchangeAccountFolderResult create_folder (ExchangeHierarchy *hier,
+						  EFolder *parent,
+						  const gchar *name,
+						  const gchar *type);
+static ExchangeAccountFolderResult remove_folder (ExchangeHierarchy *hier,
+						  EFolder *folder);
+static ExchangeAccountFolderResult xfer_folder   (ExchangeHierarchy *hier,
+						  EFolder *source,
+						  EFolder *dest_parent,
+						  const gchar *dest_name,
+						  gboolean remove_source);
+
+static void hierarchy_new_folder (ExchangeHierarchy *hier, EFolder *folder,
+				  gpointer user_data);
+static void hierarchy_removed_folder (ExchangeHierarchy *hier, EFolder *folder,
+				      gpointer user_data);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	ExchangeHierarchyClass *exchange_hierarchy_class =
+		EXCHANGE_HIERARCHY_CLASS (object_class);
+
+	folder_type_map_init ();
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* virtual method override */
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
+
+	exchange_hierarchy_class->is_empty = is_empty;
+	exchange_hierarchy_class->rescan = rescan;
+	exchange_hierarchy_class->scan_subtree = scan_subtree;
+	exchange_hierarchy_class->create_folder = create_folder;
+	exchange_hierarchy_class->remove_folder = remove_folder;
+	exchange_hierarchy_class->xfer_folder = xfer_folder;
+}
+
+static void
+init (GObject *object)
+{
+	ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (object);
+
+	hwd->priv = g_new0 (ExchangeHierarchyWebDAVPrivate, 1);
+	hwd->priv->folders_by_internal_path = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_object_unref);
+	hwd->priv->total_folder_size = 0;
+
+	g_signal_connect (object, "new_folder",
+			  G_CALLBACK (hierarchy_new_folder), NULL);
+	g_signal_connect (object, "removed_folder",
+			  G_CALLBACK (hierarchy_removed_folder), NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (object);
+
+	g_hash_table_destroy (hwd->priv->folders_by_internal_path);
+	g_free (hwd->priv->trash_path);
+	g_free (hwd->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (exchange_hierarchy_webdav, ExchangeHierarchyWebDAV, class_init, init, PARENT_TYPE)
+
+typedef struct {
+	const gchar *contentclass, *component;
+	gboolean offline_supported;
+} ExchangeFolderType;
+
+static ExchangeFolderType folder_types[] = {
+	{ "IPF.Note", "mail", FALSE },
+	{ "IPF.Contact", "contacts", FALSE },
+	{ "IPF.Appointment", "calendar", FALSE },
+	{ "IPF.Task", "tasks", FALSE },
+	{ NULL, NULL }
+};
+static GHashTable *folder_type_map;
+
+static void
+folder_type_map_init (void)
+{
+	gint i;
+
+	folder_type_map = g_hash_table_new (g_str_hash, g_str_equal);
+	for (i = 0; folder_types[i].contentclass; i++) {
+		g_hash_table_insert (folder_type_map,
+				     (gpointer) folder_types[i].contentclass,
+				     &folder_types[i]);
+	}
+}
+
+/* We maintain the folders_by_internal_path hash table by listening
+ * to our own signal emissions. (This lets ExchangeHierarchyForeign
+ * remove its folders by just calling exchange_hierarchy_removed_folder.)
+ */
+static void
+hierarchy_new_folder (ExchangeHierarchy *hier, EFolder *folder,
+		      gpointer user_data)
+{
+	const gchar *internal_uri;
+	gchar *mf_path;
+
+	g_return_if_fail (E_IS_FOLDER (folder));
+	internal_uri = e_folder_exchange_get_internal_uri (folder);
+
+	/* This should ideally not be needed. But, this causes a problem when the
+	server has identical folder names [ internal_uri ] for folders. Very much
+	possible in the case of favorite folders */
+	if (g_hash_table_lookup (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->folders_by_internal_path,
+				(gchar *)e2k_uri_path (internal_uri)))
+		return;
+
+	g_hash_table_insert (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->folders_by_internal_path,
+			     (gchar *)e2k_uri_path (internal_uri), g_object_ref (folder));
+
+	mf_path = e_folder_exchange_get_storage_file (folder, "connector-metadata.xml");
+	e_folder_exchange_save_to_file (folder, mf_path);
+	g_free (mf_path);
+}
+
+static void
+hierarchy_removed_folder (ExchangeHierarchy *hier, EFolder *folder,
+			  gpointer user_data)
+{
+	const gchar *internal_uri = e_folder_exchange_get_internal_uri (folder);
+	gchar *mf_path;
+
+	g_hash_table_remove (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->folders_by_internal_path,
+			     (gchar *)e2k_uri_path (internal_uri));
+
+	mf_path = e_folder_exchange_get_storage_file (folder, "connector-metadata.xml");
+	g_unlink (mf_path);
+	g_free (mf_path);
+
+	e_path_rmdir (hier->account->storage_dir,
+		      e_folder_exchange_get_path (folder));
+}
+
+static gboolean
+is_empty (ExchangeHierarchy *hier)
+{
+	ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
+
+	/* 1, not 0, because there will always be an entry for toplevel */
+	return g_hash_table_size (hwd->priv->folders_by_internal_path) == 1;
+}
+
+static EFolder *
+e_folder_webdav_new (ExchangeHierarchy *hier, const gchar *internal_uri,
+		     EFolder *parent, const gchar *name, const gchar *type,
+		     const gchar *outlook_class, gint unread,
+		     gboolean offline_supported)
+{
+	EFolder *folder;
+	gchar *real_type, *http_uri, *physical_uri, *fixed_name = NULL;
+
+	d( g_print ("exchange-hierarchy-webdave.c:e_folder_webdave_new: internal_uri=[%s], name=[%s], type=[%s], class=[%s]\n",
+		    internal_uri, name, type, outlook_class));
+
+	if (hier->type == EXCHANGE_HIERARCHY_PUBLIC &&
+	    !strstr (type, "/public"))
+		real_type = g_strdup_printf ("%s/public", type);
+	else if (hier->type == EXCHANGE_HIERARCHY_FOREIGN &&
+		 !strcmp (type, "calendar"))
+		real_type = g_strdup ("calendar/public"); /* Hack */
+	else
+		real_type = g_strdup (type);
+
+	fixed_name = e2k_uri_encode (name, FALSE, URI_ENCODE_CHARS);
+	physical_uri = e2k_uri_concat (e_folder_get_physical_uri (parent), fixed_name);
+	g_free (fixed_name);
+
+	if (internal_uri) {
+		folder = e_folder_exchange_new (hier, name,
+						real_type, outlook_class,
+						physical_uri, internal_uri);
+	} else {
+		gchar *temp_name;
+		gchar *encoded_name = NULL;
+		const gchar *new_internal_uri;
+		gint len;
+
+		len = strlen (name);
+
+		/* appending "/" here, so that hash table lookup in rescan() succeeds */
+		if (name[len-1] != '/') {
+			encoded_name = e2k_uri_encode (name, FALSE, URI_ENCODE_CHARS);
+		} else {
+			temp_name = g_strndup (name, len-1);
+			encoded_name = e2k_uri_encode (temp_name, FALSE, URI_ENCODE_CHARS);
+			g_free (temp_name);
+		}
+		temp_name = g_strdup_printf ("%s/", encoded_name);
+		g_free (encoded_name);
+
+		new_internal_uri = e_folder_exchange_get_internal_uri (parent);
+		http_uri = e2k_uri_concat (new_internal_uri, temp_name);
+		d(g_print ("exchange-hierarchy-webdave.c:e_folder_webdave_new: http_uri=[%s], new_internal_uri=[%s], temp_name=[%s], name[%s]\n",
+			   http_uri, new_internal_uri, temp_name, name));
+		g_free (temp_name);
+
+		folder = e_folder_exchange_new (hier, name,
+						real_type, outlook_class,
+						physical_uri, http_uri);
+		g_free (http_uri);
+	}
+	g_free (physical_uri);
+	g_free (real_type);
+
+	if (unread && hier->type != EXCHANGE_HIERARCHY_PUBLIC)
+		e_folder_set_unread_count (folder, unread);
+	if (offline_supported)
+		e_folder_set_can_sync_offline (folder, offline_supported);
+
+	/* FIXME: set is_stock */
+
+	return folder;
+}
+
+static ExchangeAccountFolderResult
+create_folder (ExchangeHierarchy *hier, EFolder *parent,
+	       const gchar *name, const gchar *type)
+{
+	EFolder *dest;
+	E2kProperties *props;
+	E2kHTTPStatus status;
+	gchar *permanent_url = NULL;
+	gint i, mode;
+
+	exchange_account_is_offline (hier->account, &mode);
+        if (mode != ONLINE_MODE)
+                return EXCHANGE_ACCOUNT_FOLDER_OFFLINE;
+
+	for (i = 0; folder_types[i].component; i++) {
+		if (!strcmp (folder_types[i].component, type))
+			break;
+	}
+	if (!folder_types[i].component)
+		return EXCHANGE_ACCOUNT_FOLDER_UNKNOWN_TYPE;
+
+	dest = e_folder_webdav_new (hier, NULL, parent, name, type,
+				    folder_types[i].contentclass, 0,
+				    folder_types[i].offline_supported);
+
+	props = e2k_properties_new ();
+	e2k_properties_set_string (props, E2K_PR_EXCHANGE_FOLDER_CLASS,
+				   g_strdup (folder_types[i].contentclass));
+
+	status = e_folder_exchange_mkcol (dest, NULL, props,
+					  &permanent_url);
+	e2k_properties_free (props);
+
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		e_folder_exchange_set_permanent_uri (dest, permanent_url);
+		g_free (permanent_url);
+		exchange_hierarchy_new_folder (hier, dest);
+		g_object_unref (dest);
+
+		/* update the folder size table, new folder, initialize the size to 0 */
+		exchange_account_folder_size_add (hier->account, name, 0);
+		return EXCHANGE_ACCOUNT_FOLDER_OK;
+	}
+
+	g_object_unref (dest);
+	if (status == E2K_HTTP_METHOD_NOT_ALLOWED)
+		return EXCHANGE_ACCOUNT_FOLDER_ALREADY_EXISTS;
+	else if (status == E2K_HTTP_CONFLICT)
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+	else if (status == E2K_HTTP_FORBIDDEN)
+		return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+	else
+		return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
+}
+
+static ExchangeAccountFolderResult
+remove_folder (ExchangeHierarchy *hier, EFolder *folder)
+{
+	E2kHTTPStatus status;
+	gint mode;
+
+        exchange_account_is_offline (hier->account, &mode);
+
+        if (mode != ONLINE_MODE)
+                return EXCHANGE_ACCOUNT_FOLDER_OFFLINE;
+
+	if (folder == hier->toplevel)
+		return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+
+	status = e_folder_exchange_delete (folder, NULL);
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		exchange_hierarchy_removed_folder (hier, folder);
+
+		/* update the folder size info */
+		exchange_account_folder_size_remove (hier->account,
+					e_folder_get_name(folder));
+		return EXCHANGE_ACCOUNT_FOLDER_OK;
+	} else
+		return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
+}
+
+static ExchangeAccountFolderResult
+xfer_folder (ExchangeHierarchy *hier, EFolder *source,
+	     EFolder *dest_parent, const gchar *dest_name,
+	     gboolean remove_source)
+{
+	E2kHTTPStatus status;
+	EFolder *dest;
+	gchar *permanent_url = NULL, *physical_uri, *source_parent;
+	const gchar *folder_type = NULL, *source_folder_name;
+	ExchangeAccountFolderResult ret_code;
+	gint mode;
+
+	exchange_account_is_offline (hier->account, &mode);
+        if (mode != ONLINE_MODE)
+                return EXCHANGE_ACCOUNT_FOLDER_OFFLINE;
+
+	if (source == hier->toplevel)
+		return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
+
+	dest = e_folder_webdav_new (hier, NULL, dest_parent, dest_name,
+				    e_folder_get_type_string (source),
+				    e_folder_exchange_get_outlook_class (source),
+				    e_folder_get_unread_count (source),
+				    e_folder_get_can_sync_offline (source));
+
+	status = e_folder_exchange_transfer_dir (source, NULL, dest,
+						 remove_source,
+						 &permanent_url);
+
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+		folder_type = e_folder_get_type_string (source);
+		if (permanent_url)
+			e_folder_exchange_set_permanent_uri (dest, permanent_url);
+		if (remove_source)
+			exchange_hierarchy_removed_folder (hier, source);
+		exchange_hierarchy_new_folder (hier, dest);
+		scan_subtree (hier, dest, mode);
+		physical_uri = g_strdup (e_folder_get_physical_uri (source));
+		ret_code = EXCHANGE_ACCOUNT_FOLDER_OK;
+
+		/* Find if folder movement or rename.
+		 * update folder size in case of rename.
+		 */
+
+		source_folder_name = strrchr (physical_uri, '/') + 1;
+		source_parent = g_strndup (physical_uri,
+					   source_folder_name - physical_uri);
+		if (!strcmp (e_folder_get_physical_uri (dest_parent), source_parent)) {
+			/* rename - remove folder entry from hash, and
+			 * update the hash table with new name
+			 */
+			exchange_account_folder_size_rename (hier->account,
+				source_folder_name+1, dest_name);
+		}
+		g_free (source_parent);
+	} else {
+		physical_uri = e2k_uri_concat (
+				e_folder_get_physical_uri (dest_parent),
+				dest_name);
+		g_object_unref (dest);
+		if (status == E2K_HTTP_FORBIDDEN ||
+		    status == E2K_HTTP_UNAUTHORIZED)
+			ret_code = EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+		else
+			ret_code = EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
+	}
+
+	/* Remove the ESource of the source folder, in case of rename/move */
+	if ((hier->type == EXCHANGE_HIERARCHY_PERSONAL ||
+	     hier->type == EXCHANGE_HIERARCHY_FAVORITES) && remove_source &&
+	     ret_code == EXCHANGE_ACCOUNT_FOLDER_OK) {
+
+		if ((strcmp (folder_type, "calendar") == 0) ||
+		    (strcmp (folder_type, "calendar/public") == 0)) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_CALENDAR_FOLDER,
+					       physical_uri);
+		}
+		else if ((strcmp (folder_type, "tasks") == 0) ||
+			 (strcmp (folder_type, "tasks/public") == 0)) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_TASKS_FOLDER,
+					       physical_uri);
+		}
+		else if ((strcmp (folder_type, "contacts") == 0) ||
+			 (strcmp (folder_type, "contacts/public") == 0)) {
+			remove_folder_esource (hier->account,
+					       EXCHANGE_CONTACTS_FOLDER,
+					       physical_uri);
+		}
+	}
+	if (physical_uri)
+		g_free (physical_uri);
+	return ret_code;
+}
+
+static void
+add_href (gpointer path, gpointer folder, gpointer hrefs)
+{
+	const gchar *folder_type;
+
+	folder_type = e_folder_get_type_string (folder);
+
+	if (!folder_type)
+		return;
+
+	if (!strcmp (folder_type, "noselect"))
+		return;
+
+	g_ptr_array_add (hrefs, path);
+}
+
+/* E2K_PR_EXCHANGE_FOLDER_SIZE also can be used for reading folder size */
+static const gchar *rescan_props[] = {
+	E2K_PR_EXCHANGE_FOLDER_SIZE,
+	E2K_PR_HTTPMAIL_UNREAD_COUNT
+};
+static const gint n_rescan_props = sizeof (rescan_props) / sizeof (rescan_props[0]);
+
+static void
+rescan (ExchangeHierarchy *hier)
+{
+	ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
+	const gchar *prop = E2K_PR_HTTPMAIL_UNREAD_COUNT;
+	const gchar *folder_size, *folder_name;
+	GPtrArray *hrefs;
+	E2kResultIter *iter;
+	E2kResult *result;
+	EFolder *folder;
+	gint unread, mode;
+	gboolean personal = ( hier->type == EXCHANGE_HIERARCHY_PERSONAL );
+	gdouble fsize_d;
+
+	exchange_account_is_offline (hier->account, &mode);
+	if ( (mode != ONLINE_MODE) ||
+		hier->type == EXCHANGE_HIERARCHY_PUBLIC)
+		return;
+
+	hrefs = g_ptr_array_new ();
+	g_hash_table_foreach (hwd->priv->folders_by_internal_path,
+			      add_href, hrefs);
+	if (!hrefs->len) {
+		g_ptr_array_free (hrefs, TRUE);
+		return;
+	}
+
+	g_object_ref (hier);
+	iter = e_folder_exchange_bpropfind_start (hier->toplevel, NULL,
+						  (const gchar **)hrefs->pdata,
+						  hrefs->len,
+						  rescan_props, n_rescan_props);
+	g_ptr_array_free (hrefs, TRUE);
+
+	while ((result = e2k_result_iter_next (iter))) {
+		folder = g_hash_table_lookup (hwd->priv->folders_by_internal_path,
+					      e2k_uri_path (result->href));
+		if (!folder)
+			continue;
+
+		prop = e2k_properties_get_prop (result->props,
+						E2K_PR_HTTPMAIL_UNREAD_COUNT);
+		if (!prop)
+			continue;
+		unread = atoi (prop);
+
+		if (unread != e_folder_get_unread_count (folder))
+			e_folder_set_unread_count (folder, unread);
+
+		folder_size = e2k_properties_get_prop (result->props,
+				E2K_PR_EXCHANGE_FOLDER_SIZE);
+
+		if (folder_size) {
+			folder_name = e_folder_get_name (folder);
+			fsize_d = g_ascii_strtod (folder_size, NULL)/1024;
+			exchange_account_folder_size_add (hier->account,
+							folder_name, fsize_d);
+			if (personal)
+				hwd->priv->total_folder_size =
+					hwd->priv->total_folder_size + fsize_d;
+		}
+	}
+	e2k_result_iter_free (iter);
+	g_object_unref (hier);
+}
+
+ExchangeAccountFolderResult
+exchange_hierarchy_webdav_status_to_folder_result (E2kHTTPStatus status)
+{
+	if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status))
+		return EXCHANGE_ACCOUNT_FOLDER_OK;
+	else if (status == E2K_HTTP_NOT_FOUND)
+		return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+	else if (status == E2K_HTTP_UNAUTHORIZED)
+		return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+	else
+		return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
+}
+
+gdouble
+exchange_hierarchy_webdav_get_total_folder_size (ExchangeHierarchyWebDAV *hwd)
+{
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY_WEBDAV (hwd), -1);
+
+	return hwd->priv->total_folder_size;
+}
+
+EFolder *
+exchange_hierarchy_webdav_parse_folder (ExchangeHierarchyWebDAV *hwd,
+					EFolder *parent,
+					E2kResult *result)
+{
+	EFolder *folder;
+	ExchangeFolderType *folder_type;
+	const gchar *name, *prop, *outlook_class, *permanenturl;
+	gint unread;
+	gboolean hassubs;
+
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY_WEBDAV (hwd), NULL);
+	g_return_val_if_fail (E_IS_FOLDER (parent), NULL);
+
+	/* It's possible to have a localized inbox folder named, eg,
+	 * "Innboks", with children whose URIs go through "Inbox"
+	 * instead. (See bugzilla 27065.) This is probably related to
+	 * the IMAP "INBOX" convention. Anyway, the important bit is
+	 * that you can't know a folder's parent URI just by looking
+	 * at its own URI. Since we only ever scan one folder at a
+	 * time here, we just keep track of what the parent was. If we
+	 * were going to read multiple folders at once, we could deal
+	 * with this by fetching DAV:parentname.
+	 */
+
+	name = e2k_properties_get_prop (result->props,
+					E2K_PR_DAV_DISPLAY_NAME);
+	if (!name)
+		return NULL;
+
+	prop = e2k_properties_get_prop (result->props,
+					E2K_PR_HTTPMAIL_UNREAD_COUNT);
+	unread = prop ? atoi (prop) : 0;
+	prop = e2k_properties_get_prop (result->props,
+					E2K_PR_DAV_HAS_SUBS);
+	hassubs = prop && atoi (prop);
+
+	outlook_class = e2k_properties_get_prop (result->props,
+						 E2K_PR_EXCHANGE_FOLDER_CLASS);
+	folder_type = NULL;
+	if (outlook_class)
+		folder_type = g_hash_table_lookup (folder_type_map, outlook_class);
+	if (!folder_type)
+		folder_type = &folder_types[0]; /* mail */
+	if (!outlook_class)
+		outlook_class = folder_type->contentclass;
+
+	/*
+	 * The permanenturl Field provides a unique identifier for an item
+	 * across the *store* and will not change as long as the item remains
+	 * in the same folder. The permanenturl Field contains the ID of the
+	 * parent folder of the item, which changes when the item is moved to a
+	 * different folder or deleted. Changing a field on an item will not
+	 * change the permanenturl Field and neither will adding more items to
+	 * the folder with the same display name or message subject.
+	 */
+	permanenturl = e2k_properties_get_prop (result->props,
+						E2K_PR_EXCHANGE_PERMANENTURL);
+	/* Check for errors */
+
+	folder = e_folder_webdav_new (EXCHANGE_HIERARCHY (hwd),
+				      result->href, parent,
+				      name, folder_type->component,
+				      outlook_class, unread,
+				      folder_type->offline_supported);
+	if (hwd->priv->trash_path && !strcmp (e2k_uri_path (result->href), hwd->priv->trash_path))
+		e_folder_set_custom_icon (folder, "stock_delete");
+	if (hassubs)
+		e_folder_exchange_set_has_subfolders (folder, TRUE);
+	if (permanenturl) {
+		/* Favorite folders and subscribed folders will not have
+		 * permanenturl
+		 */
+		e_folder_exchange_set_permanent_uri (folder, permanenturl);
+	}
+
+	return folder;
+}
+
+static void
+add_folders (ExchangeHierarchy *hier, EFolder *folder, gpointer folders)
+{
+	g_object_ref (folder);
+	g_ptr_array_add (folders, folder);
+}
+
+static const gchar *folder_props[] = {
+	E2K_PR_EXCHANGE_FOLDER_CLASS,
+	E2K_PR_HTTPMAIL_UNREAD_COUNT,
+	E2K_PR_DAV_DISPLAY_NAME,
+	E2K_PR_EXCHANGE_PERMANENTURL,
+	E2K_PR_EXCHANGE_FOLDER_SIZE,
+	E2K_PR_DAV_HAS_SUBS
+};
+static const gint n_folder_props = sizeof (folder_props) / sizeof (folder_props[0]);
+
+static const gchar *pub_folder_props[] = {
+	E2K_PR_EXCHANGE_FOLDER_CLASS,
+	E2K_PR_DAV_DISPLAY_NAME,
+	E2K_PR_EXCHANGE_PERMANENTURL,
+	E2K_PR_DAV_HAS_SUBS
+};
+static const gint n_pub_folder_props = sizeof (pub_folder_props) / sizeof (pub_folder_props[0]);
+
+static ExchangeAccountFolderResult
+scan_subtree (ExchangeHierarchy *hier, EFolder *parent, gint mode)
+{
+	static E2kRestriction *folders_rn;
+	ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
+	GSList *subtrees = NULL;
+	E2kResultIter *iter;
+	E2kResult *result;
+	E2kHTTPStatus status;
+	EFolder *folder, *tmp;
+	GPtrArray *folders;
+	gint i;
+	gdouble fsize_d;
+	const gchar *name, *folder_size, *deleted_items_uri, *int_uri;
+	gboolean personal = ( EXCHANGE_HIERARCHY (hwd)->type == EXCHANGE_HIERARCHY_PERSONAL );
+
+	if (parent) {
+		if (!e_folder_exchange_get_rescan_tree (parent)) {
+			d(g_print ("%s(%d):%s: Donot RESCAN [%s] \n", __FILE__, __LINE__, __PRETTY_FUNCTION__,
+				   e_folder_get_name (parent)));
+			return EXCHANGE_ACCOUNT_FOLDER_OK;
+		}
+	}
+
+	if (mode == OFFLINE_MODE) {
+		folders = g_ptr_array_new ();
+		exchange_hierarchy_webdav_offline_scan_subtree (EXCHANGE_HIERARCHY (hier), add_folders, folders);
+		for (i = 0; i <folders->len; i++) {
+			tmp = (EFolder *)folders->pdata[i];
+			exchange_hierarchy_new_folder (hier, (EFolder *)folders->pdata[i]);
+		}
+		return EXCHANGE_ACCOUNT_FOLDER_OK;
+	}
+
+	if (!folders_rn) {
+		folders_rn =
+			e2k_restriction_andv (
+				e2k_restriction_prop_bool (E2K_PR_DAV_IS_COLLECTION,
+							   E2K_RELOP_EQ, TRUE),
+				e2k_restriction_prop_bool (E2K_PR_DAV_IS_HIDDEN,
+							   E2K_RELOP_EQ, FALSE),
+				NULL);
+	}
+
+	if (hier->type == EXCHANGE_HIERARCHY_PUBLIC)
+		iter = e_folder_exchange_search_start (parent, NULL,
+						       pub_folder_props,
+						       n_pub_folder_props,
+						       folders_rn, NULL, TRUE);
+	else
+		iter = e_folder_exchange_search_start (parent, NULL,
+						       folder_props, n_folder_props,
+						       folders_rn, NULL, TRUE);
+
+	while ((result = e2k_result_iter_next (iter))) {
+		folder = exchange_hierarchy_webdav_parse_folder (hwd, parent, result);
+		if (!folder)
+			continue;
+
+		if (hwd->priv->deep_searchable &&
+		    e_folder_exchange_get_has_subfolders (folder)) {
+			e_folder_exchange_set_has_subfolders (folder, FALSE);
+			subtrees = g_slist_prepend (subtrees, g_object_ref (folder));
+		}
+		exchange_hierarchy_new_folder (hier, folder);
+		g_object_unref (folder);
+
+		/* Check the folder size here */
+		if (hier->type != EXCHANGE_HIERARCHY_PUBLIC) {
+			name = e2k_properties_get_prop (result->props,
+							E2K_PR_DAV_DISPLAY_NAME);
+			folder_size = e2k_properties_get_prop (result->props,
+							       E2K_PR_EXCHANGE_FOLDER_SIZE);
+
+			/* FIXME : Find a better way of doing this */
+			fsize_d = g_ascii_strtod (folder_size, NULL)/1024;
+			exchange_account_folder_size_add (hier->account, name, fsize_d);
+		}
+
+		if (personal) {
+			/* calculate mail box size only for personal folders */
+			hwd->priv->total_folder_size =
+				hwd->priv->total_folder_size + fsize_d;
+		}
+
+	}
+
+	status = e2k_result_iter_free (iter);
+
+	deleted_items_uri = exchange_account_get_standard_uri (hier->account, "deleteditems");
+
+	while (subtrees) {
+		folder = subtrees->data;
+		subtrees = g_slist_remove (subtrees, folder);
+
+		/* Dont scan the subtree for deleteditems folder */
+		int_uri = e_folder_exchange_get_internal_uri (folder);
+		if (int_uri && deleted_items_uri && !strcmp (int_uri, deleted_items_uri)) {
+			g_object_unref (folder);
+			continue;
+		}
+
+		scan_subtree (hier, folder, mode);
+		g_object_unref (folder);
+	}
+
+	e_folder_exchange_set_rescan_tree (parent, FALSE);
+
+	return exchange_hierarchy_webdav_status_to_folder_result (status);
+}
+
+struct scan_offline_data {
+	ExchangeHierarchy *hier;
+	ExchangeHierarchyWebDAVScanCallback callback;
+	gpointer user_data;
+	GPtrArray *badpaths;
+};
+
+static gboolean
+scan_offline_cb (const gchar *physical_path, const gchar *path, gpointer data)
+{
+	struct scan_offline_data *sod = data;
+	EFolder *folder;
+	gchar *mf_name;
+
+	mf_name = g_build_filename (physical_path, "connector-metadata.xml", NULL);
+	folder = e_folder_exchange_new_from_file (sod->hier, mf_name);
+	if (!folder) {
+		g_unlink (mf_name);
+		g_free (mf_name);
+		if (!sod->badpaths)
+			sod->badpaths = g_ptr_array_new ();
+		g_ptr_array_add (sod->badpaths, g_strdup (path));
+		return TRUE;
+	}
+	g_free (mf_name);
+
+	sod->callback (sod->hier, folder, sod->user_data);
+	g_object_unref (folder);
+
+	return TRUE;
+}
+
+/**
+ * exchange_hierarchy_webdav_offline_scan_subtree:
+ * @hier: a (webdav) hierarchy
+ * @callbackb: a callback
+ * @user_data: data for @cb
+ *
+ * Scans the offline folder tree cache for @hier and calls @cb
+ * with each folder successfully constructed from offline data
+ **/
+void
+exchange_hierarchy_webdav_offline_scan_subtree (ExchangeHierarchy *hier,
+						ExchangeHierarchyWebDAVScanCallback callback,
+						gpointer user_data)
+{
+	struct scan_offline_data sod;
+	const gchar *path;
+	gchar *dir, *prefix;
+	gint i;
+
+	g_return_if_fail (EXCHANGE_IS_HIERARCHY (hier));
+
+	sod.hier = hier;
+	sod.callback = callback;
+	sod.user_data = user_data;
+	sod.badpaths = NULL;
+
+	path = e_folder_exchange_get_path (hier->toplevel);
+	prefix = e2k_strdup_with_trailing_slash (path);
+	dir = e_path_to_physical (hier->account->storage_dir, prefix);
+	g_free (prefix);
+	e_path_find_folders (dir, scan_offline_cb, &sod);
+
+	if (sod.badpaths) {
+		for (i = 0; i < sod.badpaths->len; i++) {
+			e_path_rmdir (dir, sod.badpaths->pdata[i]);
+			g_free (sod.badpaths->pdata[i]);
+		}
+		g_ptr_array_free (sod.badpaths, TRUE);
+	}
+
+	g_free (dir);
+}
+
+void
+exchange_hierarchy_webdav_construct (ExchangeHierarchyWebDAV *hwd,
+				     ExchangeAccount *account,
+				     ExchangeHierarchyType type,
+				     const gchar *hierarchy_name,
+				     const gchar *physical_uri_prefix,
+				     const gchar *internal_uri_prefix,
+				     const gchar *owner_name,
+				     const gchar *owner_email,
+				     const gchar *source_uri,
+				     gboolean deep_searchable)
+{
+	EFolder *toplevel;
+
+	g_return_if_fail (EXCHANGE_IS_HIERARCHY_WEBDAV (hwd));
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+	hwd->priv->deep_searchable = deep_searchable;
+
+	toplevel = e_folder_exchange_new (EXCHANGE_HIERARCHY (hwd),
+					  hierarchy_name,
+					  "noselect", NULL,
+					  physical_uri_prefix,
+					  internal_uri_prefix);
+	e_folder_set_custom_icon (toplevel, "stock_folder");
+	e_folder_exchange_set_has_subfolders (toplevel, TRUE);
+	exchange_hierarchy_construct (EXCHANGE_HIERARCHY (hwd),
+				      account, type, toplevel,
+				      owner_name, owner_email, source_uri);
+	g_object_unref (toplevel);
+
+	if (type == EXCHANGE_HIERARCHY_PERSONAL) {
+		const gchar *trash_uri;
+
+		trash_uri = exchange_account_get_standard_uri (account, "deleteditems");
+		if (trash_uri)
+			hwd->priv->trash_path = e2k_strdup_with_trailing_slash (e2k_uri_path (trash_uri));
+	}
+}
+
+ExchangeHierarchy *
+exchange_hierarchy_webdav_new (ExchangeAccount *account,
+			       ExchangeHierarchyType type,
+			       const gchar *hierarchy_name,
+			       const gchar *physical_uri_prefix,
+			       const gchar *internal_uri_prefix,
+			       const gchar *owner_name,
+			       const gchar *owner_email,
+			       const gchar *source_uri,
+			       gboolean deep_searchable)
+{
+	ExchangeHierarchy *hier;
+
+	g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+	hier = g_object_new (EXCHANGE_TYPE_HIERARCHY_WEBDAV, NULL);
+
+	exchange_hierarchy_webdav_construct (EXCHANGE_HIERARCHY_WEBDAV (hier),
+					     account, type, hierarchy_name,
+					     physical_uri_prefix,
+					     internal_uri_prefix,
+					     owner_name, owner_email,
+					     source_uri, deep_searchable);
+	return hier;
+}
diff --git a/server/storage/exchange-hierarchy-webdav.h b/server/storage/exchange-hierarchy-webdav.h
new file mode 100644
index 0000000..ce629f1
--- /dev/null
+++ b/server/storage/exchange-hierarchy-webdav.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_HIERARCHY_WEBDAV_H__
+#define __EXCHANGE_HIERARCHY_WEBDAV_H__
+
+#include "exchange-hierarchy.h"
+/* #include "exchange-folder-size.h" */
+
+G_BEGIN_DECLS
+
+#define EXCHANGE_TYPE_HIERARCHY_WEBDAV            (exchange_hierarchy_webdav_get_type ())
+#define EXCHANGE_HIERARCHY_WEBDAV(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_HIERARCHY_WEBDAV, ExchangeHierarchyWebDAV))
+#define EXCHANGE_HIERARCHY_WEBDAV_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_HIERARCHY_WEBDAV, ExchangeHierarchyWebDAVClass))
+#define EXCHANGE_IS_HIERARCHY_WEBDAV(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_WEBDAV))
+#define EXCHANGE_IS_HIERARCHY_WEBDAV_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY_WEBDAV))
+
+struct _ExchangeHierarchyWebDAV {
+	ExchangeHierarchy parent;
+
+	ExchangeHierarchyWebDAVPrivate *priv;
+};
+
+struct _ExchangeHierarchyWebDAVClass {
+	ExchangeHierarchyClass parent_class;
+
+};
+
+GType              exchange_hierarchy_webdav_get_type (void);
+
+ExchangeHierarchy *exchange_hierarchy_webdav_new (ExchangeAccount *account,
+						  ExchangeHierarchyType type,
+						  const gchar *hierarchy_name,
+						  const gchar *physical_uri_prefix,
+						  const gchar *internal_uri_prefix,
+						  const gchar *owner_name,
+						  const gchar *owner_email,
+						  const gchar *source_uri,
+						  gboolean deep_searchable);
+
+/* for subclasses */
+ExchangeAccountFolderResult exchange_hierarchy_webdav_status_to_folder_result (E2kHTTPStatus status);
+EFolder *exchange_hierarchy_webdav_parse_folder (ExchangeHierarchyWebDAV *hwd,
+						 EFolder *parent,
+						 E2kResult *result);
+
+void exchange_hierarchy_webdav_construct   (ExchangeHierarchyWebDAV *hwd,
+					    ExchangeAccount *account,
+					    ExchangeHierarchyType type,
+					    const gchar *hierarchy_name,
+					    const gchar *physical_uri_prefix,
+					    const gchar *internal_uri_prefix,
+					    const gchar *owner_name,
+					    const gchar *owner_email,
+					    const gchar *source_uri,
+					    gboolean deep_searchable);
+
+typedef void (*ExchangeHierarchyWebDAVScanCallback)    (ExchangeHierarchy *hier,
+							EFolder *folder,
+							gpointer user_data);
+void    exchange_hierarchy_webdav_offline_scan_subtree (ExchangeHierarchy *hier,
+							ExchangeHierarchyWebDAVScanCallback cb,
+							gpointer user_data);
+
+gdouble exchange_hierarchy_webdav_get_total_folder_size (ExchangeHierarchyWebDAV *hwd);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_HIERARCHY_WEBDAV_H__ */
diff --git a/server/storage/exchange-hierarchy.c b/server/storage/exchange-hierarchy.c
new file mode 100644
index 0000000..77f5d6a
--- /dev/null
+++ b/server/storage/exchange-hierarchy.c
@@ -0,0 +1,377 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* ExchangeHierarchy: abstract class for a hierarchy of folders
+ * in an Exchange storage. Subclasses of ExchangeHierarchy implement
+ * normal WebDAV hierarchies, the GAL hierarchy, and hierarchies
+ * of individually-selected other users' folders.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "exchange-hierarchy.h"
+#include "e-folder-exchange.h"
+
+enum {
+	NEW_FOLDER,
+	REMOVED_FOLDER,
+	LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+#define HIER_CLASS(hier) (EXCHANGE_HIERARCHY_CLASS (G_OBJECT_GET_CLASS (hier)))
+
+static void dispose (GObject *object);
+static void finalize (GObject *object);
+static gboolean is_empty        (ExchangeHierarchy *hier);
+static void add_to_storage      (ExchangeHierarchy *hier);
+static ExchangeAccountFolderResult scan_subtree  (ExchangeHierarchy *hier,
+						  EFolder *folder,
+						  gint mode);
+static void                        rescan        (ExchangeHierarchy *hier);
+static ExchangeAccountFolderResult create_folder (ExchangeHierarchy *hier,
+						  EFolder *parent,
+						  const gchar *name,
+						  const gchar *type);
+static ExchangeAccountFolderResult remove_folder (ExchangeHierarchy *hier,
+						  EFolder *folder);
+static ExchangeAccountFolderResult xfer_folder   (ExchangeHierarchy *hier,
+						  EFolder *source,
+						  EFolder *dest_parent,
+						  const gchar *dest_name,
+						  gboolean remove_source);
+
+static void
+class_init (GObjectClass *object_class)
+{
+	ExchangeHierarchyClass *exchange_hierarchy_class =
+		EXCHANGE_HIERARCHY_CLASS (object_class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	/* methods */
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
+
+	exchange_hierarchy_class->is_empty = is_empty;
+	exchange_hierarchy_class->add_to_storage = add_to_storage;
+	exchange_hierarchy_class->rescan = rescan;
+	exchange_hierarchy_class->scan_subtree = scan_subtree;
+	exchange_hierarchy_class->create_folder = create_folder;
+	exchange_hierarchy_class->remove_folder = remove_folder;
+	exchange_hierarchy_class->xfer_folder = xfer_folder;
+
+	/* signals */
+	signals[NEW_FOLDER] =
+		g_signal_new ("new_folder",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (ExchangeHierarchyClass, new_folder),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_POINTER);
+	signals[REMOVED_FOLDER] =
+		g_signal_new ("removed_folder",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (ExchangeHierarchyClass, removed_folder),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_POINTER);
+}
+
+static void
+dispose (GObject *object)
+{
+	ExchangeHierarchy *hier = EXCHANGE_HIERARCHY (object);
+
+	if (hier->toplevel) {
+		g_object_unref (hier->toplevel);
+		hier->toplevel = NULL;
+	}
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	ExchangeHierarchy *hier = EXCHANGE_HIERARCHY (object);
+
+	g_free (hier->owner_name);
+	g_free (hier->owner_email);
+	g_free (hier->source_uri);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (exchange_hierarchy, ExchangeHierarchy, class_init, NULL, PARENT_TYPE)
+
+/**
+ * exchange_hierarchy_new_folder:
+ * @hier: the hierarchy
+ * @folder: the new folder
+ *
+ * Emits a %new_folder signal.
+ **/
+void
+exchange_hierarchy_new_folder (ExchangeHierarchy *hier,
+			       EFolder *folder)
+{
+	g_return_if_fail (EXCHANGE_IS_HIERARCHY (hier));
+	g_return_if_fail (E_IS_FOLDER (folder));
+
+	g_signal_emit (hier, signals[NEW_FOLDER], 0, folder);
+}
+
+/**
+ * exchange_hierarchy_removed_folder:
+ * @hier: the hierarchy
+ * @folder: the (about-to-be-)removed folder
+ *
+ * Emits a %removed_folder signal.
+ **/
+void
+exchange_hierarchy_removed_folder (ExchangeHierarchy *hier,
+				   EFolder *folder)
+{
+	g_return_if_fail (EXCHANGE_IS_HIERARCHY (hier));
+        g_return_if_fail (E_IS_FOLDER (folder));
+
+	g_signal_emit (hier, signals[REMOVED_FOLDER], 0, folder);
+}
+
+static gboolean
+is_empty (ExchangeHierarchy *hier)
+{
+	return FALSE;
+}
+
+gboolean
+exchange_hierarchy_is_empty (ExchangeHierarchy *hier)
+{
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY (hier), TRUE);
+
+	return HIER_CLASS (hier)->is_empty (hier);
+}
+
+static ExchangeAccountFolderResult
+create_folder (ExchangeHierarchy *hier, EFolder *parent,
+	       const gchar *name, const gchar *type)
+{
+	return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+}
+
+static ExchangeAccountFolderResult
+remove_folder (ExchangeHierarchy *hier, EFolder *folder)
+{
+	return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+}
+
+static ExchangeAccountFolderResult
+xfer_folder (ExchangeHierarchy *hier, EFolder *source,
+	     EFolder *dest_parent, const gchar *dest_name,
+	     gboolean remove_source)
+{
+	return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
+}
+
+/**
+ * exchange_hierarchy_create_folder:
+ * @hier: the hierarchy
+ * @parent: the parent folder of the new folder
+ * @name: name of the new folder (UTF8)
+ * @type: Evolution folder type of the new folder
+ *
+ * Attempts to create a new folder.
+ *
+ * Return value: the result code
+ **/
+ExchangeAccountFolderResult
+exchange_hierarchy_create_folder (ExchangeHierarchy *hier, EFolder *parent,
+				  const gchar *name, const gchar *type)
+{
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY (hier), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (parent), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (name != NULL, EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (type != NULL, EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	return HIER_CLASS (hier)->create_folder (hier, parent, name, type);
+}
+
+/**
+ * exchange_hierarchy_remove_folder:
+ * @hier: the hierarchy
+ * @folder: the folder to remove
+ *
+ * Attempts to remove a folder.
+ *
+ * Return value: the result code
+ **/
+ExchangeAccountFolderResult
+exchange_hierarchy_remove_folder (ExchangeHierarchy *hier, EFolder *folder)
+{
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY (hier), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	return HIER_CLASS (hier)->remove_folder (hier, folder);
+}
+
+/**
+ * exchange_hierarchy_xfer_folder:
+ * @hier: the hierarchy
+ * @source: the source folder
+ * @dest_parent: the parent of the destination folder
+ * @dest_name: name of the destination (UTF8)
+ * @remove_source: %TRUE if this is a move, %FALSE if it is a copy
+ *
+ * Attempts to move or copy a folder.
+ *
+ * Return value: the result code
+ **/
+ExchangeAccountFolderResult
+exchange_hierarchy_xfer_folder (ExchangeHierarchy *hier, EFolder *source,
+				EFolder *dest_parent, const gchar *dest_name,
+				gboolean remove_source)
+{
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY (hier), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (source), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (dest_parent), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (dest_name != NULL, EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	return HIER_CLASS (hier)->xfer_folder (hier, source,
+					       dest_parent, dest_name,
+					       remove_source);
+}
+
+static void
+rescan (ExchangeHierarchy *hier)
+{
+	;
+}
+
+/**
+ * exchange_hierarchy_rescan:
+ * @hier: the hierarchy
+ *
+ * Tells the hierarchy to rescan its folder tree
+ **/
+void
+exchange_hierarchy_rescan (ExchangeHierarchy *hier)
+{
+	g_return_if_fail (EXCHANGE_IS_HIERARCHY (hier));
+
+	HIER_CLASS (hier)->rescan (hier);
+}
+
+static ExchangeAccountFolderResult
+scan_subtree (ExchangeHierarchy *hier, EFolder *folder, gint mode)
+{
+	return EXCHANGE_ACCOUNT_FOLDER_OK;
+}
+
+/**
+ * exchange_hierarchy_scan_subtree:
+ * @hier: the hierarchy
+ * @folder: the folder to scan under
+ *
+ * Scans for folders in @hier underneath @folder, emitting %new_folder
+ * signals for each one found. Depending on the kind of hierarchy,
+ * this may initiate a recursive scan.
+ *
+ * Return value: the result code
+ **/
+ExchangeAccountFolderResult
+exchange_hierarchy_scan_subtree (ExchangeHierarchy *hier, EFolder *folder, gint mode)
+{
+	g_return_val_if_fail (EXCHANGE_IS_HIERARCHY (hier), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+	g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+
+	return HIER_CLASS (hier)->scan_subtree (hier, folder, mode);
+}
+
+static void
+add_to_storage (ExchangeHierarchy *hier)
+{
+	e_folder_set_sorting_priority (hier->toplevel, hier->type);
+	exchange_hierarchy_new_folder (hier, hier->toplevel);
+}
+
+/**
+ * exchange_hierarchy_add_to_storage:
+ * @hier: the hierarchy
+ *
+ * Tells the hierarchy to fill in its folder tree, emitting %new_folder
+ * signals as appropriate.
+ **/
+void
+exchange_hierarchy_add_to_storage (ExchangeHierarchy *hier)
+{
+	g_return_if_fail (EXCHANGE_IS_HIERARCHY (hier));
+
+	HIER_CLASS (hier)->add_to_storage (hier);
+}
+
+/**
+ * exchange_hierarchy_construct:
+ * @hier: the hierarchy
+ * @account: the hierarchy's account
+ * @type: the type of hierarchy
+ * @toplevel: the top-level folder of the hierarchy
+ * @owner_name: the display name of the owner of this hierarchy
+ * @owner_email: the email address of the owner of this hierarchy
+ * @source_uri: the evolution-mail source URI of this hierarchy.
+ *
+ * Constructs the hierarchy. @owner_name, @owner_email, and @source_uri
+ * can be %NULL if not relevant to this hierarchy.
+ **/
+void
+exchange_hierarchy_construct (ExchangeHierarchy *hier,
+			      ExchangeAccount *account,
+			      ExchangeHierarchyType type,
+			      EFolder *toplevel,
+			      const gchar *owner_name,
+			      const gchar *owner_email,
+			      const gchar *source_uri)
+{
+	g_return_if_fail (EXCHANGE_IS_HIERARCHY (hier));
+	g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+	g_return_if_fail (E_IS_FOLDER (toplevel));
+
+	/* We don't ref the account since we'll be destroyed when
+	 * the account is
+	 */
+	hier->account = account;
+
+	hier->toplevel = toplevel;
+	g_object_ref (toplevel);
+
+	hier->type = type;
+	hier->owner_name = g_strdup (owner_name);
+	hier->owner_email = g_strdup (owner_email);
+	hier->source_uri = g_strdup (source_uri);
+}
diff --git a/server/storage/exchange-hierarchy.h b/server/storage/exchange-hierarchy.h
new file mode 100644
index 0000000..2bb9107
--- /dev/null
+++ b/server/storage/exchange-hierarchy.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_HIERARCHY_H__
+#define __EXCHANGE_HIERARCHY_H__
+
+#include "exchange-types.h"
+#include "exchange-account.h"
+#include "e-folder.h"
+
+G_BEGIN_DECLS
+
+#define EXCHANGE_TYPE_HIERARCHY            (exchange_hierarchy_get_type ())
+#define EXCHANGE_HIERARCHY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_HIERARCHY, ExchangeHierarchy))
+#define EXCHANGE_HIERARCHY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_HIERARCHY, ExchangeHierarchyClass))
+#define EXCHANGE_IS_HIERARCHY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY))
+#define EXCHANGE_IS_HIERARCHY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_HIERARCHY))
+
+struct _ExchangeHierarchy {
+	GObject parent;
+
+	ExchangeAccount *account;
+	ExchangeHierarchyType type;
+	EFolder *toplevel;
+
+	gchar *owner_name;
+	gchar *owner_email;
+	gchar *source_uri;
+
+	gboolean hide_private_items;
+};
+
+struct _ExchangeHierarchyClass {
+	GObjectClass parent_class;
+
+	/* methods */
+	gboolean (*is_empty) (ExchangeHierarchy *hier);
+
+	void (*add_to_storage) (ExchangeHierarchy *hier);
+	void (*rescan) (ExchangeHierarchy *hier);
+	ExchangeAccountFolderResult (*scan_subtree)  (ExchangeHierarchy *hier,
+						      EFolder *folder,
+						      gint mode);
+
+	ExchangeAccountFolderResult (*create_folder) (ExchangeHierarchy *hier,
+						      EFolder *parent,
+						      const gchar *name,
+						      const gchar *type);
+	ExchangeAccountFolderResult (*remove_folder) (ExchangeHierarchy *hier,
+						      EFolder *folder);
+	ExchangeAccountFolderResult (*xfer_folder)   (ExchangeHierarchy *hier,
+						      EFolder *source,
+						      EFolder *dest_parent,
+						      const gchar *dest_name,
+						      gboolean remove_source);
+
+	/* signals */
+	void (*new_folder)     (ExchangeHierarchy *hier,
+				EFolder *folder);
+	void (*removed_folder) (ExchangeHierarchy *hier,
+				EFolder *folder);
+};
+
+GType    exchange_hierarchy_get_type            (void);
+
+void     exchange_hierarchy_construct           (ExchangeHierarchy *hier,
+						 ExchangeAccount   *account,
+						 ExchangeHierarchyType type,
+						 EFolder           *toplevel,
+						 const gchar        *owner_name,
+						 const gchar        *owner_email,
+						 const gchar        *source_uri);
+
+void     exchange_hierarchy_new_folder          (ExchangeHierarchy *hier,
+						 EFolder           *folder);
+void     exchange_hierarchy_removed_folder      (ExchangeHierarchy *hier,
+						 EFolder           *folder);
+
+gboolean exchange_hierarchy_is_empty            (ExchangeHierarchy *hier);
+
+void                        exchange_hierarchy_add_to_storage (ExchangeHierarchy *hier);
+void                        exchange_hierarchy_rescan         (ExchangeHierarchy *hier);
+ExchangeAccountFolderResult exchange_hierarchy_scan_subtree   (ExchangeHierarchy *hier,
+							       EFolder           *folder,
+							       gint		  mode);
+
+ExchangeAccountFolderResult exchange_hierarchy_create_folder (ExchangeHierarchy *hier,
+							      EFolder           *parent,
+							      const gchar        *name,
+							      const gchar        *type);
+ExchangeAccountFolderResult exchange_hierarchy_remove_folder (ExchangeHierarchy *hier,
+							      EFolder           *folder);
+ExchangeAccountFolderResult exchange_hierarchy_xfer_folder   (ExchangeHierarchy *hier,
+							      EFolder           *source,
+							      EFolder           *dest_parent,
+							      const gchar        *dest_name,
+							      gboolean           remove_source);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_HIERARCHY_H__ */
diff --git a/server/storage/exchange-oof.c b/server/storage/exchange-oof.c
new file mode 100644
index 0000000..9398169
--- /dev/null
+++ b/server/storage/exchange-oof.c
@@ -0,0 +1,206 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2002-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* exchange-oof: Out of Office code */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "exchange-oof.h"
+#include "exchange-account.h"
+#include "e2k-propnames.h"
+#include "e2k-utils.h"
+#include "e2k-uri.h"
+
+#include <string.h>
+
+/* Taken from gal/util/e-util.c */
+static gchar *
+find_str_case (const gchar *haystack, const gchar *needle, const gchar *end)
+{
+	/* find the needle in the haystack neglecting case */
+	const gchar *ptr;
+	gint len;
+
+	g_return_val_if_fail (haystack != NULL, NULL);
+	g_return_val_if_fail (needle != NULL, NULL);
+
+	len = strlen(needle);
+	if (len > strlen(haystack))
+		return NULL;
+
+	if (len == 0)
+		return (gchar *) haystack;
+
+	for (ptr = haystack; ptr + len < end; ptr++)
+		if (!g_ascii_strncasecmp (ptr, needle, len))
+			return (gchar *) ptr;
+
+	return NULL;
+
+}
+/**
+ * exchange_oof_get:
+ * @account: an #ExchangeAccount
+ * @oof: pointer to variable to pass back OOF state in
+ * @message: pointer to variable to pass back OOF message in
+ *
+ * Checks if Out-of-Office is enabled for @account and returns the
+ * state in * oof and the message in * message (which the caller
+ * must free).
+ *
+ * Return value: %TRUE if the OOF state was read, %FALSE if an error
+ * occurred.
+ **/
+gboolean
+exchange_oof_get (ExchangeAccount *account, gboolean *oof, gchar **message)
+{
+	E2kContext *ctx;
+	E2kHTTPStatus status;
+	gchar *url, *p = NULL, *checked, *ta_start, *ta_end;
+	SoupBuffer *response = NULL;
+	const gchar *body, *end;
+
+	ctx = exchange_account_get_context (account);
+	if (!ctx)
+		return FALSE;
+
+	if (!message) {
+		/* Do this the easy way */
+		const gchar *prop = E2K_PR_EXCHANGE_OOF_STATE;
+		E2kResult *results;
+		gint nresults = 0;
+
+		url = e2k_uri_concat (account->home_uri, "NON_IPM_SUBTREE/");
+		status = e2k_context_propfind (ctx, NULL, url, &prop, 1,
+					       &results, &nresults);
+		g_free (url);
+		if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status) || nresults == 0)
+			return FALSE;
+
+		prop = e2k_properties_get_prop (results[0].props, E2K_PR_EXCHANGE_OOF_STATE);
+		*oof = prop && atoi (prop);
+
+		e2k_results_free (results, nresults);
+		return TRUE;
+	}
+
+	url = e2k_uri_concat (account->home_uri, "?Cmd=options");
+	status = e2k_context_get_owa (ctx, NULL, url, FALSE, &response);
+	g_free (url);
+	if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status))
+		return FALSE;
+
+	body = response->data;
+	end = body + response->length;
+	p = find_str_case (body, "<!--End OOF Assist-->", end);
+	if (p)
+		end = p;
+
+	p = find_str_case (body, "name=\"OofState\"", end);
+	if (p)
+		p = find_str_case (body, "value=\"1\"", end);
+	if (!p) {
+		g_warning ("Could not find OofState in options page");
+		soup_buffer_free (response);
+		return FALSE;
+	}
+
+	checked = find_str_case (p, "checked", end);
+	*oof = (checked && checked < strchr (p, '>'));
+
+	if (message) {
+		ta_end = find_str_case (p, "</textarea>", end);
+		if (!ta_end) {
+			g_warning ("Could not find OOF text in options page");
+			soup_buffer_free (response);
+			*message = g_strdup ("");
+			return TRUE;
+		}
+		for (ta_start = ta_end - 1; ta_start > p; ta_start--) {
+			if (*ta_start == '>')
+				break;
+		}
+		if (*ta_start++ != '>') {
+			g_warning ("Could not find OOF text in options page");
+			soup_buffer_free (response);
+			*message = g_strdup ("");
+			return TRUE;
+		}
+
+		*message = g_strndup (ta_start, ta_end - ta_start);
+		/* FIXME: HTML decode */
+
+	}
+
+	soup_buffer_free (response);
+	return TRUE;
+}
+
+/**
+ * exchange_oof_set:
+ * @account: an #ExchangeAccount
+ * @oof: new OOF state
+ * @message: new OOF message, or %NULL
+ *
+ * Sets the OOF state for @account to @oof.
+ *
+ * Return value: %TRUE if the OOF state was updated, %FALSE if an
+ * error occurred.
+ **/
+gboolean
+exchange_oof_set (ExchangeAccount *account, gboolean oof, const gchar *message)
+{
+	E2kContext *ctx;
+	E2kHTTPStatus status;
+
+	ctx = exchange_account_get_context (account);
+	if (!ctx)
+		return FALSE;
+
+	if (message) {
+		gchar *body, *message_enc;
+
+		message_enc = e2k_uri_encode (message, FALSE, NULL);
+		body = g_strdup_printf ("Cmd=options&OofState=%d&"
+					"OofReply=%s",
+					oof ? 1 : 0, message_enc);
+		status = e2k_context_post (ctx, NULL, account->home_uri,
+					   "application/x-www-form-urlencoded",
+					   body, strlen (body), NULL, NULL);
+		g_free (message_enc);
+		g_free (body);
+	} else {
+		E2kProperties *props;
+		gchar *url;
+
+		props = e2k_properties_new ();
+		e2k_properties_set_bool (props, E2K_PR_EXCHANGE_OOF_STATE, oof);
+		url = e2k_uri_concat (account->home_uri, "NON_IPM_SUBTREE/");
+		/* Need to pass TRUE for "create" here or it won't work */
+		status = e2k_context_proppatch (ctx, NULL, url, props,
+						TRUE, NULL);
+		g_free (url);
+		e2k_properties_free (props);
+	}
+
+	return E2K_HTTP_STATUS_IS_SUCCESSFUL (status) ||
+		E2K_HTTP_STATUS_IS_REDIRECTION (status);
+}
diff --git a/server/storage/exchange-oof.h b/server/storage/exchange-oof.h
new file mode 100644
index 0000000..ca279be
--- /dev/null
+++ b/server/storage/exchange-oof.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2002-2004 Novell, Inc. */
+
+#ifndef __EXCHANGE_OOF_H__
+#define __EXCHANGE_OOF_H__
+
+#include "exchange-types.h"
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+gboolean      exchange_oof_get              (ExchangeAccount  *account,
+					     gboolean         *oof,
+					     gchar            **mmsg);
+gboolean      exchange_oof_set              (ExchangeAccount  *account,
+					     gboolean          oof,
+					     const gchar       *msg);
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_OOF_H__ */
diff --git a/server/storage/exchange-types.h b/server/storage/exchange-types.h
new file mode 100644
index 0000000..cfb1556
--- /dev/null
+++ b/server/storage/exchange-types.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2002-2004 Novell, Inc. */
+
+#include "e2k-types.h"
+#include <glib-object.h>
+
+#ifndef __EXCHANGE_TYPES_H__
+#define __EXCHANGE_TYPES_H__
+
+typedef struct _ExchangeAccount                  ExchangeAccount;
+typedef struct _ExchangeAccountPrivate           ExchangeAccountPrivate;
+typedef struct _ExchangeAccountClass             ExchangeAccountClass;
+typedef struct _ExchangeConfigListener           ExchangeConfigListener;
+typedef struct _ExchangeConfigListenerPrivate    ExchangeConfigListenerPrivate;
+typedef struct _ExchangeConfigListenerClass      ExchangeConfigListenerClass;
+typedef struct _ExchangeHierarchy                ExchangeHierarchy;
+typedef struct _ExchangeHierarchyPrivate         ExchangeHierarchyPrivate;
+typedef struct _ExchangeHierarchyClass           ExchangeHierarchyClass;
+typedef struct _ExchangeHierarchyFavorites        ExchangeHierarchyFavorites;
+typedef struct _ExchangeHierarchyFavoritesPrivate ExchangeHierarchyFavoritesPrivate;
+typedef struct _ExchangeHierarchyFavoritesClass   ExchangeHierarchyFavoritesClass;
+typedef struct _ExchangeHierarchyGAL             ExchangeHierarchyGAL;
+typedef struct _ExchangeHierarchyGALPrivate      ExchangeHierarchyGALPrivate;
+typedef struct _ExchangeHierarchyGALClass        ExchangeHierarchyGALClass;
+typedef struct _ExchangeHierarchySomeDAV         ExchangeHierarchySomeDAV;
+typedef struct _ExchangeHierarchySomeDAVPrivate  ExchangeHierarchySomeDAVPrivate;
+typedef struct _ExchangeHierarchySomeDAVClass    ExchangeHierarchySomeDAVClass;
+typedef struct _ExchangeHierarchyWebDAV          ExchangeHierarchyWebDAV;
+typedef struct _ExchangeHierarchyWebDAVPrivate   ExchangeHierarchyWebDAVPrivate;
+typedef struct _ExchangeHierarchyWebDAVClass     ExchangeHierarchyWebDAVClass;
+typedef struct _ExchangeOfflineHandler           ExchangeOfflineHandler;
+typedef struct _ExchangeOfflineHandlerClass      ExchangeOfflineHandlerClass;
+typedef struct _ExchangePermissionsDialog        ExchangePermissionsDialog;
+typedef struct _ExchangePermissionsDialogPrivate ExchangePermissionsDialogPrivate;
+typedef struct _ExchangePermissionsDialogClass   ExchangePermissionsDialogClass;
+typedef struct _ExchangeStorage                  ExchangeStorage;
+typedef struct _ExchangeStoragePrivate           ExchangeStoragePrivate;
+typedef struct _ExchangeStorageClass             ExchangeStorageClass;
+
+typedef struct _EFolderExchange                  EFolderExchange;
+typedef struct _EFolderExchangePrivate           EFolderExchangePrivate;
+typedef struct _EFolderExchangeClass             EFolderExchangeClass;
+
+typedef struct  XCBackend                        XCBackend;
+typedef struct  XCBackendPrivate                 XCBackendPrivate;
+typedef struct  XCBackendClass                   XCBackendClass;
+
+typedef struct  XCBackendComponent               XCBackendComponent;
+typedef struct  XCBackendComponentPrivate        XCBackendComponentPrivate;
+typedef struct  XCBackendComponentClass          XCBackendComponentClass;
+
+typedef struct  XCBackendView		 XCBackendView;
+typedef struct  XCBackendViewPrivate		 XCBackendViewPrivate;
+typedef struct  XCBackendViewClass		 XCBackendViewClass;
+
+typedef enum {
+	EXCHANGE_HIERARCHY_PERSONAL,
+	EXCHANGE_HIERARCHY_FAVORITES,
+	EXCHANGE_HIERARCHY_PUBLIC,
+	EXCHANGE_HIERARCHY_GAL,
+	EXCHANGE_HIERARCHY_FOREIGN
+} ExchangeHierarchyType;
+
+G_END_DECLS
+
+#endif /* __EXCHANGE_TYPES_H__ */
diff --git a/server/xntlm/ChangeLog b/server/xntlm/ChangeLog
new file mode 100644
index 0000000..6b07afc
--- /dev/null
+++ b/server/xntlm/ChangeLog
@@ -0,0 +1,61 @@
+2005-04-21  Sarfraaz Ahmed <asarfraaz novell com>
+
+	* Moved the code to e-d-s from exchange
+
+2005-02-08  David Malcolm  <dmalcolm redhat com>
+
+	* Makefile.am:
+
+	Use appropriate libtool macro for convenience libraries
+	(_LTLIBRARIES, rather than _LTLIBRARIES), since some architectures
+	do not allow using static libraries this way.
+
+2005-01-08  Not Zed  <NotZed Ximian com>
+
+	** See Ximian Bug #70323.
+
+	* xntlm-md4.c, xntlm-des.c: convert all unsigned long/long to
+	guint32, it expects them to be 32 bits.
+
+2004-04-23  Dan Winship  <danw ximian com>
+
+	* xntlm.c (xntlm_negotiate): Redo the doc comment to make gtk-doc
+	happy
+
+2003-11-13  Dan Winship  <danw ximian com>
+
+	* xntlm-des.c: ANSIfy prototypes for gtk-doc
+
+2003-10-07  Dan Winship  <danw ximian com>
+
+	* xntlm.c (ntlm_lanmanager_hash): fix a gcc 3.3 warning
+
+2003-04-08  Dan Winship  <danw ximian com>
+
+	* xntlm.c (setup_schedule): Fix to not read uninitialized memory
+	to make valgrind/purify happy
+
+2003-04-04  Dan Winship  <danw ximian com>
+
+	* xntlm-des.h: add XNTLM_DES_ENCRYPT and XNTLM_DES_DECRYPT defines.
+
+	* xntlm-des.c (xntlm_deskey): make the key arg const.
+
+	* xntlm.c (setup_schedule): s/0/XNTLM_DES_ENCRYPT/
+
+2003-03-19  Dan Winship  <danw ximian com>
+
+	* Makefile.am: Make this a static library
+
+2003-02-26  Dan Winship  <danw ximian com>
+
+	* xntlm.c (strip_dup): Fix a bug
+
+2003-02-21  Dan Winship  <danw ximian com>
+
+	* xntlm.c, xntlm-des.c, xntlm-md4.c: #include config.h
+
+2002-08-05  Dan Winship  <danw ximian com>
+
+	* Split this code out of camel/soup and tidy it up
+
diff --git a/server/xntlm/Makefile.am b/server/xntlm/Makefile.am
new file mode 100644
index 0000000..9d68a04
--- /dev/null
+++ b/server/xntlm/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libxntlm.la
+
+libxntlm_la_SOURCES = \
+	xntlm.c \
+	xntlm.h \
+	xntlm-des.c \
+	xntlm-des.h \
+	xntlm-md4.c \
+	xntlm-md4.h
+
+libxntlm_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir) \
+	$(GLIB_CFLAGS)
+
+-include $(top_srcdir)/git.mk
diff --git a/server/xntlm/xntlm-des.c b/server/xntlm/xntlm-des.c
new file mode 100644
index 0000000..8d00169
--- /dev/null
+++ b/server/xntlm/xntlm-des.c
@@ -0,0 +1,346 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xntlm-des.h"
+#include <string.h>
+
+/* Public domain DES implementation from Phil Karn */
+
+static guint32 Spbox[8][64] = {
+	{ 0x01010400,0x00000000,0x00010000,0x01010404,
+	  0x01010004,0x00010404,0x00000004,0x00010000,
+	  0x00000400,0x01010400,0x01010404,0x00000400,
+	  0x01000404,0x01010004,0x01000000,0x00000004,
+	  0x00000404,0x01000400,0x01000400,0x00010400,
+	  0x00010400,0x01010000,0x01010000,0x01000404,
+	  0x00010004,0x01000004,0x01000004,0x00010004,
+	  0x00000000,0x00000404,0x00010404,0x01000000,
+	  0x00010000,0x01010404,0x00000004,0x01010000,
+	  0x01010400,0x01000000,0x01000000,0x00000400,
+	  0x01010004,0x00010000,0x00010400,0x01000004,
+	  0x00000400,0x00000004,0x01000404,0x00010404,
+	  0x01010404,0x00010004,0x01010000,0x01000404,
+	  0x01000004,0x00000404,0x00010404,0x01010400,
+	  0x00000404,0x01000400,0x01000400,0x00000000,
+	  0x00010004,0x00010400,0x00000000,0x01010004 },
+	{ 0x80108020,0x80008000,0x00008000,0x00108020,
+	  0x00100000,0x00000020,0x80100020,0x80008020,
+	  0x80000020,0x80108020,0x80108000,0x80000000,
+	  0x80008000,0x00100000,0x00000020,0x80100020,
+	  0x00108000,0x00100020,0x80008020,0x00000000,
+	  0x80000000,0x00008000,0x00108020,0x80100000,
+	  0x00100020,0x80000020,0x00000000,0x00108000,
+	  0x00008020,0x80108000,0x80100000,0x00008020,
+	  0x00000000,0x00108020,0x80100020,0x00100000,
+	  0x80008020,0x80100000,0x80108000,0x00008000,
+	  0x80100000,0x80008000,0x00000020,0x80108020,
+	  0x00108020,0x00000020,0x00008000,0x80000000,
+	  0x00008020,0x80108000,0x00100000,0x80000020,
+	  0x00100020,0x80008020,0x80000020,0x00100020,
+	  0x00108000,0x00000000,0x80008000,0x00008020,
+	  0x80000000,0x80100020,0x80108020,0x00108000 },
+	{ 0x00000208,0x08020200,0x00000000,0x08020008,
+	  0x08000200,0x00000000,0x00020208,0x08000200,
+	  0x00020008,0x08000008,0x08000008,0x00020000,
+	  0x08020208,0x00020008,0x08020000,0x00000208,
+	  0x08000000,0x00000008,0x08020200,0x00000200,
+	  0x00020200,0x08020000,0x08020008,0x00020208,
+	  0x08000208,0x00020200,0x00020000,0x08000208,
+	  0x00000008,0x08020208,0x00000200,0x08000000,
+	  0x08020200,0x08000000,0x00020008,0x00000208,
+	  0x00020000,0x08020200,0x08000200,0x00000000,
+	  0x00000200,0x00020008,0x08020208,0x08000200,
+	  0x08000008,0x00000200,0x00000000,0x08020008,
+	  0x08000208,0x00020000,0x08000000,0x08020208,
+	  0x00000008,0x00020208,0x00020200,0x08000008,
+	  0x08020000,0x08000208,0x00000208,0x08020000,
+	  0x00020208,0x00000008,0x08020008,0x00020200 },
+	{ 0x00802001,0x00002081,0x00002081,0x00000080,
+	  0x00802080,0x00800081,0x00800001,0x00002001,
+	  0x00000000,0x00802000,0x00802000,0x00802081,
+	  0x00000081,0x00000000,0x00800080,0x00800001,
+	  0x00000001,0x00002000,0x00800000,0x00802001,
+	  0x00000080,0x00800000,0x00002001,0x00002080,
+	  0x00800081,0x00000001,0x00002080,0x00800080,
+	  0x00002000,0x00802080,0x00802081,0x00000081,
+	  0x00800080,0x00800001,0x00802000,0x00802081,
+	  0x00000081,0x00000000,0x00000000,0x00802000,
+	  0x00002080,0x00800080,0x00800081,0x00000001,
+	  0x00802001,0x00002081,0x00002081,0x00000080,
+	  0x00802081,0x00000081,0x00000001,0x00002000,
+	  0x00800001,0x00002001,0x00802080,0x00800081,
+	  0x00002001,0x00002080,0x00800000,0x00802001,
+	  0x00000080,0x00800000,0x00002000,0x00802080 },
+	{ 0x00000100,0x02080100,0x02080000,0x42000100,
+	  0x00080000,0x00000100,0x40000000,0x02080000,
+	  0x40080100,0x00080000,0x02000100,0x40080100,
+	  0x42000100,0x42080000,0x00080100,0x40000000,
+	  0x02000000,0x40080000,0x40080000,0x00000000,
+	  0x40000100,0x42080100,0x42080100,0x02000100,
+	  0x42080000,0x40000100,0x00000000,0x42000000,
+	  0x02080100,0x02000000,0x42000000,0x00080100,
+	  0x00080000,0x42000100,0x00000100,0x02000000,
+	  0x40000000,0x02080000,0x42000100,0x40080100,
+	  0x02000100,0x40000000,0x42080000,0x02080100,
+	  0x40080100,0x00000100,0x02000000,0x42080000,
+	  0x42080100,0x00080100,0x42000000,0x42080100,
+	  0x02080000,0x00000000,0x40080000,0x42000000,
+	  0x00080100,0x02000100,0x40000100,0x00080000,
+	  0x00000000,0x40080000,0x02080100,0x40000100 },
+	{ 0x20000010,0x20400000,0x00004000,0x20404010,
+	  0x20400000,0x00000010,0x20404010,0x00400000,
+	  0x20004000,0x00404010,0x00400000,0x20000010,
+	  0x00400010,0x20004000,0x20000000,0x00004010,
+	  0x00000000,0x00400010,0x20004010,0x00004000,
+	  0x00404000,0x20004010,0x00000010,0x20400010,
+	  0x20400010,0x00000000,0x00404010,0x20404000,
+	  0x00004010,0x00404000,0x20404000,0x20000000,
+	  0x20004000,0x00000010,0x20400010,0x00404000,
+	  0x20404010,0x00400000,0x00004010,0x20000010,
+	  0x00400000,0x20004000,0x20000000,0x00004010,
+	  0x20000010,0x20404010,0x00404000,0x20400000,
+	  0x00404010,0x20404000,0x00000000,0x20400010,
+	  0x00000010,0x00004000,0x20400000,0x00404010,
+	  0x00004000,0x00400010,0x20004010,0x00000000,
+	  0x20404000,0x20000000,0x00400010,0x20004010 },
+	{ 0x00200000,0x04200002,0x04000802,0x00000000,
+	  0x00000800,0x04000802,0x00200802,0x04200800,
+	  0x04200802,0x00200000,0x00000000,0x04000002,
+	  0x00000002,0x04000000,0x04200002,0x00000802,
+	  0x04000800,0x00200802,0x00200002,0x04000800,
+	  0x04000002,0x04200000,0x04200800,0x00200002,
+	  0x04200000,0x00000800,0x00000802,0x04200802,
+	  0x00200800,0x00000002,0x04000000,0x00200800,
+	  0x04000000,0x00200800,0x00200000,0x04000802,
+	  0x04000802,0x04200002,0x04200002,0x00000002,
+	  0x00200002,0x04000000,0x04000800,0x00200000,
+	  0x04200800,0x00000802,0x00200802,0x04200800,
+	  0x00000802,0x04000002,0x04200802,0x04200000,
+	  0x00200800,0x00000000,0x00000002,0x04200802,
+	  0x00000000,0x00200802,0x04200000,0x00000800,
+	  0x04000002,0x04000800,0x00000800,0x00200002 },
+	{ 0x10001040,0x00001000,0x00040000,0x10041040,
+	  0x10000000,0x10001040,0x00000040,0x10000000,
+	  0x00040040,0x10040000,0x10041040,0x00041000,
+	  0x10041000,0x00041040,0x00001000,0x00000040,
+	  0x10040000,0x10000040,0x10001000,0x00001040,
+	  0x00041000,0x00040040,0x10040040,0x10041000,
+	  0x00001040,0x00000000,0x00000000,0x10040040,
+	  0x10000040,0x10001000,0x00041040,0x00040000,
+	  0x00041040,0x00040000,0x10041000,0x00001000,
+	  0x00000040,0x10040040,0x00001000,0x00041040,
+	  0x10001000,0x00000040,0x10000040,0x10040000,
+	  0x10040040,0x10000000,0x00040000,0x10001040,
+	  0x00000000,0x10041040,0x00040040,0x10000040,
+	  0x10040000,0x10001000,0x10001040,0x00000000,
+	  0x10041040,0x00041000,0x00041000,0x00001040,
+	  0x00001040,0x00040040,0x10000000,0x10041000 }
+};
+
+#undef F
+#define	F(l,r,key){\
+	work = ((r >> 4) | (r << 28)) ^ key[0];\
+	l ^= Spbox[6][work & 0x3f];\
+	l ^= Spbox[4][(work >> 8) & 0x3f];\
+	l ^= Spbox[2][(work >> 16) & 0x3f];\
+	l ^= Spbox[0][(work >> 24) & 0x3f];\
+	work = r ^ key[1];\
+	l ^= Spbox[7][work & 0x3f];\
+	l ^= Spbox[5][(work >> 8) & 0x3f];\
+	l ^= Spbox[3][(work >> 16) & 0x3f];\
+	l ^= Spbox[1][(work >> 24) & 0x3f];\
+}
+/* Encrypt or decrypt a block of data in ECB mode */
+void
+xntlm_des(XNTLM_DES_KS ks, guchar block[8])
+{
+	guint32 left,right,work;
+
+	/* Read input block and place in left/right in big-endian order */
+	left = ((guint32)block[0] << 24)
+	 | ((guint32)block[1] << 16)
+	 | ((guint32)block[2] << 8)
+	 | (guint32)block[3];
+	right = ((guint32)block[4] << 24)
+	 | ((guint32)block[5] << 16)
+	 | ((guint32)block[6] << 8)
+	 | (guint32)block[7];
+
+	/* Hoey's clever initial permutation algorithm, from Outerbridge
+	 * (see Schneier p 478)
+	 *
+	 * The convention here is the same as Outerbridge: rotate each
+	 * register left by 1 bit, i.e., so that "left" contains permuted
+	 * input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32
+	 * (using origin-1 numbering as in the FIPS). This allows us to avoid
+	 * one of the two rotates that would otherwise be required in each of
+	 * the 16 rounds.
+	 */
+	work = ((left >> 4) ^ right) & 0x0f0f0f0f;
+	right ^= work;
+	left ^= work << 4;
+	work = ((left >> 16) ^ right) & 0xffff;
+	right ^= work;
+	left ^= work << 16;
+	work = ((right >> 2) ^ left) & 0x33333333;
+	left ^= work;
+	right ^= (work << 2);
+	work = ((right >> 8) ^ left) & 0xff00ff;
+	left ^= work;
+	right ^= (work << 8);
+	right = (right << 1) | (right >> 31);
+	work = (left ^ right) & 0xaaaaaaaa;
+	left ^= work;
+	right ^= work;
+	left = (left << 1) | (left >> 31);
+
+	/* Now do the 16 rounds */
+	F(left,right,ks[0]);
+	F(right,left,ks[1]);
+	F(left,right,ks[2]);
+	F(right,left,ks[3]);
+	F(left,right,ks[4]);
+	F(right,left,ks[5]);
+	F(left,right,ks[6]);
+	F(right,left,ks[7]);
+	F(left,right,ks[8]);
+	F(right,left,ks[9]);
+	F(left,right,ks[10]);
+	F(right,left,ks[11]);
+	F(left,right,ks[12]);
+	F(right,left,ks[13]);
+	F(left,right,ks[14]);
+	F(right,left,ks[15]);
+
+	/* Inverse permutation, also from Hoey via Outerbridge and Schneier */
+	right = (right << 31) | (right >> 1);
+	work = (left ^ right) & 0xaaaaaaaa;
+	left ^= work;
+	right ^= work;
+	left = (left >> 1) | (left  << 31);
+	work = ((left >> 8) ^ right) & 0xff00ff;
+	right ^= work;
+	left ^= work << 8;
+	work = ((left >> 2) ^ right) & 0x33333333;
+	right ^= work;
+	left ^= work << 2;
+	work = ((right >> 16) ^ left) & 0xffff;
+	left ^= work;
+	right ^= work << 16;
+	work = ((right >> 4) ^ left) & 0x0f0f0f0f;
+	left ^= work;
+	right ^= work << 4;
+
+	/* Put the block back into the user's buffer with final swap */
+	block[0] = right >> 24;
+	block[1] = right >> 16;
+	block[2] = right >> 8;
+	block[3] = right;
+	block[4] = left >> 24;
+	block[5] = left >> 16;
+	block[6] = left >> 8;
+	block[7] = left;
+}
+
+/* Key schedule-related tables from FIPS-46 */
+
+/* permuted choice table (key) */
+static guchar pc1[] = {
+	57, 49, 41, 33, 25, 17,  9,
+	 1, 58, 50, 42, 34, 26, 18,
+	10,  2, 59, 51, 43, 35, 27,
+	19, 11,  3, 60, 52, 44, 36,
+
+	63, 55, 47, 39, 31, 23, 15,
+	 7, 62, 54, 46, 38, 30, 22,
+	14,  6, 61, 53, 45, 37, 29,
+	21, 13,  5, 28, 20, 12,  4
+};
+
+/* number left rotations of pc1 */
+static guchar totrot[] = {
+	1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28
+};
+
+/* permuted choice key (table) */
+static guchar pc2[] = {
+	14, 17, 11, 24,  1,  5,
+	 3, 28, 15,  6, 21, 10,
+	23, 19, 12,  4, 26,  8,
+	16,  7, 27, 20, 13,  2,
+	41, 52, 31, 37, 47, 55,
+	30, 40, 51, 45, 33, 48,
+	44, 49, 39, 56, 34, 53,
+	46, 42, 50, 36, 29, 32
+};
+
+/* End of DES-defined tables */
+
+/* bit 0 is left-most in byte */
+static gint bytebit[] = {
+	0200,0100,040,020,010,04,02,01
+};
+
+/* Generate key schedule for encryption or decryption
+ * depending on the value of "decrypt"
+ */
+void
+xntlm_deskey(XNTLM_DES_KS k, const guchar *key, gint decrypt)
+{
+	guchar pc1m[56];		/* place to modify pc1 into */
+	guchar pcr[56];		/* place to rotate pc1 into */
+	register gint i,j,l;
+	gint m;
+	guchar ks[8];
+
+	for (j=0; j<56; j++) {		/* convert pc1 to bits of key */
+		l=pc1[j]-1;		/* integer bit location	 */
+		m = l & 07;		/* find bit		 */
+		pc1m[j]=(key[l>>3] &	/* find which key byte l is in */
+			bytebit[m])	/* and which bit of that byte */
+			? 1 : 0;	/* and store 1-bit result */
+	}
+	for (i=0; i<16; i++) {		/* key chunk for each iteration */
+		memset(ks,0,sizeof(ks));	/* Clear key schedule */
+		for (j=0; j<56; j++)	/* rotate pc1 the right amount */
+			pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28];
+			/* rotate left and right halves independently */
+		for (j=0; j<48; j++){	/* select bits individually */
+			/* check bit that goes to ks[j] */
+			if (pcr[pc2[j]-1]) {
+				/* mask it in if it's there */
+				l= j % 6;
+				ks[j/6] |= bytebit[l] >> 2;
+			}
+		}
+		/* Now convert to packed odd/even interleaved form */
+		k[i][0] = ((guint32)ks[0] << 24)
+		 | ((guint32)ks[2] << 16)
+		 | ((guint32)ks[4] << 8)
+		 | ((guint32)ks[6]);
+		k[i][1] = ((guint32)ks[1] << 24)
+		 | ((guint32)ks[3] << 16)
+		 | ((guint32)ks[5] << 8)
+		 | ((guint32)ks[7]);
+	}
+}
diff --git a/server/xntlm/xntlm-des.h b/server/xntlm/xntlm-des.h
new file mode 100644
index 0000000..ea39144
--- /dev/null
+++ b/server/xntlm/xntlm-des.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef _XNTLM_DES_H
+#define _XNTLM_DES_H
+
+#include <glib.h>
+
+typedef guint32 XNTLM_DES_KS[16][2];
+
+enum {
+	XNTLM_DES_ENCRYPT = 0,
+	XNTLM_DES_DECRYPT = 1
+};
+
+void xntlm_deskey (XNTLM_DES_KS ks, const guchar *key, gint decrypt);
+
+void xntlm_des (XNTLM_DES_KS ks, guchar block[8]);
+
+#endif /* _XNTLM_DES_H */
diff --git a/server/xntlm/xntlm-md4.c b/server/xntlm/xntlm-md4.c
new file mode 100644
index 0000000..382f984
--- /dev/null
+++ b/server/xntlm/xntlm-md4.c
@@ -0,0 +1,175 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include "xntlm-md4.h"
+
+#include <string.h>
+
+/* MD4 encoder. The reference implementation in RFC1320 isn't
+ * GPL-compatible.
+ */
+
+#define F(X,Y,Z) ( ((X)&(Y)) | ((~(X))&(Z)) )
+#define G(X,Y,Z) ( ((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)) )
+#define H(X,Y,Z) ( (X)^(Y)^(Z) )
+#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) )
+
+static void
+md4sum_round (const guchar *M,
+	      guint32 *AA, guint32 *BB,
+	      guint32 *CC, guint32 *DD)
+{
+	guint32 A, B, C, D, X[16];
+	gint i;
+
+	for (i = 0; i < 16; i++) {
+		X[i] =  (M[i*4]) | (M[i*4 + 1] << 8) |
+			(M[i*4 + 2] << 16) | (M[i*4 + 3] << 24);
+	}
+
+	A = *AA;
+	B = *BB;
+	C = *CC;
+	D = *DD;
+
+	A = ROT (A + F(B, C, D) + X[0], 3);
+	D = ROT (D + F(A, B, C) + X[1], 7);
+	C = ROT (C + F(D, A, B) + X[2], 11);
+	B = ROT (B + F(C, D, A) + X[3], 19);
+	A = ROT (A + F(B, C, D) + X[4], 3);
+	D = ROT (D + F(A, B, C) + X[5], 7);
+	C = ROT (C + F(D, A, B) + X[6], 11);
+	B = ROT (B + F(C, D, A) + X[7], 19);
+	A = ROT (A + F(B, C, D) + X[8], 3);
+	D = ROT (D + F(A, B, C) + X[9], 7);
+	C = ROT (C + F(D, A, B) + X[10], 11);
+	B = ROT (B + F(C, D, A) + X[11], 19);
+	A = ROT (A + F(B, C, D) + X[12], 3);
+	D = ROT (D + F(A, B, C) + X[13], 7);
+	C = ROT (C + F(D, A, B) + X[14], 11);
+	B = ROT (B + F(C, D, A) + X[15], 19);
+
+	A = ROT (A + G(B, C, D) + X[0]  + 0x5A827999, 3);
+	D = ROT (D + G(A, B, C) + X[4]  + 0x5A827999, 5);
+	C = ROT (C + G(D, A, B) + X[8]  + 0x5A827999, 9);
+	B = ROT (B + G(C, D, A) + X[12] + 0x5A827999, 13);
+	A = ROT (A + G(B, C, D) + X[1]  + 0x5A827999, 3);
+	D = ROT (D + G(A, B, C) + X[5]  + 0x5A827999, 5);
+	C = ROT (C + G(D, A, B) + X[9]  + 0x5A827999, 9);
+	B = ROT (B + G(C, D, A) + X[13] + 0x5A827999, 13);
+	A = ROT (A + G(B, C, D) + X[2]  + 0x5A827999, 3);
+	D = ROT (D + G(A, B, C) + X[6]  + 0x5A827999, 5);
+	C = ROT (C + G(D, A, B) + X[10] + 0x5A827999, 9);
+	B = ROT (B + G(C, D, A) + X[14] + 0x5A827999, 13);
+	A = ROT (A + G(B, C, D) + X[3]  + 0x5A827999, 3);
+	D = ROT (D + G(A, B, C) + X[7]  + 0x5A827999, 5);
+	C = ROT (C + G(D, A, B) + X[11] + 0x5A827999, 9);
+	B = ROT (B + G(C, D, A) + X[15] + 0x5A827999, 13);
+
+	A = ROT (A + H(B, C, D) + X[0]  + 0x6ED9EBA1, 3);
+	D = ROT (D + H(A, B, C) + X[8]  + 0x6ED9EBA1, 9);
+	C = ROT (C + H(D, A, B) + X[4]  + 0x6ED9EBA1, 11);
+	B = ROT (B + H(C, D, A) + X[12] + 0x6ED9EBA1, 15);
+	A = ROT (A + H(B, C, D) + X[2]  + 0x6ED9EBA1, 3);
+	D = ROT (D + H(A, B, C) + X[10] + 0x6ED9EBA1, 9);
+	C = ROT (C + H(D, A, B) + X[6]  + 0x6ED9EBA1, 11);
+	B = ROT (B + H(C, D, A) + X[14] + 0x6ED9EBA1, 15);
+	A = ROT (A + H(B, C, D) + X[1]  + 0x6ED9EBA1, 3);
+	D = ROT (D + H(A, B, C) + X[9]  + 0x6ED9EBA1, 9);
+	C = ROT (C + H(D, A, B) + X[5]  + 0x6ED9EBA1, 11);
+	B = ROT (B + H(C, D, A) + X[13] + 0x6ED9EBA1, 15);
+	A = ROT (A + H(B, C, D) + X[3]  + 0x6ED9EBA1, 3);
+	D = ROT (D + H(A, B, C) + X[11] + 0x6ED9EBA1, 9);
+	C = ROT (C + H(D, A, B) + X[7]  + 0x6ED9EBA1, 11);
+	B = ROT (B + H(C, D, A) + X[15] + 0x6ED9EBA1, 15);
+
+	*AA += A;
+	*BB += B;
+	*CC += C;
+	*DD += D;
+}
+
+/**
+ * xntlm_md4sum:
+ * @in: the input data
+ * @nbytes: the length of @in in bytes
+ * @digest: buffer to compute the digest
+ *
+ * Computes the MD4 checksum of @in and puts it in @digest.
+ **/
+void
+xntlm_md4sum (const guchar *in, gint nbytes, guchar digest[16])
+{
+	guchar M[128];
+	guint32 A, B, C, D;
+	gint pbytes, nbits = nbytes * 8, remaining_bytes;
+	gint total_len, offset;
+
+	pbytes = (120 - (nbytes % 64)) % 64;
+	total_len = nbytes + pbytes + 8;
+
+	A = 0x67452301;
+	B = 0xEFCDAB89;
+	C = 0x98BADCFE;
+	D = 0x10325476;
+
+	for (offset = 0; offset < nbytes - 64; offset += 64)
+		md4sum_round (in + offset, &A, &B, &C, &D);
+
+	/* Copy the leftover part of the message into M */
+	remaining_bytes = nbytes - offset;
+	memcpy (M, in + offset, remaining_bytes);
+
+	/* Append a single "1" bit and appropriate padding */
+	M[remaining_bytes] = 0x80;
+	memset (M + remaining_bytes + 1, 0, pbytes - 1 + 8);
+
+	/* Append length. */
+	M[remaining_bytes + pbytes] = nbits & 0xFF;
+	M[remaining_bytes + pbytes + 1] = (nbits >> 8) & 0xFF;
+	M[remaining_bytes + pbytes + 2] = (nbits >> 16) & 0xFF;
+	M[remaining_bytes + pbytes + 3] = (nbits >> 24) & 0xFF;
+	/* We assume nbits is less than 2^32 */
+
+	md4sum_round (M, &A, &B, &C, &D);
+	if (remaining_bytes > 56)
+		md4sum_round (M + 64, &A, &B, &C, &D);
+
+	digest[0]  =  A        & 0xFF;
+	digest[1]  = (A >>  8) & 0xFF;
+	digest[2]  = (A >> 16) & 0xFF;
+	digest[3]  = (A >> 24) & 0xFF;
+	digest[4]  =  B        & 0xFF;
+	digest[5]  = (B >>  8) & 0xFF;
+	digest[6]  = (B >> 16) & 0xFF;
+	digest[7]  = (B >> 24) & 0xFF;
+	digest[8]  =  C        & 0xFF;
+	digest[9]  = (C >>  8) & 0xFF;
+	digest[10] = (C >> 16) & 0xFF;
+	digest[11] = (C >> 24) & 0xFF;
+	digest[12] =  D        & 0xFF;
+	digest[13] = (D >>  8) & 0xFF;
+	digest[14] = (D >> 16) & 0xFF;
+	digest[15] = (D >> 24) & 0xFF;
+}
diff --git a/server/xntlm/xntlm-md4.h b/server/xntlm/xntlm-md4.h
new file mode 100644
index 0000000..7e4eccd
--- /dev/null
+++ b/server/xntlm/xntlm-md4.h
@@ -0,0 +1,10 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef _XNTLM_MD4_H
+#define _XNTLM_MD4_H
+
+void xntlm_md4sum (const guchar *in, gint nbytes,
+		   guchar digest[16]);
+
+#endif /* _XNTLM_MD4_H */
diff --git a/server/xntlm/xntlm.c b/server/xntlm/xntlm.c
new file mode 100644
index 0000000..def4b6d
--- /dev/null
+++ b/server/xntlm/xntlm.c
@@ -0,0 +1,323 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* Copyright (C) 2001-2004 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xntlm.h"
+#include "xntlm-des.h"
+#include "xntlm-md4.h"
+
+#include <ctype.h>
+#include <string.h>
+
+static guchar NTLM_NEGOTIATE_MESSAGE[] = {
+	 'N',  'T',  'L',  'M',  'S',  'S',  'P', 0x00,
+	0x01, 0x00, 0x00, 0x00, 0x06, 0x82, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00
+};
+
+/**
+ * xntlm_negotiate:
+ *
+ * Creates an NTLM Type 1 (Negotiate) message
+ *
+ * Return value: the message
+ **/
+GByteArray *
+xntlm_negotiate (void)
+{
+	GByteArray *message;
+
+	message = g_byte_array_new ();
+	g_byte_array_append (message, NTLM_NEGOTIATE_MESSAGE,
+			     sizeof (NTLM_NEGOTIATE_MESSAGE));
+	return message;
+}
+
+#define GET_SHORTY(p) ((p)[0] + ((p)[1] << 8))
+
+static gchar *
+strip_dup (guchar *mem, gint len)
+{
+	gchar *buf = g_malloc (len / 2 + 1), *p = buf;
+
+	while (len > 0) {
+		*p = (gchar)*mem;
+		p++;
+		mem += 2;
+		len -= 2;
+	}
+
+	*p = '\0';
+	return buf;
+}
+
+#define NTLM_CHALLENGE_NONCE_POS       24
+#define NTLM_CHALLENGE_NONCE_LEN        8
+
+#define NTLM_CHALLENGE_DATA_OFFSET_POS 44
+#define NTLM_CHALLENGE_DATA_LENGTH_POS 40
+
+#define NTLM_CHALLENGE_DATA_NT_DOMAIN   2
+#define NTLM_CHALLENGE_DATA_W2K_DOMAIN  4
+
+#define NTLM_CHALLENGE_BASE_SIZE       48
+
+/**
+ * xntlm_parse_challenge:
+ * @challenge: buffer containing an NTLM Type 2 (Challenge) message
+ * @len: the length of @challenge
+ * @nonce: return variable for the challenge nonce, or %NULL
+ * @nt_domain: return variable for the server NT domain, or %NULL
+ * @w2k_domain: return variable for the server W2k domain, or %NULL
+ *
+ * Attempts to parse the challenge in @challenge. If @nonce is
+ * non-%NULL, the 8-byte nonce from @challenge will be returned in it.
+ * Likewise, if @nt_domain and/or @w2k_domain are non-%NULL, the
+ * server's domain names will be returned in them. The strings
+ * returned must be freed with g_free().
+ *
+ * Return value: %TRUE if the challenge could be parsed,
+ * %FALSE otherwise.
+ **/
+gboolean
+xntlm_parse_challenge (gpointer challenge, gint len, gchar **nonce,
+		       gchar **nt_domain, gchar **w2k_domain)
+{
+	guchar *chall = (guchar *)challenge;
+	gint off, dlen, doff, type;
+
+	if (len < NTLM_CHALLENGE_BASE_SIZE)
+		return FALSE;
+
+	off = GET_SHORTY (chall + NTLM_CHALLENGE_DATA_OFFSET_POS);
+	dlen = GET_SHORTY (chall + NTLM_CHALLENGE_DATA_LENGTH_POS);
+	if (len < off + dlen)
+		return FALSE;
+
+	if (nonce) {
+		*nonce = g_memdup (chall + NTLM_CHALLENGE_NONCE_POS,
+				   NTLM_CHALLENGE_NONCE_LEN);
+	}
+
+	if (!nt_domain && !w2k_domain)
+		return TRUE;
+
+	while (off < len - 4) {
+		type = GET_SHORTY (chall + off);
+		dlen = GET_SHORTY (chall + off + 2);
+		doff = off + 4;
+		if (doff + dlen > len)
+			break;
+
+		switch (type) {
+		case NTLM_CHALLENGE_DATA_NT_DOMAIN:
+			if (nt_domain)
+				*nt_domain = strip_dup (chall + doff, dlen);
+			break;
+		case NTLM_CHALLENGE_DATA_W2K_DOMAIN:
+			if (w2k_domain)
+				*w2k_domain = strip_dup (chall + doff, dlen);
+			break;
+		}
+
+		off = doff + dlen;
+	}
+
+	return TRUE;
+}
+
+static void
+ntlm_set_string (GByteArray *ba, gint offset, const gchar *data, gint len)
+{
+	ba->data[offset    ] = ba->data[offset + 2] =  len       & 0xFF;
+	ba->data[offset + 1] = ba->data[offset + 3] = (len >> 8) & 0xFF;
+	ba->data[offset + 4] =  ba->len       & 0xFF;
+	ba->data[offset + 5] = (ba->len >> 8) & 0xFF;
+	g_byte_array_append (ba, (guint8 *) data, len);
+}
+
+static void ntlm_lanmanager_hash (const gchar *password, gchar hash[21]);
+static void ntlm_nt_hash         (const gchar *password, gchar hash[21]);
+static void ntlm_calc_response   (const guchar key[21],
+				  const guchar plaintext[8],
+				  guchar results[24]);
+
+static guchar NTLM_RESPONSE_MESSAGE_HEADER[] = {
+	 'N',  'T',  'L',  'M',  'S',  'S',  'P', 0x00,
+	0x03, 0x00, 0x00, 0x00, 0x02, 0x82, 0x00, 0x00
+};
+
+#define NTLM_RESPONSE_BASE_SIZE             64
+#define NTLM_RESPONSE_LM_RESP_OFFSET        12
+#define NTLM_RESPONSE_NT_RESP_OFFSET        20
+#define NTLM_RESPONSE_DOMAIN_OFFSET         28
+#define NTLM_RESPONSE_USER_OFFSET           36
+#define NTLM_RESPONSE_WORKSTATION_OFFSET    44
+
+/**
+ * xntlm_authenticate:
+ * @nonce: the nonce from an NTLM Type 2 (Challenge) message
+ * @domain: the NT domain to authenticate against
+ * @user: the name of the user in @domain
+ * @password: @user's password
+ * @workstation: the name of the local workstation authenticated
+ * against, or %NULL.
+ *
+ * Generates an NTLM Type 3 (Authenticate) message from the given
+ * data. @workstation is provided for completeness, but can basically
+ * always be left %NULL.
+ *
+ * Return value: the NTLM Type 3 message
+ **/
+GByteArray *
+xntlm_authenticate (const gchar *nonce, const gchar *domain,
+		    const gchar *user, const gchar *password,
+		    const gchar *workstation)
+{
+	GByteArray *message;
+	guchar hash[21], lm_resp[24], nt_resp[24];
+
+	if (!workstation)
+		workstation = "";
+
+	message = g_byte_array_new ();
+
+	ntlm_lanmanager_hash (password, (gchar *) hash);
+	ntlm_calc_response (hash, (guchar *) nonce, lm_resp);
+	ntlm_nt_hash (password, (gchar *) hash);
+	ntlm_calc_response (hash, (guchar *) nonce, nt_resp);
+
+	g_byte_array_set_size (message, NTLM_RESPONSE_BASE_SIZE);
+	memset (message->data, 0, NTLM_RESPONSE_BASE_SIZE);
+	memcpy (message->data, NTLM_RESPONSE_MESSAGE_HEADER,
+		sizeof (NTLM_RESPONSE_MESSAGE_HEADER));
+
+	ntlm_set_string (message, NTLM_RESPONSE_DOMAIN_OFFSET,
+			 domain, strlen (domain));
+	ntlm_set_string (message, NTLM_RESPONSE_USER_OFFSET,
+			 user, strlen (user));
+	ntlm_set_string (message, NTLM_RESPONSE_WORKSTATION_OFFSET,
+			 workstation, strlen (workstation));
+	ntlm_set_string (message, NTLM_RESPONSE_LM_RESP_OFFSET,
+			 (gchar *) lm_resp, sizeof (lm_resp));
+	ntlm_set_string (message, NTLM_RESPONSE_NT_RESP_OFFSET,
+			 (gchar *) nt_resp, sizeof (nt_resp));
+
+	return message;
+}
+
+static void
+setup_schedule (const guchar *key_56, XNTLM_DES_KS ks)
+{
+	guchar key[8];
+	gint i, c, bit;
+
+	key[0] = (key_56[0]);
+	key[1] = (key_56[1] >> 1) | ((key_56[0] << 7) & 0xFF);
+	key[2] = (key_56[2] >> 2) | ((key_56[1] << 6) & 0xFF);
+	key[3] = (key_56[3] >> 3) | ((key_56[2] << 5) & 0xFF);
+	key[4] = (key_56[4] >> 4) | ((key_56[3] << 4) & 0xFF);
+	key[5] = (key_56[5] >> 5) | ((key_56[4] << 3) & 0xFF);
+	key[6] = (key_56[6] >> 6) | ((key_56[5] << 2) & 0xFF);
+	key[7] =                    ((key_56[6] << 1) & 0xFF);
+
+	/* Fix parity */
+	for (i = 0; i < 8; i++) {
+		for (c = bit = 0; bit < 8; bit++)
+			if (key [i] & (1 << bit))
+				c++;
+		if (!(c & 1))
+			key [i] ^= 0x01;
+	}
+
+        xntlm_deskey (ks, key, XNTLM_DES_ENCRYPT);
+}
+
+static guchar LM_PASSWORD_MAGIC[] = {
+	0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25,
+	0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25,
+	0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static void
+ntlm_lanmanager_hash (const gchar *password, gchar hash[21])
+{
+	guchar lm_password [15];
+	XNTLM_DES_KS ks;
+	guint i;
+
+	for (i = 0; i < 14 && password [i]; i++)
+		lm_password [i] = toupper ((guchar) password [i]);
+
+	for (; i < sizeof (lm_password); i++)
+		lm_password [i] = '\0';
+
+	memcpy (hash, LM_PASSWORD_MAGIC, sizeof (LM_PASSWORD_MAGIC));
+
+	setup_schedule (lm_password, ks);
+	xntlm_des (ks, (guchar *) hash);
+
+	setup_schedule (lm_password + 7, ks);
+	xntlm_des (ks, (guchar *) hash + 8);
+}
+
+static void
+ntlm_nt_hash (const gchar *password, gchar hash[21])
+{
+	guchar *buf, *p;
+
+	p = buf = g_malloc (strlen (password) * 2);
+
+	while (*password) {
+		*p++ = *password++;
+		*p++ = '\0';
+	}
+
+	xntlm_md4sum (buf, p - buf, (guchar *) hash);
+	memset (hash + 16, 0, 5);
+
+	g_free (buf);
+}
+
+static void
+ntlm_calc_response (const guchar key[21], const guchar plaintext[8],
+		    guchar results[24])
+{
+        XNTLM_DES_KS ks;
+
+	memcpy (results, plaintext, 8);
+	memcpy (results + 8, plaintext, 8);
+	memcpy (results + 16, plaintext, 8);
+
+        setup_schedule (key, ks);
+	xntlm_des (ks, results);
+
+        setup_schedule (key + 7, ks);
+	xntlm_des (ks, results + 8);
+
+        setup_schedule (key + 14, ks);
+        xntlm_des (ks, results + 16);
+}
+
diff --git a/server/xntlm/xntlm.h b/server/xntlm/xntlm.h
new file mode 100644
index 0000000..ff052da
--- /dev/null
+++ b/server/xntlm/xntlm.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef _XNTLM_H
+#define _XNTLM_H
+
+#include <glib.h>
+
+GByteArray *xntlm_negotiate       (void);
+gboolean    xntlm_parse_challenge (gpointer challenge, gint len, gchar **nonce,
+				   gchar **nt_domain, gchar **w2k_domain);
+GByteArray *xntlm_authenticate    (const gchar *nonce, const gchar *domain,
+				   const gchar *user, const gchar *password,
+				   const gchar *workstation);
+
+#endif /* _XNTLM_H */
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 37fe910..c494138 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,42 +1,54 @@
 AM_CPPFLAGS = \
 	-I$(top_srcdir) \
 	-I$(top_srcdir)/camel \
-	-DG_LOG_DOMAIN=\"evolution-exchange-storage\" \
-	$(LIBEXCHANGE_CFLAGS) \
-	$(EXCHANGE_STORAGE_CFLAGS) \
+	-I$(top_srcdir)/server/lib \
+	-I$(top_srcdir)/server/storage \
+	-I$(top_builddir)/server/lib \
+	-DG_LOG_DOMAIN="\"evolution-exchange-storage\"" \
+	$(GNOME_PLATFORM_CFLAGS) \
+	$(EVOLUTION_DATA_SERVER_CFLAGS) \
+	$(EVOLUTION_PLUGIN_CFLAGS) \
 	$(LDAP_CFLAGS) \
-	-DPREFIX=\"$(prefix)\" \
+	-DPREFIX=\""$(prefix)"\" \
 	-DSYSCONFDIR=\""$(sysconfdir)"\" \
 	-DDATADIR=\""$(datadir)"\" \
 	-DLIBDIR=\""$(datadir)"\" \
 	-DCONNECTOR_IMAGESDIR=\""$(imagesdir)"\" \
 	-DCONNECTOR_UIDIR=\""$(uidir)"\" \
-	-DCONNECTOR_LOCALEDIR=\""$(localedir)\""
+	-DCONNECTOR_LOCALEDIR=\""$(localedir)"\"
 
 noinst_PROGRAMS = \
 	exchange-connector-setup
 
 noinst_LTLIBRARIES = libevolution-exchange-shared.la
 
-libevolution_exchange_shared_la_SOURCES = 	\
-	exchange-share-config-listener.h	\
+libevolution_exchange_shared_la_SOURCES = \
+	exchange-share-config-listener.h \
 	exchange-share-config-listener.c
 
-libevolution_exchange_shared_la_LIBADD = 	\
-	$(EXCHANGE_STORAGE_LIBS)		\
-	$(LIBEXCHANGE_LIBS)
+libevolution_exchange_shared_la_LIBADD = \
+	$(top_builddir)/server/xntlm/libxntlm.la \
+	$(top_builddir)/server/lib/libexchange.la \
+	$(top_builddir)/server/storage/libexchange-storage.la \
+	$(GNOME_PLATFORM_LIBS) \
+	$(EVOLUTION_DATA_SERVER_LIBS) \
+	$(EVOLUTION_PLUGIN_LIBS)
 
 libevolution_exchange_shared_la_LDFLAGS = $(NO_UNDEFINED)
 
-exchange_connector_setup_SOURCES =	\
-	ximian-connector-setup.c	\
-	exchange-autoconfig-wizard.h	\
+exchange_connector_setup_SOURCES = \
+	ximian-connector-setup.c \
+	exchange-autoconfig-wizard.h \
 	exchange-autoconfig-wizard.c
 
 exchange_connector_setup_LDADD = \
-	$(LDAP_LIBS) \
-	$(EXCHANGE_STORAGE_LIBS) \
-	$(LIBEXCHANGE_LIBS)
+	$(top_builddir)/server/xntlm/libxntlm.la \
+	$(top_builddir)/server/lib/libexchange.la \
+	$(top_builddir)/server/storage/libexchange-storage.la \
+	$(GNOME_PLATFORM_LIBS) \
+	$(EVOLUTION_DATA_SERVER_LIBS) \
+	$(EVOLUTION_PLUGIN_LIBS) \
+	$(LDAP_LIBS)
 
 install-exec-local: exchange-connector-setup
 	$(mkinstalldirs) $(DESTDIR)$(bindir)



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