[evolution-exchange] Move server code over from evolution-data-server.
- From: Matthew Barnes <mbarnes src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [evolution-exchange] Move server code over from evolution-data-server.
- Date: Thu, 8 Oct 2009 17:05:42 +0000 (UTC)
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’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 **)×tamp, 10) - 1900;
+ if (*timestamp++ != '-')
+ return -1;
+ tm.tm_mon = strtoul (timestamp, (gchar **)×tamp, 10) - 1;
+ if (*timestamp++ != '-')
+ return -1;
+ tm.tm_mday = strtoul (timestamp, (gchar **)×tamp, 10);
+ if (*timestamp++ != 'T')
+ return -1;
+ tm.tm_hour = strtoul (timestamp, (gchar **)×tamp, 10);
+ if (*timestamp++ != ':')
+ return -1;
+ tm.tm_min = strtoul (timestamp, (gchar **)×tamp, 10);
+ if (*timestamp++ != ':')
+ return -1;
+ tm.tm_sec = strtoul (timestamp, (gchar **)×tamp, 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, "<");
+ break;
+ case '>':
+ g_string_append (string, ">");
+ break;
+ case '&':
+ g_string_append (string, "&");
+ break;
+ case '"':
+ g_string_append (string, """);
+ 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]