First movement of Exchange server communication code to e-d-s HEAD.
authorAhmed Sarfraaz <sarfraaz@src.gnome.org>
Fri, 10 Jun 2005 10:51:10 +0000 (10:51 +0000)
committerAhmed Sarfraaz <sarfraaz@src.gnome.org>
Fri, 10 Jun 2005 10:51:10 +0000 (10:51 +0000)
88 files changed:
servers/exchange/ChangeLog [new file with mode: 0644]
servers/exchange/Makefile.am [new file with mode: 0644]
servers/exchange/lib/Makefile.am [new file with mode: 0644]
servers/exchange/lib/e2k-action.c [new file with mode: 0644]
servers/exchange/lib/e2k-action.h [new file with mode: 0644]
servers/exchange/lib/e2k-autoconfig.c [new file with mode: 0644]
servers/exchange/lib/e2k-autoconfig.h [new file with mode: 0644]
servers/exchange/lib/e2k-context.c [new file with mode: 0644]
servers/exchange/lib/e2k-context.h [new file with mode: 0644]
servers/exchange/lib/e2k-encoding-utils.c [new file with mode: 0644]
servers/exchange/lib/e2k-encoding-utils.h [new file with mode: 0644]
servers/exchange/lib/e2k-freebusy.c [new file with mode: 0644]
servers/exchange/lib/e2k-freebusy.h [new file with mode: 0644]
servers/exchange/lib/e2k-global-catalog.c [new file with mode: 0644]
servers/exchange/lib/e2k-global-catalog.h [new file with mode: 0644]
servers/exchange/lib/e2k-http-utils.c [new file with mode: 0644]
servers/exchange/lib/e2k-http-utils.h [new file with mode: 0644]
servers/exchange/lib/e2k-kerberos.c [new file with mode: 0644]
servers/exchange/lib/e2k-kerberos.h [new file with mode: 0644]
servers/exchange/lib/e2k-operation.c [new file with mode: 0644]
servers/exchange/lib/e2k-operation.h [new file with mode: 0644]
servers/exchange/lib/e2k-path.c [new file with mode: 0644]
servers/exchange/lib/e2k-path.h [new file with mode: 0644]
servers/exchange/lib/e2k-properties.c [new file with mode: 0644]
servers/exchange/lib/e2k-properties.h [new file with mode: 0644]
servers/exchange/lib/e2k-propnames.c.in [new file with mode: 0644]
servers/exchange/lib/e2k-propnames.h.in [new file with mode: 0644]
servers/exchange/lib/e2k-proptags.h.in [new file with mode: 0644]
servers/exchange/lib/e2k-restriction.c [new file with mode: 0644]
servers/exchange/lib/e2k-restriction.h [new file with mode: 0644]
servers/exchange/lib/e2k-result.c [new file with mode: 0644]
servers/exchange/lib/e2k-result.h [new file with mode: 0644]
servers/exchange/lib/e2k-rule-xml.c [new file with mode: 0644]
servers/exchange/lib/e2k-rule-xml.h [new file with mode: 0644]
servers/exchange/lib/e2k-rule.c [new file with mode: 0644]
servers/exchange/lib/e2k-rule.h [new file with mode: 0644]
servers/exchange/lib/e2k-security-descriptor.c [new file with mode: 0644]
servers/exchange/lib/e2k-security-descriptor.h [new file with mode: 0644]
servers/exchange/lib/e2k-sid.c [new file with mode: 0644]
servers/exchange/lib/e2k-sid.h [new file with mode: 0644]
servers/exchange/lib/e2k-types.h [new file with mode: 0644]
servers/exchange/lib/e2k-uri.c [new file with mode: 0644]
servers/exchange/lib/e2k-uri.h [new file with mode: 0644]
servers/exchange/lib/e2k-user-dialog.c [new file with mode: 0644]
servers/exchange/lib/e2k-user-dialog.h [new file with mode: 0644]
servers/exchange/lib/e2k-utils.c [new file with mode: 0644]
servers/exchange/lib/e2k-utils.h [new file with mode: 0644]
servers/exchange/lib/e2k-validate.h [new file with mode: 0644]
servers/exchange/lib/e2k-xml-utils.c [new file with mode: 0644]
servers/exchange/lib/e2k-xml-utils.h [new file with mode: 0644]
servers/exchange/lib/mapi-properties [new file with mode: 0644]
servers/exchange/lib/mapi.h [new file with mode: 0644]
servers/exchange/storage/Makefile.am [new file with mode: 0644]
servers/exchange/storage/e-folder-exchange.c [new file with mode: 0644]
servers/exchange/storage/e-folder-exchange.h [new file with mode: 0644]
servers/exchange/storage/e-folder-tree.c [new file with mode: 0644]
servers/exchange/storage/e-folder-tree.h [new file with mode: 0644]
servers/exchange/storage/e-folder-type-registry.c [new file with mode: 0644]
servers/exchange/storage/e-folder-type-registry.h [new file with mode: 0644]
servers/exchange/storage/e-folder.c [new file with mode: 0644]
servers/exchange/storage/e-folder.h [new file with mode: 0644]
servers/exchange/storage/e-storage.c [new file with mode: 0644]
servers/exchange/storage/e-storage.h [new file with mode: 0644]
servers/exchange/storage/exchange-account.c [new file with mode: 0644]
servers/exchange/storage/exchange-account.h [new file with mode: 0644]
servers/exchange/storage/exchange-constants.h [new file with mode: 0644]
servers/exchange/storage/exchange-folder-size.c [new file with mode: 0644]
servers/exchange/storage/exchange-folder-size.h [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy-favorites.c [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy-favorites.h [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy-gal.c [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy-gal.h [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy-somedav.c [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy-somedav.h [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy-webdav.c [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy-webdav.h [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy.c [new file with mode: 0644]
servers/exchange/storage/exchange-hierarchy.h [new file with mode: 0644]
servers/exchange/storage/exchange-types.h [new file with mode: 0644]
servers/exchange/storage/libexchange-storage.pc.in [new file with mode: 0644]
servers/exchange/xntlm/ChangeLog [new file with mode: 0644]
servers/exchange/xntlm/Makefile.am [new file with mode: 0644]
servers/exchange/xntlm/xntlm-des.c [new file with mode: 0644]
servers/exchange/xntlm/xntlm-des.h [new file with mode: 0644]
servers/exchange/xntlm/xntlm-md4.c [new file with mode: 0644]
servers/exchange/xntlm/xntlm-md4.h [new file with mode: 0644]
servers/exchange/xntlm/xntlm.c [new file with mode: 0644]
servers/exchange/xntlm/xntlm.h [new file with mode: 0644]

diff --git a/servers/exchange/ChangeLog b/servers/exchange/ChangeLog
new file mode 100644 (file)
index 0000000..eaf597f
--- /dev/null
@@ -0,0 +1,48 @@
+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/servers/exchange/Makefile.am b/servers/exchange/Makefile.am
new file mode 100644 (file)
index 0000000..c004e72
--- /dev/null
@@ -0,0 +1 @@
+SUBDIRS = xntlm lib storage
diff --git a/servers/exchange/lib/Makefile.am b/servers/exchange/lib/Makefile.am
new file mode 100644 (file)
index 0000000..61a7635
--- /dev/null
@@ -0,0 +1,155 @@
+INCLUDES =                                                      \
+       -DCONNECTOR_PREFIX=\""$(prefix)"\"                      \
+       -DCONNECTOR_LOCALEDIR=\""$(localedir)"\"                \
+       -DCONNECTOR_DATADIR=\""$(CONNECTOR_DATADIR)"\"          \
+       -DEVOLUTION_VERSION=\"$(EVOLUTION_VERSION)\"            \
+       $(LDAP_CFLAGS)                                          \
+       $(KRB5_CFLAGS)                                          \
+       $(SOUP_CFLAGS)                                          \
+       $(E_DATA_SERVER_CFLAGS)                                 \
+       $(E_DATA_SERVER_UI_CFLAGS)                                      \
+       -I$(top_srcdir)                                         \
+       -I$(top_srcdir)/servers/exchange/xntlm
+#$(LIBEXCHANGE_CFLAGS)                         
+
+noinst_LTLIBRARIES =                                           \
+       libexchange.la
+
+PROP_GENERATED = e2k-propnames.h e2k-propnames.c e2k-proptags.h
+
+mapi_properties    = 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 HAVE_KRB5
+KERBEROS_FILES = \
+       e2k-kerberos.c  \
+       e2k-kerberos.h
+else
+KERBEROS_FILES =
+endif
+
+
+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-encoding-utils.c                                    \
+       e2k-encoding-utils.h                                    \
+       e2k-freebusy.c                                          \
+       e2k-freebusy.h                                          \
+       e2k-global-catalog.c                                    \
+       e2k-global-catalog.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
+
+#libexchange_la_LDADD =                                        \
+       #$(E_DATA_SERVER_LIBS)                                  \
+       #$(E_DATA_SERVER_UI_LIBS)                               \
+       #$(SOUP_LIBS)
+
+libexchangeincludedir = $(privincludedir)/exchange
+
+libexchangeinclude_HEADERS = \
+       e2k-autoconfig.h                                        \
+       e2k-context.h                                           \
+       e2k-freebusy.h                                          \
+       e2k-global-catalog.h                                    \
+       e2k-http-utils.h                                        \
+       e2k-marshal.h                                           \
+       e2k-operation.h                                         \
+       e2k-properties.h                                        \
+       e2k-propnames.h                                         \
+       e2k-restriction.h                                       \
+       e2k-result.h                                            \
+       e2k-security-descriptor.h                               \
+       e2k-types.h                                             \
+       e2k-uri.h                                               \
+       e2k-utils.h                                             \
+       e2k-xml-utils.h                                         \
+       mapi.h
+
+EXTRA_DIST =                                                   \
+       e2k-marshal.list                                        \
+       $(e2k_propnames_h_in)                                   \
+       $(e2k_propnames_c_in)                                   \
+       $(e2k_proptags_h_in)
+
+dist-hook:
+       cd $(distdir); rm -f $(NODIST_FILES)
diff --git a/servers/exchange/lib/e2k-action.c b/servers/exchange/lib/e2k-action.c
new file mode 100644 (file)
index 0000000..99a5768
--- /dev/null
@@ -0,0 +1,801 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 char *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 (int 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 char *display_name, const char *email_type,
+                    const char *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, int entry_num,
+                        const char *display_name,
+                        const char *exchange_dn,
+                        const char *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, int entry_num,
+                         const char *display_name, const char *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)
+{
+       int 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)
+{
+       int 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, int *len, E2kAction **act_ret)
+{
+       int 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;
+               int 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, int *len, GPtrArray **actions)
+{
+       GPtrArray *acts;
+       E2kAction *act;
+       guint32 actlen;
+       guint16 nacts;
+       int 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)
+{
+       int actlen_offset, actlen;
+       char 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, 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, &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:
+       {
+               int 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)
+{
+       int 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/servers/exchange/lib/e2k-action.h b/servers/exchange/lib/e2k-action.h
new file mode 100644 (file)
index 0000000..6011c7f
--- /dev/null
@@ -0,0 +1,59 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+gboolean     e2k_actions_extract       (guint8             **data,
+                                       int                  *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 char                  *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         (int                          nentries);
+void         e2k_addr_list_set_local   (E2kAddrList                 *list,
+                                       int                   entry_num,
+                                       const char           *display_name,
+                                       const char           *exchange_dn,
+                                       const char           *email);
+void         e2k_addr_list_set_oneoff  (E2kAddrList                 *list,
+                                       int                   entry_num,
+                                       const char           *display_name,
+                                       const char           *email);
+void         e2k_addr_list_free        (E2kAddrList                 *list);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E2K_ACTION_H__ */
diff --git a/servers/exchange/lib/e2k-autoconfig.c b/servers/exchange/lib/e2k-autoconfig.c
new file mode 100644 (file)
index 0000000..02f6697
--- /dev/null
@@ -0,0 +1,1605 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+#include "e2k-autoconfig.h"
+#include "e2k-validate.h"
+#include "e2k-encoding-utils.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 <e-util/e-account.h>
+//#include <e-util/e-account-list.h>
+#include <libedataserver/e-account.h>
+#include <libedataserver/e-account-list.h>
+#include <libedataserverui/e-passwords.h>
+//#include <e-util/e-dialog-utils.h>
+#include <gconf/gconf-client.h>
+#include <libxml/tree.h>
+#include <libxml/HTMLparser.h>
+
+#include <gtk/gtk.h>
+
+static char *find_olson_timezone (const char *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 char *owa_uri, const char *username,
+                   const char *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);
+       e2k_autoconfig_set_gc_server (ac, NULL, -1);
+       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 char *owa_uri)
+{
+       reset_owa_derived (ac);
+       if (ac->gc_server_autodetected)
+               e2k_autoconfig_set_gc_server (ac, NULL, -1);
+       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
+ *
+ * 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 char *gc_server,
+                             int gal_limit)
+{
+       const char *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;
+}
+
+/**
+ * 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 char *username)
+{
+       int 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 char *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;
+       const GSList *headers;
+       const char *challenge_hdr;
+       GByteArray *challenge;
+
+       ac->saw_ntlm = ac->saw_basic = FALSE;
+       headers = soup_message_get_header_list (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)) {
+                       challenge = e2k_base64_decode (challenge_hdr + 5);
+                       if (!ac->nt_domain)
+                               ac->nt_domain_defaulted = TRUE;
+                       xntlm_parse_challenge (challenge->data, challenge->len,
+                                              NULL,
+                                              ac->nt_domain ? NULL : &ac->nt_domain,
+                                              ac->w2k_domain ? NULL : &ac->w2k_domain);
+                       g_byte_array_free (challenge, TRUE);
+                       ac->saw_ntlm = TRUE;
+                       return;
+               }
+
+               headers = headers->next;
+       }
+}
+
+/*
+ * 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 char *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);
+       soup_message_add_header (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, E2K_HTTP_UNAUTHORIZED,
+                                             SOUP_HANDLER_PRE_BODY,
+                                             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. 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 char *location;
+               char *new_uri;
+
+               location = soup_message_get_header (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")) {
+                       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.length > 0) {
+               msg->response.body[msg->response.length - 1] = '\0';
+               if (strstr (msg->response.body, "SSL")) {
+                       char *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_get_header (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 char *server = soup_message_get_header (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, msg->response.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, "http-equiv");
+               content = xmlGetProp (node, "content");
+               if (equiv && content &&
+                   !g_ascii_strcasecmp (equiv, "REFRESH") &&
+                   strstr (content, "/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, "href");
+               ac->home_uri = g_strdup (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 char *home_properties[] = {
+       PR_STORE_ENTRYID,
+       E2K_PR_EXCHANGE_TIMEZONE
+};
+static const int 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;
+       char *new_uri, *pf_uri;
+       E2kContext *ctx;
+       gboolean redirected = FALSE;
+       E2kResultIter *iter;
+       E2kResult *results;
+       GByteArray *entryid;
+       const char *exchange_dn, *timezone, *prop, *hrefs[] = { "" };
+       char *body;
+       int len;
+       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, &body, &len);
+       g_free (pf_uri);
+       if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
+               doc = e2k_parse_html (body, len);
+               g_free (body);
+       } else
+               doc = NULL;
+
+       if (doc) {
+               for (node = e2k_xml_find (doc->children, "img"); node; node = e2k_xml_find (node, "img")) {
+                       prop = xmlGetProp (node, "src");
+                       if (prop && strstr (prop, "public") && node->parent) {
+                               node = node->parent;
+                               prop = xmlGetProp (node, "href");
+                               if (prop) {
+                                       euri = e2k_uri_new (prop);
+                                       ac->pf_server = g_strdup (euri->host);
+                                       e2k_uri_free (euri);
+                               }
+                               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)
+{
+       int count, len;
+       unsigned char 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, namebuf, sizeof (namebuf));
+               p += 4;
+       }
+
+       /* Read answers */
+       while (count-- && p < end) {
+               p += dn_expand (answer, end, p, 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, namebuf, sizeof (namebuf));
+
+               /* FIXME: obey priority and weight */
+               ac->gc_server = g_strdup (namebuf);
+               ac->gc_server_autodetected = TRUE;
+               return;
+       }
+
+       return;
+}
+
+/**
+ * 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);
+}
+
+/*
+ * 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;
+       char *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);
+
+       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 char *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 int n_zone_mappings = sizeof (zonemap) / sizeof (zonemap[0]);
+
+static char *
+find_olson_timezone (const char *windows_timezone)
+{
+       int i, tzlen;
+       const char *locale, *p;
+       char 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). */
+       locale = getenv ("LANG");
+       if (locale) {
+               strncpy (lang, locale, 2);
+               locale = strchr (locale, '_');
+               if (locale++)
+                       strncpy (country, locale, 2);
+       }
+
+       /* 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;
+       char *p, *name, *value;
+       char *config_data;
+       int fd;
+
+       config_options = g_hash_table_new (e2k_ascii_strcase_hash,
+                                           e2k_ascii_strcase_equal);
+
+       fd = open ("/etc/ximian/connector.conf", O_RDONLY);
+       if (fd == -1)
+               fd = open (CONNECTOR_PREFIX "/etc/connector.conf", O_RDONLY);
+       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 ((unsigned char)*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 char *
+e2k_autoconfig_lookup_option (const char *option)
+{
+       if (!config_options)
+               read_config ();
+       return g_hash_table_lookup (config_options, option);
+}
+
+static gboolean 
+validate (const char *owa_url, char *user, char *password, ExchangeParams *exchange_params)
+{
+       E2kAutoconfig *ac;
+       E2kOperation op;        /* FIXME */
+       E2kAutoconfigResult result;
+       E2kUri *euri;
+       gboolean valid = FALSE;
+       const char *old, *new;
+       char *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) {
+               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);
+               mailbox = strrchr (path, '/');
+               if (mailbox && !mailbox[1]) {
+                       *mailbox = '\0';
+                       mailbox = strrchr (path, '/');
+               }
+               if (mailbox)
+                       *mailbox++ = '\0';
+
+               exchange_params->mailbox  = g_strdup (mailbox);
+               exchange_params->owa_path = g_strdup_printf ("%s%s", "/", path);
+               g_free (path);
+               exchange_params->host = ac->pf_server;
+               if (ac->gc_server) 
+                       exchange_params->ad_server = 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;
+               }
+       }
+
+
+       return valid;
+}
+
+gboolean
+e2k_validate_user (const char *owa_url, char *user,
+                  ExchangeParams *exchange_params, gboolean *remember_password)
+{
+       gboolean valid = FALSE, remember=FALSE;
+       char *key, *password, *prompt;
+
+       key = g_strdup_printf ("%s//%s@%s", "exchange:", user, owa_url); /* FIXME */
+       password = e_passwords_get_password ("Exchange", key);
+       if (!password) {
+               prompt = g_strdup_printf (_("Enter password for %s"), user);
+               password = e_passwords_ask_password (_("Enter password"),
+                                       "Exchange", key, prompt,
+                                       E_PASSWORDS_REMEMBER_FOREVER|E_PASSWORDS_SECRET,
+                                       &remember, NULL);
+               if (password) {
+                       valid = validate (owa_url, user, password, exchange_params);
+                       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);
+                               key = g_strdup_printf ("%s//%s@%s", 
+                                                      "exchange:", user, exchange_params->host);
+                               e_passwords_add_password (key, password);
+                               e_passwords_remember_password ("Exchange", key);
+                       }
+               }
+               g_free (prompt);
+       }
+       if (password && !valid) {
+               e_passwords_forget_password ("Exchange", key);
+       }
+       g_free (key);
+
+       return valid;
+}
diff --git a/servers/exchange/lib/e2k-autoconfig.h b/servers/exchange/lib/e2k-autoconfig.h
new file mode 100644 (file)
index 0000000..a2a0376
--- /dev/null
@@ -0,0 +1,103 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+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) */
+       char *owa_uri, *gc_server;
+       char *username, *password;
+       int gal_limit;
+
+       /* Output data */
+       E2kExchangeVersion version;
+       char *display_name, *email;
+       char *account_uri, *exchange_server;
+       char *timezone;
+
+       /* Private-ish members */
+       char *nt_domain, *w2k_domain;
+       char *home_uri, *exchange_dn;
+       char *pf_server;
+       E2kAutoconfigAuthPref auth_pref;
+       gboolean require_ntlm, use_ntlm;
+       gboolean saw_basic, saw_ntlm;
+       gboolean nt_domain_defaulted, gc_server_autodetected;
+} E2kAutoconfig;
+
+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;
+
+E2kAutoconfig       *e2k_autoconfig_new                  (const char *owa_uri,
+                                                         const char *username,
+                                                         const char *password,
+                                                         E2kAutoconfigAuthPref auth_pref);
+void                 e2k_autoconfig_free                 (E2kAutoconfig *ac);
+
+void                 e2k_autoconfig_set_owa_uri          (E2kAutoconfig *ac,
+                                                         const char *owa_uri);
+void                 e2k_autoconfig_set_gc_server        (E2kAutoconfig *ac,
+                                                         const char *gc_server,
+                                                         int gal_limit);
+void                 e2k_autoconfig_set_username         (E2kAutoconfig *ac,
+                                                         const char *username);
+void                 e2k_autoconfig_set_password         (E2kAutoconfig *ac,
+                                                         const char *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 char          *e2k_autoconfig_lookup_option        (const char *option);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_AUTOCONFIG_H__ */
diff --git a/servers/exchange/lib/e2k-context.c b/servers/exchange/lib/e2k-context.c
new file mode 100644 (file)
index 0000000..35a3c0c
--- /dev/null
@@ -0,0 +1,2668 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "e2k-context.h"
+#include "e2k-encoding-utils.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 <libsoup/soup-address.h>
+#include <libsoup/soup-message-filter.h>
+#include <libsoup/soup-session-async.h>
+#include <libsoup/soup-session-sync.h>
+#include <libsoup/soup-socket.h>
+#include <libsoup/soup-uri.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+
+#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;
+       char *owa_uri, *username, *password;
+       time_t last_timestamp;
+
+       /* Notification listener */
+       SoupSocket *get_local_address_sock;
+       GIOChannel *listener_channel;
+       int listener_watch_id;
+
+       char *notification_uri;
+       GHashTable *subscriptions_by_id, *subscriptions_by_uri;
+
+       /* Forms-based authentication */
+       char *cookie;
+       gboolean cookie_verified;
+};
+
+/* For operations with progress */
+#define E2K_CONTEXT_MIN_BATCH_SIZE 25
+#define E2K_CONTEXT_MAX_BATCH_SIZE 100
+
+#ifdef E2K_DEBUG
+char *e2k_debug;
+int e2k_debug_level;
+#endif
+
+static gboolean renew_subscription (gpointer user_data);
+static void unsubscribe_internal (E2kContext *ctx, const char *uri, GList *sub_list);
+static gboolean do_notification (GIOChannel *source, GIOCondition condition, gpointer data);
+
+static void setup_message (SoupMessageFilter *filter, SoupMessage *msg);
+
+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);
+}
+
+static void
+destroy_sub_list (gpointer uri, gpointer sub_list, gpointer ctx)
+{
+       unsubscribe_internal (ctx, uri, sub_list);
+       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);
+               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);
+}
+
+static void
+filter_iface_init (SoupMessageFilterClass *filter_class)
+{
+       /* interface implementation */
+       filter_class->setup_message = setup_message;
+
+#ifdef E2K_DEBUG
+       e2k_debug = getenv ("E2K_DEBUG");
+       if (e2k_debug)
+               e2k_debug_level = atoi (e2k_debug);
+#endif
+}
+
+E2K_MAKE_TYPE_WITH_IFACE (e2k_context, E2kContext, class_init, init, PARENT_TYPE, filter_iface_init, SOUP_TYPE_MESSAGE_FILTER)
+
+
+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 char *local_ipaddr;
+       unsigned short port;
+       int 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 (s == -1)
+               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 (ret == -1 && errno == EADDRINUSE);
+
+       if (ret == -1) {
+               close (s);
+               goto done;
+       }
+
+       ctx->priv->listener_channel = g_io_channel_unix_new (s);
+       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 char *uri)
+{
+       E2kContext *ctx;
+       SoupUri *suri;
+
+       suri = soup_uri_new (uri);
+       if (!suri)
+               return NULL;
+
+       ctx = g_object_new (E2K_TYPE_CONTEXT, NULL);
+       ctx->priv->owa_uri = g_strdup (uri);
+
+       g_object_ref (ctx);
+       ctx->priv->get_local_address_sock =
+               soup_socket_client_new_async (
+                       suri->host, suri->port, FALSE,
+                       got_connection, ctx);
+       soup_uri_free (suri);
+
+       return ctx;
+}
+
+static void
+session_authenticate (SoupSession *session, SoupMessage *msg,
+                     const char *auth_type, const char *auth_realm,
+                     char **username, char **password, gpointer user_data)
+{
+       E2kContext *ctx = user_data;
+
+       *username = g_strdup (ctx->priv->username);
+       *password = g_strdup (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 char *username,
+                     const char *domain, const char *authmech,
+                     const char *password)
+{
+
+       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);
+
+       ctx->priv->session = soup_session_sync_new_with_options (
+               SOUP_SESSION_USE_NTLM, !authmech || !strcmp (authmech, "NTLM"),
+               NULL);
+       g_signal_connect (ctx->priv->session, "authenticate",
+                         G_CALLBACK (session_authenticate), ctx);
+       soup_session_add_filter (ctx->priv->session,
+                                SOUP_MESSAGE_FILTER (ctx));
+
+       ctx->priv->async_session = soup_session_async_new_with_options (
+               SOUP_SESSION_USE_NTLM, !authmech || !strcmp (authmech, "NTLM"),
+               NULL);
+       g_signal_connect (ctx->priv->async_session, "authenticate",
+                         G_CALLBACK (session_authenticate), ctx);
+       soup_session_add_filter (ctx->priv->async_session,
+                                SOUP_MESSAGE_FILTER (ctx));
+}
+
+/**
+ * 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
+ * 2 - 1 plus all headers
+ * 3 - 2 plus all bodies
+ * 4 - 3 plus Global Catalog debug too
+ */
+
+static void
+print_header (gpointer name, gpointer value, gpointer data)
+{
+       printf ("%s: %s\n", (char *)name, (char *)value);
+}
+
+static void
+e2k_debug_print_request (SoupMessage *msg, const char *note)
+{
+       const SoupUri *uri;
+
+       uri = soup_message_get_uri (msg);
+       printf ("%s %s%s%s HTTP/1.1\nE2k-Debug: %p @ %lu",
+               msg->method, uri->path,
+               uri->query ? "?" : "",
+               uri->query ? uri->query : "",
+               msg, (unsigned long)time (0));
+       if (note)
+               printf (" [%s]\n", note);
+       else
+               printf ("\n");
+       if (e2k_debug_level > 1) {
+               print_header ("Host", uri->host, NULL);
+               soup_message_foreach_header (msg->request_headers,
+                                            print_header, NULL);
+       }
+       if (e2k_debug_level > 2 && msg->request.length &&
+           strcmp (msg->method, "POST")) {
+               printf ("\n");
+               fwrite (msg->request.body, 1, msg->request.length, stdout);
+               if (msg->request.body[msg->request.length - 1] != '\n')
+                       printf ("\n");
+       }
+       printf ("\n");
+}
+
+static void
+e2k_debug_print_response (SoupMessage *msg)
+{
+       printf ("%d %s\nE2k-Debug: %p @ %lu\n",
+               msg->status_code, msg->reason_phrase,
+               msg, time (0));
+       if (e2k_debug_level > 1) {
+               soup_message_foreach_header (msg->response_headers,
+                                            print_header, NULL);
+       }
+       if (e2k_debug_level > 2 && msg->response.length &&
+           E2K_HTTP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+               const char *content_type =
+                       soup_message_get_header (msg->response_headers,
+                                                "Content-Type");
+               if (!content_type || e2k_debug_level > 4 ||
+                   g_ascii_strcasecmp (content_type, "text/html")) {
+                       printf ("\n");
+                       fwrite (msg->response.body, 1, msg->response.length, stdout);
+                       if (msg->response.body[msg->response.length - 1] != '\n')
+                               printf ("\n");
+               }
+       }
+       printf ("\n");
+}
+
+static void
+e2k_debug_handler (SoupMessage *msg, gpointer user_data)
+{
+       gboolean restarted = GPOINTER_TO_INT (user_data);
+
+       e2k_debug_print_response (msg);
+       if (restarted)
+               e2k_debug_print_request (msg, "restarted");
+}
+
+static void
+e2k_debug_setup (SoupMessage *msg)
+{
+       if (!e2k_debug_level)
+               return;
+
+       e2k_debug_print_request (msg, NULL);
+
+       g_signal_connect (msg, "finished",
+                         G_CALLBACK (e2k_debug_handler),
+                         GINT_TO_POINTER (FALSE));
+       g_signal_connect (msg, "restarted",
+                         G_CALLBACK (e2k_debug_handler),
+                         GINT_TO_POINTER (TRUE));
+}
+#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;
+       int status, len;
+       char *body;
+       char *action, *method, *name, *value;
+       xmlDoc *doc = NULL;
+       xmlNode *node;
+       SoupMessage *post_msg;
+       GString *form_body, *cookie_str;
+       const 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, &body, &len);
+       if (!SOUP_STATUS_IS_SUCCESSFUL (status) || len == 0)
+               goto failed;
+
+       doc = e2k_parse_html (body, len);
+       g_free (body);
+
+       node = e2k_xml_find (doc->children, "form");
+       if (!node)
+               goto failed;
+
+       method = xmlGetProp (node, "method");
+       if (!method || g_ascii_strcasecmp (method, "post") != 0) {
+               if (method)
+                       xmlFree (method);
+               goto failed;
+       }
+       xmlFree (method);
+
+       value = xmlGetProp (node, "action");
+       if (!value)
+               goto failed;
+       if (*value == '/') {
+               SoupUri *suri;
+
+               suri = soup_uri_new (ctx->priv->owa_uri);
+               g_free (suri->path);
+               suri->path = g_strdup (value);
+               action = soup_uri_to_string (suri, FALSE);
+               soup_uri_free (suri);
+       } else
+               action = g_strdup (value);
+       xmlFree (value);
+
+       form_body = g_string_new (NULL);
+       while ((node = e2k_xml_find (node, "input"))) {
+               name = xmlGetProp (node, "name");
+               if (!name)
+                       continue;
+               value = xmlGetProp (node, "value");
+
+               if (!g_ascii_strcasecmp (name, "destination") && value) {
+                       g_string_append (form_body, name);
+                       g_string_append_c (form_body, '=');
+                       e2k_uri_append_encoded (form_body, value, FALSE, NULL);
+                       g_string_append_c (form_body, '&');
+               } else if (!g_ascii_strcasecmp (name, "flags")) {
+                       g_string_append_printf (form_body, "flags=%d",
+                                               E2K_FBA_FLAG_TRUSTED);
+                       g_string_append_c (form_body, '&');
+               } else if (!g_ascii_strcasecmp (name, "username")) {
+                       g_string_append (form_body, "username=");
+                       e2k_uri_append_encoded (form_body, ctx->priv->username, FALSE, NULL);
+                       g_string_append_c (form_body, '&');
+               } else if (!g_ascii_strcasecmp (name, "password")) {
+                       g_string_append (form_body, "password=");
+                       e2k_uri_append_encoded (form_body, ctx->priv->password, FALSE, NULL);
+                       g_string_append_c (form_body, '&');
+               }
+
+               if (value)
+                       xmlFree (value);
+               xmlFree (name);
+       }
+       g_string_append_printf (form_body, "trusted=%d", E2K_FBA_FLAG_TRUSTED);
+       xmlFreeDoc (doc);
+       doc = NULL;
+
+       post_msg = e2k_soup_message_new_full (ctx, action, "POST",
+                                             "application/x-www-form-urlencoded",
+                                             SOUP_BUFFER_SYSTEM_OWNED,
+                                             form_body->str, form_body->len);
+       soup_message_set_flags (post_msg, SOUP_MESSAGE_NO_REDIRECT);
+       e2k_context_send_message (ctx, NULL /* FIXME? */, post_msg);
+       g_string_free (form_body, FALSE);
+       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 = soup_message_get_header_list (post_msg->response_headers,
+                                               "Set-Cookie");
+       cookie_str = g_string_new (NULL);
+
+       for (c = cookies; c; c = c->next) {
+               value = c->data;
+               len = strcspn (value, ";");
+
+               if (cookie_str->len)
+                       g_string_append (cookie_str, "; ");
+               g_string_append_len (cookie_str, value, len);
+       }
+       ctx->priv->cookie = cookie_str->str;
+       ctx->priv->cookie_verified = FALSE;
+       g_string_free (cookie_str, FALSE);
+       g_object_unref (post_msg);
+
+       in_fba_auth = FALSE;
+
+       /* Set up the failed message to be requeued */
+       soup_message_remove_header (failed_msg->request_headers, "Cookie");
+       soup_message_add_header (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;
+
+#ifdef E2K_DEBUG
+       if (e2k_debug_level)
+               e2k_debug_print_response (msg);
+#endif
+
+       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 char *date;
+
+       date = soup_message_get_header (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 char *new_uri;
+       SoupUri *soup_uri;
+       char *old_uri;
+
+       if (soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT)
+               return;
+
+       new_uri = soup_message_get_header (msg->response_headers, "Location");
+       if (new_uri) {
+               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 (SoupMessageFilter *filter, SoupMessage *msg)
+{
+       E2kContext *ctx = E2K_CONTEXT (filter);
+
+
+       if (ctx->priv->cookie) {
+               soup_message_remove_header (msg->request_headers, "Cookie");
+               soup_message_add_header (msg->request_headers,
+                                        "Cookie", ctx->priv->cookie);
+       }
+
+       /* Only do this the first time through */
+       if (!soup_message_get_header (msg->request_headers, "User-Agent")) {
+               soup_message_add_handler (msg, SOUP_HANDLER_PRE_BODY,
+                                         timestamp_handler, ctx);
+               soup_message_add_status_class_handler (msg, SOUP_STATUS_CLASS_REDIRECT,
+                                                      SOUP_HANDLER_PRE_BODY,
+                                                      redirect_handler, ctx);
+               soup_message_add_status_code_handler (msg, E2K_HTTP_TIMEOUT,
+                                                     SOUP_HANDLER_PRE_BODY,
+                                                     fba_timeout_handler, ctx);
+               soup_message_add_header (msg->request_headers, "User-Agent",
+                                        "Evolution/" EVOLUTION_VERSION);
+
+#ifdef E2K_DEBUG
+               e2k_debug_setup (msg);
+#endif
+       }
+}
+
+/**
+ * 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 char *uri, const char *method)
+{
+       SoupMessage *msg;
+
+       if (method[0] == 'B') {
+               char *slash_uri = e2k_strdup_with_trailing_slash (uri);
+               msg = soup_message_new (method, slash_uri);
+               g_free (slash_uri);
+       } else
+               msg = soup_message_new (method, uri);
+
+       return msg;
+}
+
+/**
+ * e2k_soup_message_new_full:
+ * @ctx: the context
+ * @uri: the URI
+ * @method: the HTTP method
+ * @content_type: MIME Content-Type of @body
+ * @owner: ownership 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 char *uri,
+                          const char *method, const char *content_type,
+                          SoupOwnership owner, const char *body,
+                          gulong length)
+{
+       SoupMessage *msg;
+
+       msg = e2k_soup_message_new (ctx, uri, method);
+       soup_message_set_request (msg, content_type,
+                                 owner, (char *)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,
+                          SoupMessageCallbackFn 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_message_set_status (msg, SOUP_STATUS_CANCELLED);
+       soup_session_cancel_message (ctx->priv->session, msg);
+}
+
+/**
+ * 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 char *folder_uri, const char *encoded_name, int *count,
+                  E2kContextTestCallback test_callback, gpointer user_data)
+{
+       SoupUri *suri;
+       char *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 char *uri, gboolean owa, gboolean claim_ie)
+{
+       SoupMessage *msg;
+
+       msg = e2k_soup_message_new (ctx, uri, "GET");
+       if (!owa)
+               soup_message_add_header (msg->request_headers, "Translate", "F");
+       if (claim_ie) {
+               soup_message_remove_header (msg->request_headers, "User-Agent");
+               soup_message_add_header (msg->request_headers, "User-Agent",
+                                        "MSIE 6.0b (Windows NT 5.0; compatible; "
+                                        "Evolution/" 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.
+ * @body: if not %NULL, will contain the response body on return
+ * @len: if not %NULL, will contain the response body length on return
+ *
+ * Performs a GET on @ctx for @uri. If successful (2xx status code),
+ * the Content-Type, body and length will be returned. The body is not
+ * terminated by a '\0'. If the GET is not successful, @content_type,
+ * @body and @len 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 char *uri,
+                char **content_type, char **body, int *len)
+{
+       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 char *header;
+                       header = soup_message_get_header (msg->response_headers,
+                                                         "Content-Type");
+                       *content_type = g_strdup (header);
+               }
+               if (body) {
+                       *body = msg->response.body;
+                       msg->response.owner = SOUP_BUFFER_USER_OWNED;
+               }
+               if (len)
+                       *len = msg->response.length;
+       }
+
+       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
+ * @body: if not %NULL, will contain the response body on return
+ * @len: if not %NULL, will contain the response body length 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 char *uri, gboolean claim_ie,
+                    char **body, int *len)
+{
+       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 (body) {
+                       *body = msg->response.body;
+                       msg->response.owner = SOUP_BUFFER_USER_OWNED;
+               }
+               if (len)
+                       *len = msg->response.length;
+       }
+
+       g_object_unref (msg);
+       return status;
+}
+
+/* PUT / POST */
+
+static SoupMessage *
+put_msg (E2kContext *ctx, const char *uri, const char *content_type,
+        SoupOwnership buffer_type, const char *body, int length)
+{
+       SoupMessage *msg;
+
+       msg = e2k_soup_message_new_full (ctx, uri, "PUT", content_type,
+                                        buffer_type, body, length);
+       soup_message_add_header (msg->request_headers, "Translate", "f");
+
+       return msg;
+}
+
+static SoupMessage *
+post_msg (E2kContext *ctx, const char *uri, const char *content_type,
+         SoupOwnership buffer_type, const char *body, int 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, char **location, char **repl_uid)
+{
+       const char *header;
+
+       if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (msg->status_code))
+               return;
+
+       if (repl_uid) {
+               header = soup_message_get_header (msg->response_headers,
+                                                 "Repl-UID");
+               *repl_uid = g_strdup (header);
+       }
+       if (location) {
+               header = soup_message_get_header (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 char *uri,
+                const char *content_type, const char *body, int length,
+                char **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_BUFFER_USER_OWNED,
+                      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 char *folder_uri, const char *object_name,
+                    E2kContextTestCallback test_callback, gpointer user_data,
+                    const char *content_type, const char *body, int length,
+                    char **location, char **repl_uid)
+{
+       SoupMessage *msg;
+       E2kHTTPStatus status;
+       char *slash_uri, *encoded_name;
+       int 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_BUFFER_USER_OWNED, body, length);
+       soup_message_add_header (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 char *uri,
+                 const char *content_type, const char *body, int length,
+                 char **location, char **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_BUFFER_USER_OWNED,
+                       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 char *namespace, char 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 char *propertyname,
+           E2kPropType type, gpointer value, gboolean set)
+{
+       const char *namespace, *name, *typestr;
+       char *encoded, abbrev;
+       gboolean b64enc, need_type;
+       GByteArray *data;
+       GPtrArray *array;
+       int i;
+
+       if (value == NULL)
+               return;
+       namespace = e2k_prop_namespace_name (propertyname);
+       abbrev = e2k_prop_namespace_abbrev (propertyname);
+       name = e2k_prop_property_name (propertyname);
+
+       need_type = (strstr (namespace, "/mapi/id/") != NULL);
+
+       g_string_append_printf (xml, "<%c:%s", abbrev, name);
+       if (!set) {
+               g_string_append (xml, "/>");
+               return;
+       } else 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 = e2k_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 = e2k_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 char *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 char *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 char *uri, const char *method,
+          const char **hrefs, int nhrefs, E2kProperties *props,
+          gboolean create)
+{
+       SoupMessage *msg;
+       GString *propxml, *subxml;
+       int 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_BUFFER_SYSTEM_OWNED,
+                                        propxml->str, propxml->len);
+       g_string_free (propxml, FALSE);
+       soup_message_add_header (msg->request_headers, "Brief", "t");
+       if (!create)
+               soup_message_add_header (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 char *uri, E2kProperties *props,
+                      gboolean create, char **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 char *folder_uri, const char *object_name,
+                          E2kContextTestCallback test_callback,
+                          gpointer user_data,
+                          E2kProperties *props,
+                          char **location, char **repl_uid)
+{
+       SoupMessage *msg;
+       E2kHTTPStatus status;
+       char *slash_uri, *encoded_name;
+       int 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_add_header (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, int *nresults,
+                 int *first, int *total,
+                 gpointer user_data)
+{
+       SoupMessage *msg = user_data;
+       E2kHTTPStatus status;
+
+       if (msg->status != SOUP_MESSAGE_STATUS_IDLE)
+               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 char *uri, const char **hrefs, int 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 char *base_uri,
+             const char **props, int nprops, const char **hrefs, int nhrefs)
+{
+       SoupMessage *msg;
+       GString *propxml;
+       GData *set_namespaces;
+       const char *name;
+       char abbrev;
+       int 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_BUFFER_SYSTEM_OWNED,
+                                        propxml->str, propxml->len);
+       g_string_free (propxml, FALSE);
+       soup_message_add_header (msg->request_headers, "Brief", "t");
+       soup_message_add_header (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 char *uri, const char **props, int nprops,
+                     E2kResult **results, int *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, int *nresults,
+                int *first, int *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 char *uri, const char **hrefs, int nhrefs,
+                            const char **props, int nprops)
+{
+       SoupMessage *msg;
+       GSList **msgs;
+       int 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 char *uri,
+           SoupOwnership buffer_type, const char *searchxml,
+           int size, gboolean ascending, int offset)
+{
+       SoupMessage *msg;
+
+       msg = e2k_soup_message_new_full (ctx, uri, "SEARCH", "text/xml",
+                                        buffer_type, searchxml,
+                                        strlen (searchxml));
+       soup_message_add_header (msg->request_headers, "Brief", "t");
+
+       if (size) {
+               char *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_add_header (msg->request_headers, "Range", range);
+               g_free (range);
+       }
+
+       return msg;
+}
+
+static char *
+search_xml (const char **props, int nprops,
+           E2kRestriction *rn, const char *orderby)
+{
+       GString *xml;
+       char *ret, *where;
+       int 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, int *first, int *total)
+{
+       const char *range, *p;
+
+       range = soup_message_get_header (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 {
+       char *uri, *xml;
+       gboolean ascending;
+       int batch_size, next;
+} E2kSearchData;
+
+static E2kHTTPStatus
+search_fetch (E2kResultIter *iter,
+             E2kContext *ctx, E2kOperation *op,
+             E2kResult **results, int *nresults,
+             int *first, int *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_BUFFER_USER_OWNED, 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 char *uri,
+                         const char **props, int nprops, E2kRestriction *rn,
+                         const char *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 char *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 char *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 char *uri, const char **hrefs, int nhrefs)
+{
+       SoupMessage *msg;
+       GString *xml;
+       int 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_BUFFER_SYSTEM_OWNED,
+                                        xml->str, xml->len);
+       g_string_free (xml, FALSE);
+
+       return msg;
+}
+
+static E2kHTTPStatus
+bdelete_fetch (E2kResultIter *iter,
+              E2kContext *ctx, E2kOperation *op,
+              E2kResult **results, int *nresults,
+              int *first, int *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 char *uri, const char **hrefs, int nhrefs)
+{
+       GSList **msgs;
+       int 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 char *uri, E2kProperties *props,
+                  char **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 char *header;
+
+               header = soup_message_get_header (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 char *source_uri, const char *dest_uri,
+             const char **source_hrefs, int nhrefs,
+             gboolean delete_originals)
+{
+       SoupMessage *msg;
+       GString *xml;
+       int 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_BUFFER_SYSTEM_OWNED,
+                                        xml->str, xml->len);
+       soup_message_add_header (msg->request_headers, "Overwrite", "f");
+       soup_message_add_header (msg->request_headers, "Allow-Rename", "t");
+       soup_message_add_header (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, int *nresults,
+              int *first, int *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 char *source_folder, const char *dest_folder,
+                           GPtrArray *source_hrefs, gboolean delete_originals)
+{
+       GSList **msgs;
+       SoupMessage *msg;
+       char *dest_uri;
+       const char **hrefs;
+       int 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);
+       hrefs = (const char **)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 char *source_href, const char *dest_href,
+                         gboolean delete_original,
+                         char **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_add_header (msg->request_headers, "Overwrite", "f");
+       soup_message_add_header (msg->request_headers, "Destination", dest_href);
+
+       status = e2k_context_send_message (ctx, op, msg);
+       if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status) && permanent_url) {
+               const char *header;
+
+               header = soup_message_get_header (msg->response_headers,
+                                                 "MS-Exchange-Permanent-URL");
+               *permanent_url = g_strdup (header);
+       }
+
+       g_object_unref (msg);
+       return status;
+}
+
+
+/* Subscriptions */
+
+typedef struct {
+       E2kContext *ctx;
+       char *uri, *id;
+       E2kContextChangeType type;
+       int 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);
+       int 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 (SoupMessage *msg, gpointer user_data)
+{
+       E2kSubscription *sub = user_data;
+       E2kContext *ctx = sub->ctx;
+       E2kResult *results;
+       int nresults, i;
+       xmlNode *ids;
+       char *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 (strcmp (ids->name, "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_add_header (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;
+       char buffer[1024], *id, *lasts;
+       gsize len;
+       GIOError err;
+
+       err = g_io_channel_read_chars (source, buffer, sizeof (buffer) - 1, &len, NULL);
+       if (err != G_IO_ERROR_NONE && err != G_IO_ERROR_AGAIN) {
+               g_warning ("do_notification I/O error: %d (%s)", err,
+                          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 (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_get_header (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 char *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;
+       char 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_add_header (sub->renew_msg->request_headers,
+                                "Subscription-lifetime", ltbuf);
+       soup_message_add_header (sub->renew_msg->request_headers,
+                                "Notification-type",
+                                subscription_type[sub->type]);
+       if (sub->min_interval > 1) {
+               sprintf (ltbuf, "%d", sub->min_interval);
+               soup_message_add_header (sub->renew_msg->request_headers,
+                                        "Notification-delay", ltbuf);
+       }
+       soup_message_add_header (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 char *uri,
+                      E2kContextChangeType type, int 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);
+       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);
+       g_free (sub->uri);
+       g_free (sub->id);
+       g_free (sub);
+}
+
+static void
+unsubscribed (SoupMessage *msg, gpointer user_data)
+{
+       ;
+}
+
+static void
+unsubscribe_internal (E2kContext *ctx, const char *uri, GList *sub_list)
+{
+       GList *l;
+       E2kSubscription *sub;
+       SoupMessage *msg;
+       GString *subscription_ids = NULL;
+
+       for (l = sub_list; l; l = l->next) {
+               sub = l->data;
+               if (sub->id) {
+                       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");
+               soup_message_add_header (msg->request_headers,
+                                        "Subscription-id",
+                                        subscription_ids->str);
+               e2k_context_queue_message (ctx, msg, unsubscribed, NULL);
+               g_string_free (subscription_ids, TRUE);
+       }
+}
+
+/**
+ * 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 char *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);
+       g_list_free (sub_list);
+}
diff --git a/servers/exchange/lib/e2k-context.h b/servers/exchange/lib/e2k-context.h
new file mode 100644 (file)
index 0000000..0da58d2
--- /dev/null
@@ -0,0 +1,218 @@
+/* -*- 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 <sys/time.h>
+
+#include <glib-object.h>
+
+#include "e2k-types.h"
+#include "e2k-operation.h"
+#include "e2k-http-utils.h"
+#include "e2k-result.h"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char *old_uri, const char *new_uri);
+};
+
+GType          e2k_context_get_type         (void);
+
+E2kContext    *e2k_context_new              (const char  *uri);
+void           e2k_context_set_auth         (E2kContext  *ctx,
+                                            const char  *username,
+                                            const char  *domain,
+                                            const char  *authmech,
+                                            const char  *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 char *test_name,
+                                               gpointer user_data);
+
+E2kHTTPStatus  e2k_context_get               (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             char **content_type,
+                                             char **body, int *len);
+E2kHTTPStatus  e2k_context_get_owa           (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             gboolean claim_ie,
+                                             char **body, int *len);
+
+E2kHTTPStatus  e2k_context_put               (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             const char *content_type,
+                                             const char *body, int length,
+                                             char **repl_uid);
+E2kHTTPStatus  e2k_context_put_new           (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *folder_uri,
+                                             const char *object_name,
+                                             E2kContextTestCallback test_callback,
+                                             gpointer user_data,
+                                             const char *content_type,
+                                             const char *body, int length,
+                                             char **location,
+                                             char **repl_uid);
+E2kHTTPStatus  e2k_context_post              (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             const char *content_type,
+                                             const char *body, int length,
+                                             char **location,
+                                             char **repl_uid);
+
+E2kHTTPStatus  e2k_context_proppatch         (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             E2kProperties *props,
+                                             gboolean create,
+                                             char **repl_uid);
+E2kHTTPStatus  e2k_context_proppatch_new     (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *folder_uri,
+                                             const char *object_name,
+                                             E2kContextTestCallback test_callback,
+                                             gpointer user_data,
+                                             E2kProperties *props,
+                                             char **location,
+                                             char **repl_uid);
+E2kResultIter *e2k_context_bproppatch_start  (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             const char **hrefs,
+                                             int nhrefs,
+                                             E2kProperties *props,
+                                             gboolean create);
+
+E2kHTTPStatus  e2k_context_propfind          (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             const char **props,
+                                             int nprops,
+                                             E2kResult **results,
+                                             int *nresults);
+E2kResultIter *e2k_context_bpropfind_start   (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             const char **hrefs,
+                                             int nhrefs,
+                                             const char **props,
+                                             int nprops);
+
+E2kResultIter *e2k_context_search_start      (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             const char **props,
+                                             int nprops,
+                                             E2kRestriction *rn,
+                                             const char *orderby,
+                                             gboolean ascending);
+
+E2kHTTPStatus  e2k_context_delete            (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri);
+
+E2kResultIter *e2k_context_bdelete_start     (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             const char **hrefs,
+                                             int nhrefs);
+
+E2kHTTPStatus  e2k_context_mkcol             (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *uri,
+                                             E2kProperties *props,
+                                             char **permanent_url);
+
+E2kResultIter *e2k_context_transfer_start    (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *source_folder,
+                                             const char *dest_folder,
+                                             GPtrArray *source_hrefs,
+                                             gboolean delete_originals);
+E2kHTTPStatus  e2k_context_transfer_dir      (E2kContext *ctx,
+                                             E2kOperation *op,
+                                             const char *source_href,
+                                             const char *dest_href,
+                                             gboolean delete_original,
+                                             char **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 char *uri,
+                                             E2kContextChangeType type,
+                                             gpointer user_data);
+
+void          e2k_context_subscribe          (E2kContext *ctx,
+                                             const char *uri,
+                                             E2kContextChangeType type,
+                                             int min_interval,
+                                             E2kContextChangeCallback callback,
+                                             gpointer user_data);
+void          e2k_context_unsubscribe        (E2kContext *ctx,
+                                             const char *uri);
+
+
+/*
+ * Utility functions
+ */
+SoupMessage   *e2k_soup_message_new      (E2kContext *ctx,
+                                         const char *uri,
+                                         const char *method);
+SoupMessage   *e2k_soup_message_new_full (E2kContext *ctx,
+                                         const char *uri,
+                                         const char *method,
+                                         const char *content_type,
+                                         SoupOwnership owner,
+                                         const char *body,
+                                         gulong length);
+void           e2k_context_queue_message (E2kContext *ctx,
+                                         SoupMessage *msg, 
+                                         SoupMessageCallbackFn callback, 
+                                         gpointer user_data);
+E2kHTTPStatus  e2k_context_send_message  (E2kContext *ctx,
+                                         E2kOperation *op,
+                                         SoupMessage *msg);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E2K_CONTEXT_H__ */
diff --git a/servers/exchange/lib/e2k-encoding-utils.c b/servers/exchange/lib/e2k-encoding-utils.c
new file mode 100644 (file)
index 0000000..3dbba46
--- /dev/null
@@ -0,0 +1,152 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-encoding-utils.h"
+
+#include <string.h>
+
+static const char *b64_alphabet =
+       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/**
+ * e2k_base64_encode:
+ * @data: binary data
+ * @len: the length of @data
+ *
+ * Base64-encodes @len bytes of data at @data.
+ *
+ * Return value: the base64-encoded representation of @data, which
+ * the caller must free.
+ **/
+char *
+e2k_base64_encode (const guint8 *data, int len)
+{
+       char *buf, *p;
+
+       p = buf = g_malloc (((len + 2) / 3) * 4 + 1);
+       while (len >= 3) {
+               p[0] = b64_alphabet[ (data[0] >> 2) & 0x3f];
+               p[1] = b64_alphabet[((data[0] << 4) & 0x30) +
+                                   ((data[1] >> 4) & 0x0f)];
+               p[2] = b64_alphabet[((data[1] << 2) & 0x3c) +
+                                   ((data[2] >> 6) & 0x03)];
+               p[3] = b64_alphabet[  data[2]       & 0x3f];
+
+               data += 3;
+               p += 4;
+               len -= 3;
+       }
+
+       switch (len) {
+       case 2:
+               p[0] = b64_alphabet[ (data[0] >> 2) & 0x3f];
+               p[1] = b64_alphabet[((data[0] << 4) & 0x30) +
+                                   ((data[1] >> 4) & 0xf)];
+               p[2] = b64_alphabet[ (data[1] << 2) & 0x3c];
+               p[3] = '=';
+               p += 4;
+               break;
+       case 1:
+               p[0] = b64_alphabet[ (data[0] >> 2) & 0x3f];
+               p[1] = b64_alphabet[ (data[0] << 4) & 0x30];
+               p[2] = '=';
+               p[3] = '=';
+               p += 4;
+               break;
+       }
+
+       *p = '\0';
+       return buf;
+}
+
+static guint8 base64_unphabet[] = {
+       -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1,
+       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+       -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+       52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+       -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+       15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+       -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+       41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+};
+#define E2K_B64_SPACE ((guint8)-2)
+#define E2K_B64_BAD   ((guint8)-1)
+
+/**
+ * e2k_base64_decode:
+ * @string: base64-encoded data
+ *
+ * Decodes the base64-encoded data in @string.
+ *
+ * Return value: the binary data encoded in @string
+ **/
+GByteArray *
+e2k_base64_decode (const char *string)
+{
+       GByteArray *rc;
+       int bits, length, qw = 0;
+       guint8 *data;
+
+       rc = g_byte_array_new ();
+
+       length = strlen (string);
+       if (length == 0)
+               return rc;
+
+       g_byte_array_set_size (rc, ((length / 4) + 1) * 3);
+
+       data = rc->data;
+       for (; *string; string++) {
+               if ((unsigned char)*string > 127)
+                       break;
+               bits = base64_unphabet[(unsigned char)*string];
+               if (bits == E2K_B64_BAD)
+                       break;
+               else if (bits == E2K_B64_SPACE)
+                       continue;
+
+               switch (qw++) {
+               case 0:
+                       data[0]  = (bits << 2) & 0xfc;
+                       break;
+               case 1:
+                       data[0] |= (bits >> 4) & 0x03;
+                       data[1]  = (bits << 4) & 0xf0;
+                       break;
+               case 2:
+                       data[1] |= (bits >> 2) & 0x0f;
+                       data[2]  = (bits << 6) & 0xc0;
+                       break;
+               case 3:
+                       data[2] |=  bits       & 0x3f;
+                       data += 3;
+                       qw = 0;
+                       break;
+               }
+       }
+
+       rc->len = data - rc->data;
+       if (qw > 1)
+               rc->len += qw - 1;
+       return rc;
+}
diff --git a/servers/exchange/lib/e2k-encoding-utils.h b/servers/exchange/lib/e2k-encoding-utils.h
new file mode 100644 (file)
index 0000000..78c44f8
--- /dev/null
@@ -0,0 +1,12 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef E2K_ENCODING_UTILS_H
+#define E2K_ENCODING_UTILS_H
+
+#include <glib.h>
+
+char       *e2k_base64_encode (const guint8 *data, int len); 
+GByteArray *e2k_base64_decode (const char *string);
+
+#endif /* E2K_ENCODING_UTILS_H */
diff --git a/servers/exchange/lib/e2k-freebusy.c b/servers/exchange/lib/e2k-freebusy.c
new file mode 100644 (file)
index 0000000..b22c9bf
--- /dev/null
@@ -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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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)
+{
+       int 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 char *
+fb_uri_for_dn (const char *public_uri, const char *dn)
+{
+       char *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;
+       int 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;
+       int i, monthyear;
+       GByteArray *fbdata;
+       unsigned char *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 char *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 int 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 char *public_uri, const char *dn)
+{
+       E2kFreebusy *fb;
+       char *uri, *time;
+       GPtrArray *monthyears, *fbdatas;
+       E2kHTTPStatus status;
+       E2kResult *results;
+       int nresults, 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, int nmonths)
+{
+       time_t now;
+       struct tm tm;
+       int 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;
+       int 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;
+       int 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 char *freebusy_props[] = {
+       E2K_PR_CALENDAR_DTSTART,
+       E2K_PR_CALENDAR_DTEND,
+       E2K_PR_CALENDAR_BUSY_STATUS
+};
+static const int 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 char *uri,
+                                   time_t start_tt, time_t end_tt)
+{
+       char *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 char *month_list_prop, const char *data_list_prop)
+{
+       E2kFreebusyEvent *events = (E2kFreebusyEvent *)events_array->data;
+       int i, evt_start, evt_end, monthyear;
+       struct tm start_tm, end_tm;
+       time_t start, end;
+       GPtrArray *monthyears, *datas;
+       GByteArray *data;
+       char 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, 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;
+       char *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/servers/exchange/lib/e2k-freebusy.h b/servers/exchange/lib/e2k-freebusy.h
new file mode 100644 (file)
index 0000000..70d9503
--- /dev/null
@@ -0,0 +1,69 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+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;
+       char *dn, *uri;
+
+       time_t start, end;
+
+       GArray *events[E2K_BUSYSTATUS_MAX];
+} E2kFreebusy;
+
+E2kFreebusy   *e2k_freebusy_new                   (E2kContext      *ctx,
+                                                  const char      *public_uri,
+                                                  const char      *dn);
+
+void           e2k_freebusy_reset                 (E2kFreebusy     *fb,
+                                                  int              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 char      *uri,
+                                                  time_t           start_tt,
+                                                  time_t           end_tt);
+
+E2kHTTPStatus  e2k_freebusy_save                  (E2kFreebusy     *fb);
+
+void           e2k_freebusy_destroy               (E2kFreebusy     *fb);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E2K_FREEBUSY_H__ */
diff --git a/servers/exchange/lib/e2k-global-catalog.c b/servers/exchange/lib/e2k-global-catalog.c
new file mode 100644 (file)
index 0000000..868eaf9
--- /dev/null
@@ -0,0 +1,1214 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-global-catalog.h"
+#include "e2k-sid.h"
+#include "e2k-utils.h"
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <ldap.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;
+
+       char *server, *user, *nt_domain, *password;
+};
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+static void finalize (GObject *);
+static int get_gc_connection (E2kGlobalCatalog *gc, E2kOperation *op);
+
+
+static void
+class_init (GObjectClass *object_class)
+{
+#ifdef E2K_DEBUG
+       char *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...
+        */
+       putenv("SASL_PATH=");
+
+       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)
+{
+       int 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);
+       int 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);
+
+               if (gc->priv->server)
+                       g_free (gc->priv->server);
+               if (gc->priv->user)
+                       g_free (gc->priv->user);
+               if (gc->priv->nt_domain)
+                       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;
+       }
+
+       if (gc->domain) {
+               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 int
+gc_ldap_result (LDAP *ldap, E2kOperation *op,
+               int msgid, LDAPMessage **msg)
+{
+       struct timeval tv;
+       int 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 int
+gc_search (E2kGlobalCatalog *gc, E2kOperation *op,
+          const char *base, int scope, const char *filter,
+          const char **attrs, LDAPMessage **msg)
+{
+       int 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, (char **)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 int
+ntlm_bind (E2kGlobalCatalog *gc, E2kOperation *op, LDAP *ldap)
+{
+       LDAPMessage *msg;
+       int ldap_error, msgid, err;
+       char *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 = 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 = 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 int
+ldap_connect (E2kGlobalCatalog *gc, E2kOperation *op, LDAP *ldap)
+{
+       int ldap_error;
+#ifndef HAVE_LDAP_NTLM_BIND
+       char *nt_name;
+#endif
+
+       /* authenticate */
+#ifdef HAVE_LDAP_NTLM_BIND
+       ldap_error = ntlm_bind (gc, op, ldap);
+#else
+       nt_name = gc->priv->nt_domain ?
+               g_strdup_printf ("%s\\%s", gc->priv->nt_domain, gc->priv->user) :
+               g_strdup (gc->priv->user);
+       ldap_error = ldap_simple_bind_s (ldap, nt_name, gc->priv->password);
+       g_free (nt_name);
+#endif
+       if (ldap_error != LDAP_SUCCESS)
+               g_warning ("LDAP authentication failed (0x%02x)", ldap_error);
+       else
+               E2K_GC_DEBUG_MSG(("GC: connected\n\n"));
+
+       return ldap_error;
+}
+
+static int
+get_ldap_connection (E2kGlobalCatalog *gc, E2kOperation *op,
+                    const char *server, int port,
+                    LDAP **ldap)
+{
+       int 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 = ldap_connect (gc, op, *ldap);
+       if (ldap_error != LDAP_SUCCESS) {
+               ldap_unbind (*ldap);
+               *ldap = NULL;
+       }
+       return ldap_error;
+}
+
+static int
+get_gc_connection (E2kGlobalCatalog *gc, E2kOperation *op)
+{
+       int 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 ldap_connect (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
+ *
+ * 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)
+{
+       LDAP *ldap;
+
+       g_return_val_if_fail (E2K_IS_GLOBAL_CATALOG (gc), NULL);
+
+       get_ldap_connection (gc, op, gc->priv->server, 3268, &ldap);
+       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 char *server, int response_limit,
+                       const char *user, const char *domain,
+                       const char *password)
+{
+       E2kGlobalCatalog *gc;
+
+       gc = g_object_new (E2K_TYPE_GLOBAL_CATALOG, NULL);
+       gc->priv->server = g_strdup (server);
+       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 char *
+lookup_mta (E2kGlobalCatalog *gc, E2kOperation *op, const char *mta_dn)
+{
+       char *hostname, **values;
+       const char *attrs[2];
+       LDAPMessage *resp;
+       int 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)
+{
+       char **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, 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)
+{
+       char **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 = (char *)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)
+{
+       char **values;
+       int 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)
+{
+       char **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)
+{
+       char **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 char *key,
+                          E2kGlobalCatalogLookupFlags flags,
+                          E2kGlobalCatalogEntry **entry_p)
+{
+       E2kGlobalCatalogEntry *entry;
+       GPtrArray *attrs;
+       E2kGlobalCatalogLookupFlags lookup_flags, need_flags = 0;
+       const char *base = NULL;
+       char *filter = NULL, *dn;
+       int 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, "displayName");
+       if (!entry->email) {
+               g_ptr_array_add (attrs, "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, "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, "objectSid");
+               g_ptr_array_add (attrs, "objectCategory");
+               need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_SID;
+       }
+       if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX) {
+               g_ptr_array_add (attrs, "mailNickname");
+               g_ptr_array_add (attrs, "homeMTA");
+               need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX;
+       }
+       if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_DELEGATES)
+               g_ptr_array_add (attrs, "publicDelegates");
+       if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_DELEGATORS)
+               g_ptr_array_add (attrs, "publicDelegatesBL");
+       if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_QUOTA) {
+               g_ptr_array_add (attrs, "mDBUseDefaults");
+               g_ptr_array_add (attrs, "mDBStorageQuota");
+               g_ptr_array_add (attrs, "mDBOverQuotaLimit");
+               g_ptr_array_add (attrs, "mDBOverHardQuotaLimit");
+       }
+       if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL)
+               g_ptr_array_add (attrs, "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 char **)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;
+       char *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 void *
+do_lookup_thread (void *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 char *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 char *
+lookup_controlling_ad_server (E2kGlobalCatalog *gc, E2kOperation *op,
+                             const char *dn)
+{
+       char *hostname, **values, *ad_dn;
+       const char *attrs[2];
+       LDAPMessage *resp;
+       int 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 (char *domain)
+{
+       GString *dn_value = g_string_new (NULL);
+       gchar *dn;
+       char  *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, ".");
+       }
+       dn = g_strndup (dn_value->str, strlen(dn_value->str) - 1);
+       g_string_free (dn_value, TRUE);
+       return dn;
+}
+
+double 
+lookup_passwd_max_age (E2kGlobalCatalog *gc, E2kOperation *op)
+{
+       char **values = NULL, *filter = NULL, *val=NULL;
+       const char *attrs[2];
+       LDAP *ldap;
+       LDAPMessage *msg=NULL;
+       int ldap_error, msgid;
+       double 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, (char **)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, int deleg_op,
+               const char *self_dn, const char *delegate_dn)
+{
+       LDAP *ldap;
+       LDAPMod *mods[2], mod;
+       const char *ad_server;
+       char *values[2];
+       int 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 = "publicDelegates";
+       mod.mod_values = values;
+       values[0] = (char *)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 char *self_dn,
+                                const char *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 char *self_dn,
+                                   const char *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/servers/exchange/lib/e2k-global-catalog.h b/servers/exchange/lib/e2k-global-catalog.h
new file mode 100644 (file)
index 0000000..8bf1fc0
--- /dev/null
@@ -0,0 +1,126 @@
+/* -*- 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 <ldap.h>
+#include "e2k-types.h"
+#include "e2k-operation.h"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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;
+
+       char *domain;
+       int response_limit;
+
+       E2kGlobalCatalogPrivate *priv;
+};
+
+struct _E2kGlobalCatalogClass {
+       GObjectClass parent_class;
+
+};
+
+GType             e2k_global_catalog_get_type        (void);
+E2kGlobalCatalog *e2k_global_catalog_new             (const char *server,
+                                                     int response_limit,
+                                                     const char *user,
+                                                     const char *domain,
+                                                     const char *password);
+
+LDAP             *e2k_global_catalog_get_ldap        (E2kGlobalCatalog *gc,
+                                                     E2kOperation     *op);
+
+
+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 {
+       char *dn, *display_name;
+       E2kSid *sid;
+       char *email, *exchange_server, *mailbox, *legacy_exchange_dn;
+       GPtrArray *delegates, *delegators;
+       int quota_warn, quota_nosend, quota_norecv; 
+       int user_account_control;
+
+       E2kGlobalCatalogLookupFlags mask;
+} E2kGlobalCatalogEntry;
+
+E2kGlobalCatalogStatus e2k_global_catalog_lookup (E2kGlobalCatalog *gc,
+                                                 E2kOperation     *op,
+                                                 E2kGlobalCatalogLookupType type,
+                                                 const char *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 char *key,
+                                                 E2kGlobalCatalogLookupFlags flags,
+                                                 E2kGlobalCatalogCallback callback,
+                                                 gpointer user_data);
+
+double         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 char *self_dn,
+                                                          const char *delegate_dn);
+E2kGlobalCatalogStatus e2k_global_catalog_remove_delegate (E2kGlobalCatalog *gc,
+                                                          E2kOperation     *op,
+                                                          const char *self_dn,
+                                                          const char *delegate_dn);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E2K_GLOBAL_CATALOG_H__ */
diff --git a/servers/exchange/lib/e2k-http-utils.c b/servers/exchange/lib/e2k-http-utils.c
new file mode 100644 (file)
index 0000000..83d74ff
--- /dev/null
@@ -0,0 +1,155 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 char *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 char *date)
+{
+       struct tm tm;
+       char *p;
+
+       if (strlen (date) < 29 || date[3] != ',' || date[4] != ' ')
+               return -1;
+
+       memset (&tm, 0, sizeof (tm));
+       p = (char *)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 char *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 char *
+e2k_http_accept_language (void)
+{
+       static char *accept = NULL;
+
+       if (!accept) {
+               GString *buf;
+               const char *lang, *sub;
+               int 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;
+}
+
diff --git a/servers/exchange/lib/e2k-http-utils.h b/servers/exchange/lib/e2k-http-utils.h
new file mode 100644 (file)
index 0000000..e03b5c3
--- /dev/null
@@ -0,0 +1,60 @@
+/* -*- 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>
+
+typedef unsigned int E2kHTTPStatus;
+
+time_t         e2k_http_parse_date      (const char *date);
+E2kHTTPStatus  e2k_http_parse_status    (const char *status_line);
+
+const char    *e2k_http_accept_language (void);
+
+#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
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E2K_HTTP_UTILS_H__ */
diff --git a/servers/exchange/lib/e2k-kerberos.c b/servers/exchange/lib/e2k-kerberos.c
new file mode 100644 (file)
index 0000000..396204b
--- /dev/null
@@ -0,0 +1,182 @@
+/* -*- 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 char *domain)
+{
+       krb5_context ctx;
+       char *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 (int 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 char *usr_name, const char *passwd,
+              const char *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, passwd,
+                                              NULL, NULL, 0,
+                                              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 char *user, const char *domain,
+                             const char *old_password, const char *new_password)
+{
+       krb5_context ctx;
+       krb5_creds creds;
+       krb5_data res_code_string, res_string;
+       E2kKerberosResult result;
+       int 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, (char *)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 char *user, const char *domain,
+                            const char *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/servers/exchange/lib/e2k-kerberos.h b/servers/exchange/lib/e2k-kerberos.h
new file mode 100644 (file)
index 0000000..51e5c56
--- /dev/null
@@ -0,0 +1,38 @@
+/* -*- 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__
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+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 char *user,
+                                               const char *domain,
+                                               const char *password);
+
+E2kKerberosResult e2k_kerberos_change_password (const char *user,
+                                               const char *domain,
+                                               const char *old_password,
+                                               const char *new_password);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E2K_FREEBUSY_H__ */
diff --git a/servers/exchange/lib/e2k-operation.c b/servers/exchange/lib/e2k-operation.c
new file mode 100644 (file)
index 0000000..4a9ea3f
--- /dev/null
@@ -0,0 +1,173 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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/servers/exchange/lib/e2k-operation.h b/servers/exchange/lib/e2k-operation.h
new file mode 100644 (file)
index 0000000..ce2b92d
--- /dev/null
@@ -0,0 +1,44 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+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);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E2k_OPERATION_H__ */
diff --git a/servers/exchange/lib/e2k-path.c b/servers/exchange/lib/e2k-path.c
new file mode 100644 (file)
index 0000000..d394aca
--- /dev/null
@@ -0,0 +1,255 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-path.c
+ *
+ * Copyright (C) 2001 Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <glib.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
+ **/
+char *
+e_path_to_physical (const char *prefix, const char *vpath)
+{
+       const char *p, *newp;
+       char *dp;
+       char *ppath;
+       int ppath_len;
+       int 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 char *physical_path, const char *path,
+                       EPathFindFoldersCallback callback, gpointer data)
+{
+       DIR *dir;
+       char *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 = opendir (subfolder_directory_path);
+       if (dir == NULL) {
+               g_free (subfolder_directory_path);
+               return TRUE;
+       }
+
+       ok = TRUE;
+       while (ok) {
+               struct stat file_stat;
+               struct dirent *dirent;
+               char *file_path;
+               char *new_path;
+
+               dirent = readdir (dir);
+               if (dirent == NULL)
+                       break;
+
+               if (strcmp (dirent->d_name, ".") == 0 || strcmp (dirent->d_name, "..") == 0)
+                       continue;
+
+               file_path = g_strdup_printf ("%s/%s", subfolder_directory_path,
+                                            dirent->d_name);
+
+               if (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->d_name);
+
+               ok = find_folders_recursive (file_path, new_path, callback, data);
+
+               g_free (file_path);
+               g_free (new_path);
+       }
+
+       closedir (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 char *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.
+ **/
+int
+e_path_rmdir (const char *prefix, const char *vpath)
+{
+       char *physical_path, *p;
+
+       /* Remove the directory itself */
+       physical_path = e_path_to_physical (prefix, vpath);
+       if (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;
+       }
+
+       rmdir (physical_path);
+       g_free (physical_path);
+       return 0;
+}
diff --git a/servers/exchange/lib/e2k-path.h b/servers/exchange/lib/e2k-path.h
new file mode 100644 (file)
index 0000000..58c1287
--- /dev/null
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* 
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * This library 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 library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 char *physical_path,
+                                             const char *path,
+                                             gpointer user_data);
+
+char *   e_path_to_physical  (const char *prefix, const char *vpath);
+
+gboolean e_path_find_folders (const char *prefix,
+                             EPathFindFoldersCallback callback,
+                             gpointer data);
+
+int      e_path_rmdir        (const char *prefix, const char *vpath);
+#endif /* __E_PATH__ */
diff --git a/servers/exchange/lib/e2k-properties.c b/servers/exchange/lib/e2k-properties.c
new file mode 100644 (file)
index 0000000..9875f21
--- /dev/null
@@ -0,0 +1,778 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 {
+       char *name;
+       const char *namespace;
+       const char *short_name;
+
+       E2kPropType type;
+       guint32 proptag;
+} E2kPropInfo;
+
+static GHashTable *known_properties;
+
+/**
+ * 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 char *name = key;
+       GHashTable *props_copy = data;
+       gpointer value_copy;
+       E2kPropInfo *pi;
+
+       pi = g_hash_table_lookup (known_properties, name);
+       switch (pi->type) {
+       case E2K_PROP_TYPE_BINARY_ARRAY:
+       {
+               GPtrArray *orig = value, *copy;
+               GByteArray *new, *old;
+               int 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;
+               int 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;
+               int 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;
+               int 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;
+
+       pi = g_hash_table_lookup (known_properties, key);
+       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 char *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 char e2k_des_key[8];
+
+static E2kPropInfo *
+get_propinfo (const char *propname, E2kPropType type)
+{
+       E2kPropInfo *pi;
+
+       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;
+               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);
+
+       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 char    *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, char *, 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, char *, 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 char    *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, char *)
+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 char *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;
+
+       pi = g_hash_table_lookup (known_properties, key);
+       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 char *name;
+
+       pi = g_hash_table_lookup (known_properties, key);
+       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,
+                                    (char *)name, (char *)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 int next_namespace = 'a';
+
+
+static const char *
+get_div (const char *propname)
+{
+       const char *div;
+
+       div = strrchr (propname, '/');
+       if (div)
+               return div;
+       return strrchr (propname, ':');
+}
+
+static gint
+prop_equal (gconstpointer v1, gconstpointer v2)
+{
+       const char *s1 = (const char *)v1, *s2 = (const char *)v2;
+       const char *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 char *d = get_div (v);
+       const char *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, "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 char *
+e2k_prop_namespace_name (const char *prop)
+{
+       const char *div = get_div (prop);
+       gpointer key, value;
+       char *name;
+
+       if (!namespaces)
+               setup_namespaces ();
+
+       if (g_hash_table_lookup_extended (namespaces, prop, &key, &value))
+               return key;
+
+       name = g_strndup (prop, div - prop + 1);
+       g_hash_table_insert (namespaces, name, GINT_TO_POINTER (next_namespace));
+       next_namespace++;
+       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
+ **/
+char
+e2k_prop_namespace_abbrev (const char *prop)
+{
+       const char *div = get_div (prop);
+       gpointer key, value;
+       char *name;
+
+       if (!namespaces)
+               setup_namespaces ();
+
+       if (g_hash_table_lookup_extended (namespaces, prop, &key, &value))
+               return GPOINTER_TO_INT (value);
+
+       name = g_strndup (prop, div - prop + 1);
+       g_hash_table_insert (namespaces, name, GINT_TO_POINTER (next_namespace));
+       return next_namespace++;
+}
+
+/**
+ * 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 char *
+e2k_prop_property_name (const char *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 char *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 char *
+e2k_proptag_prop (guint32 proptag)
+{
+       E2kPropInfo *pi;
+       char *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/servers/exchange/lib/e2k-properties.h b/servers/exchange/lib/e2k-properties.h
new file mode 100644 (file)
index 0000000..fdfc7df
--- /dev/null
@@ -0,0 +1,123 @@
+/* -*- 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 char    *propname);
+gboolean       e2k_properties_empty             (E2kProperties *props);
+
+void           e2k_properties_set_string        (E2kProperties *props,
+                                                const char    *propname,
+                                                char          *value);
+void           e2k_properties_set_string_array  (E2kProperties *props,
+                                                const char    *propname,
+                                                GPtrArray     *value);
+void           e2k_properties_set_binary        (E2kProperties *props,
+                                                const char    *propname,
+                                                GByteArray    *value);
+void           e2k_properties_set_binary_array  (E2kProperties *props,
+                                                const char    *propname,
+                                                GPtrArray     *value);
+void           e2k_properties_set_int           (E2kProperties *props,
+                                                const char    *propname,
+                                                int            value);
+void           e2k_properties_set_int_array     (E2kProperties *props,
+                                                const char    *propname,
+                                                GPtrArray     *value);
+void           e2k_properties_set_xml           (E2kProperties *props,
+                                                const char    *propname,
+                                                xmlNode       *value);
+void           e2k_properties_set_bool          (E2kProperties *props,
+                                                const char    *propname,
+                                                gboolean       value);
+void           e2k_properties_set_float         (E2kProperties *props,
+                                                const char    *propname,
+                                                float          value);
+void           e2k_properties_set_date          (E2kProperties *props,
+                                                const char    *propname,
+                                                char          *value);
+
+void           e2k_properties_set_type_as_string       (E2kProperties *props,
+                                                       const char    *propname,
+                                                       E2kPropType    type,
+                                                       char          *value);
+void           e2k_properties_set_type_as_string_array (E2kProperties *props,
+                                                       const char    *propname,
+                                                       E2kPropType    type,
+                                                       GPtrArray     *value);
+
+void           e2k_properties_remove            (E2kProperties *props,
+                                                const char    *propname);
+
+
+typedef void (*E2kPropertiesForeachFunc)        (const char    *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 char    *namespace,
+                                                char           abbrev,
+                                                gpointer       user_data);
+void           e2k_properties_foreach_namespace (E2kProperties *props,
+                                                E2kPropertiesForeachNamespaceFunc callback,
+                                                gpointer user_data);
+
+const char *e2k_prop_namespace_name   (const char *prop);
+char        e2k_prop_namespace_abbrev (const char *prop);
+const char *e2k_prop_property_name    (const char *prop);
+
+guint32     e2k_prop_proptag          (const char *prop);
+const char *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/servers/exchange/lib/e2k-propnames.c.in b/servers/exchange/lib/e2k-propnames.c.in
new file mode 100644 (file)
index 0000000..c120233
--- /dev/null
@@ -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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+struct mapi_proptag {
+       char *proptag, *name;
+} mapi_proptags[] = {
+@AUTOGENERATE@
+};
+static int nmapi_proptags = sizeof (mapi_proptags) / sizeof (mapi_proptags[0]);
+
diff --git a/servers/exchange/lib/e2k-propnames.h.in b/servers/exchange/lib/e2k-propnames.h.in
new file mode 100644 (file)
index 0000000..6d91ee4
--- /dev/null
@@ -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/servers/exchange/lib/e2k-proptags.h.in b/servers/exchange/lib/e2k-proptags.h.in
new file mode 100644 (file)
index 0000000..7891424
--- /dev/null
@@ -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/servers/exchange/lib/e2k-restriction.c b/servers/exchange/lib/e2k-restriction.c
new file mode 100644 (file)
index 0000000..e27386b
--- /dev/null
@@ -0,0 +1,1070 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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, int nrns, E2kRestriction **rns, gboolean unref)
+{
+       E2kRestriction *ret = g_new0 (E2kRestriction, 1);
+       int 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 (int 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 (int 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 char *propname,
+                        E2kRestrictionFuzzyLevel fuzzy_level,
+                        const char *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 char *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 char *propname, E2kRestrictionRelop relop,
+                         int 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 char *propname, E2kRestrictionRelop relop,
+                          const char *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 char *propname, E2kRestrictionRelop relop,
+                            const char *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 char *propname, E2kRestrictionRelop relop,
+                            gconstpointer data, int 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 char *propname1, E2kRestrictionRelop relop,
+                        const char *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 char *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 char *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 char *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 char *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)
+{
+       int 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 char *sql_relops[] = { "<", "<=", ">", ">=", "=", "!=" };
+static const int n_sql_relops = G_N_ELEMENTS (sql_relops);
+
+static gboolean
+rns_to_sql (E2kRestrictionType type, E2kRestriction **rns, int nrns, GString *sql)
+{
+       int 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 char *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')",
+                                               (char *)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.
+ **/
+char *
+e2k_restriction_to_sql (E2kRestriction *rn)
+{
+       GString *sql;
+       char *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, int *len, E2kRestriction **rn)
+{
+       int 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;
+               int 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;
+               int 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, int *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)
+{
+       int 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)
+{
+       int 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/servers/exchange/lib/e2k-restriction.h b/servers/exchange/lib/e2k-restriction.h
new file mode 100644 (file)
index 0000000..5f0cdac
--- /dev/null
@@ -0,0 +1,102 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+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         (int                   nrns,
+                                            E2kRestriction      **rns,
+                                            gboolean              unref);
+E2kRestriction *e2k_restriction_andv        (E2kRestriction       *rn, ...);
+E2kRestriction *e2k_restriction_or          (int                   nrns,
+                                            E2kRestriction      **rns,
+                                            gboolean              unref);
+E2kRestriction *e2k_restriction_orv         (E2kRestriction       *rn, ...);
+E2kRestriction *e2k_restriction_not         (E2kRestriction       *rn,
+                                            gboolean              unref);
+E2kRestriction *e2k_restriction_content     (const char           *propname,
+                                            E2kRestrictionFuzzyLevel fuzzy_level,
+                                            const char           *value);
+E2kRestriction *e2k_restriction_prop_bool   (const char           *propname,
+                                            E2kRestrictionRelop   relop,
+                                            gboolean              value);
+E2kRestriction *e2k_restriction_prop_int    (const char           *propname,
+                                            E2kRestrictionRelop   relop,
+                                            int                   value);
+E2kRestriction *e2k_restriction_prop_date   (const char           *propname,
+                                            E2kRestrictionRelop   relop,
+                                            const char           *value);
+E2kRestriction *e2k_restriction_prop_string (const char           *propname,
+                                            E2kRestrictionRelop   relop,
+                                            const char           *value);
+E2kRestriction *e2k_restriction_prop_binary (const char           *propname,
+                                            E2kRestrictionRelop   relop,
+                                            gconstpointer         data,
+                                            int                   len);
+E2kRestriction *e2k_restriction_compare     (const char           *propname1,
+                                            E2kRestrictionRelop   relop,
+                                            const char           *propname2);
+E2kRestriction *e2k_restriction_bitmask     (const char           *propname,
+                                            E2kRestrictionBitop   bitop,
+                                            guint32               mask); 
+E2kRestriction *e2k_restriction_size        (const char           *propname,
+                                            E2kRestrictionRelop   relop,
+                                            guint32               size);
+E2kRestriction *e2k_restriction_exist       (const char           *propname);
+E2kRestriction *e2k_restriction_sub         (const char           *subtable,
+                                            E2kRestriction       *rn,
+                                            gboolean              unref);
+
+void            e2k_restriction_ref         (E2kRestriction       *rn);
+void            e2k_restriction_unref       (E2kRestriction       *rn);
+
+char           *e2k_restriction_to_sql      (E2kRestriction       *rn);
+
+gboolean        e2k_restriction_extract     (guint8              **data,
+                                            int                  *len,
+                                            E2kRestriction      **rn);
+void            e2k_restriction_append      (GByteArray           *ba,
+                                            E2kRestriction       *rn);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E2K_RESTRICTION_H__ */
diff --git a/servers/exchange/lib/e2k-result.c b/servers/exchange/lib/e2k-result.c
new file mode 100644 (file)
index 0000000..40227d2
--- /dev/null
@@ -0,0 +1,609 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "e2k-result.h"
+#include "e2k-encoding-utils.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 char *propname, 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, e2k_base64_decode (node->xmlChildrenNode->content));
+               else
+                       g_ptr_array_add (array, g_byte_array_new ());
+       }
+
+       e2k_properties_set_binary_array (result->props, propname, array);
+}
+
+static void
+prop_get_string_array (E2kResult *result, const char *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 (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 char *propname, xmlNode *node)
+{
+       GByteArray *data;
+
+       if (node->xmlChildrenNode && node->xmlChildrenNode->content)
+               data = e2k_base64_decode (node->xmlChildrenNode->content);
+       else
+               data = g_byte_array_new ();
+
+       e2k_properties_set_binary (result->props, propname, data);
+}
+
+static void
+prop_get_string (E2kResult *result, const char *propname,
+                E2kPropType real_type, xmlNode *node)
+{
+       char *content;
+
+       if (node->xmlChildrenNode && node->xmlChildrenNode->content)
+               content = g_strdup (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 char *propname, xmlNode *node)
+{
+       e2k_properties_set_xml (result->props, propname,
+                               xmlCopyNode (node, TRUE));
+}
+
+static void
+prop_parse (xmlNode *node, E2kResult *result)
+{
+       char *name, *type;
+
+       g_return_if_fail (node->ns != NULL);
+
+       if (!result->props)
+               result->props = e2k_properties_new ();
+
+       if (!strncmp (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, "dt", E2K_NS_TYPE);
+       if (type && !strcmp (type, "mv.bin.base64"))
+               prop_get_binary_array (result, name, node);
+       else if (type && !strcmp (type, "mv.int"))
+               prop_get_string_array (result, name, E2K_PROP_TYPE_INT_ARRAY, node);
+       else if (type && !strncmp (type, "mv.", 3))
+               prop_get_string_array (result, name, E2K_PROP_TYPE_STRING_ARRAY, node);
+       else if (type && !strcmp (type, "bin.base64"))
+               prop_get_binary (result, name, node);
+       else if (type && !strcmp (type, "int"))
+               prop_get_string (result, name, E2K_PROP_TYPE_INT, node);
+       else if (type && !strcmp (type, "boolean"))
+               prop_get_string (result, name, E2K_PROP_TYPE_BOOL, node);
+       else if (type && !strcmp (type, "float"))
+               prop_get_string (result, name, E2K_PROP_TYPE_FLOAT, node);
+       else if (type && !strcmp (type, "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 (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 char *
+sanitize_bad_multistatus (const char *buf, int len)
+{
+       GString *body;
+       const char *p;
+       int start, end;
+       char 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;
+       char *body;
+
+       g_return_if_fail (msg->status_code == E2K_HTTP_MULTI_STATUS);
+
+       body = sanitize_bad_multistatus (msg->response.body, msg->response.length);
+       if (body) {
+               doc = e2k_parse_xml (body, -1);
+               g_free (body);
+       } else
+               doc = e2k_parse_xml (msg->response.body, msg->response.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 = xmlNodeGetContent (rnode);
+                       else if (E2K_IS_NODE (rnode, "DAV:", "status")) {
+                               result.status = e2k_http_parse_status (
+                                       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, int *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, int nresults)
+{
+       GArray *results_array = NULL;
+       E2kResult result, *new_results;
+       int 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, int nresults)
+{
+       int 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;
+       int nresults, next;
+       int 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, int 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
+ **/
+int
+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
+ **/
+int
+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/servers/exchange/lib/e2k-result.h b/servers/exchange/lib/e2k-result.h
new file mode 100644 (file)
index 0000000..310556e
--- /dev/null
@@ -0,0 +1,64 @@
+/* -*- 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 {
+       char *href;
+       int status;
+       E2kProperties *props;
+} E2kResult;
+
+void       e2k_results_from_multistatus           (SoupMessage  *msg,
+                                                  E2kResult   **results,
+                                                  int          *nresults);
+
+E2kResult *e2k_results_copy                       (E2kResult    *results,
+                                                  int           nresults);
+
+void       e2k_results_free                       (E2kResult    *results,
+                                                  int           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,
+                                                int *nresults,
+                                                int *first,
+                                                int *total,
+                                                gpointer user_data);
+typedef void          (*E2kResultIterFreeFunc)  (E2kResultIter *iter,
+                                                gpointer user_data);
+
+E2kResultIter *e2k_result_iter_new         (E2kContext            *ctx,
+                                           E2kOperation          *op,
+                                           gboolean               ascending,
+                                           int                    total,
+                                           E2kResultIterFetchFunc fetch_func,
+                                           E2kResultIterFreeFunc  free_func,
+                                           gpointer               user_data);
+
+E2kResult     *e2k_result_iter_next        (E2kResultIter         *iter);
+int            e2k_result_iter_get_index   (E2kResultIter         *iter);
+int            e2k_result_iter_get_total   (E2kResultIter         *iter);
+E2kHTTPStatus  e2k_result_iter_free        (E2kResultIter         *iter);
+
+#endif /* __E2K_RESULT_H__ */
diff --git a/servers/exchange/lib/e2k-rule-xml.c b/servers/exchange/lib/e2k-rule-xml.c
new file mode 100644 (file)
index 0000000..28a836d
--- /dev/null
@@ -0,0 +1,788 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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"
+
+const char *contains_types[] = { NULL, "contains", NULL, NULL, NULL, "not contains", NULL, NULL };
+const char *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 char *name, const char *map[],
+                      int *fuzzy_level, gboolean *negated)
+{
+       int 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 char *
+fuzzy_level_to_name (int fuzzy_level, gboolean negated, const char *map[])
+{
+       fuzzy_level = E2K_FL_MATCH_TYPE (fuzzy_level);
+       if (negated)
+               fuzzy_level |= E2K_FL_NEGATE;
+
+       return map[fuzzy_level];
+}
+
+const char *is_types[] = { NULL, NULL, NULL, NULL, "is", "is not" };
+const char *include_types[] = { NULL, NULL, NULL, NULL, "include", "not include" };
+const char *date_types[] = { "before", "before", "after", "after", NULL, NULL };
+const char *size_types[] = { "less than", "less than", "greater than", "greater than", NULL, NULL };
+
+#if 0
+static gboolean
+relop_from_name (const char *name, const char *map[],
+                E2kRestrictionRelop *relop)
+{
+       int 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 char *
+relop_to_name (E2kRestrictionRelop relop, gboolean negated, const char *map[])
+{
+       static const int 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 char *name, const char *type, const char *value)
+{
+       xmlNode *node;
+
+       node = xmlNewChild (part, NULL, "value", NULL);
+       xmlSetProp (node, "name", name);
+       xmlSetProp (node, "type", type);
+       if (value)
+               xmlSetProp (node, "value", value);
+
+       return node;
+}
+
+static xmlNode *
+new_value_int (xmlNode *part, const char *name, const char *type,
+              const char *value_name, long value)
+{
+       xmlNode *node;
+       char *str;
+
+       node = xmlNewChild (part, NULL, "value", NULL);
+       xmlSetProp (node, "name", name);
+       xmlSetProp (node, "type", type);
+
+       str = g_strdup_printf ("%ld", value);
+       xmlSetProp (node, value_name, str);
+       g_free (str);
+
+       return node;
+}
+
+static xmlNode *
+new_part (const char *part_name)
+{
+       xmlNode *part;
+
+       part = xmlNewNode (NULL, "part");
+       xmlSetProp (part, "name", part_name);
+       return part;
+}
+
+static xmlNode *
+match (const char *part_name, const char *value_name, const char *value_value,
+       const char *string_name, const char *string_value)
+{
+       xmlNode *part, *value;
+
+       part = new_part (part_name);
+       value = new_value (part, value_name, "option", value_value);
+       value = new_value (part, string_name, "string", NULL);
+       xmlNewTextChild (value, NULL, "string", string_value);
+
+       return part;
+}
+
+static xmlNode *
+message_is (const char *name, const char *type_name,
+           const char *kind, gboolean negated)
+{
+       xmlNode *part;
+
+       part = new_part (name);
+       new_value (part, type_name, "option", negated ? "is not" : "is");
+       new_value (part, "kind", "option", kind);
+
+       return part;
+}
+
+static xmlNode *
+address_is (E2kRestriction *comment_rn, gboolean recipients, gboolean negated)
+{
+       xmlNode *part;
+       E2kRestriction *rn;
+       E2kPropValue *pv;
+       const char *relation, *display_name, *p;
+       char *addr, *full_addr;
+       GByteArray *ba;
+       int 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 ((char *)ba->data, ':');
+       if (p)
+               addr = g_ascii_strdown (p + 1, -1);
+       else
+               addr = g_ascii_strdown ((char *)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 ("recipient", "recipient-type", relation,
+                             "recipient", full_addr);
+       } else {
+               part = match ("sender", "sender-type", relation,
+                             "sender", 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 char *match_type;
+       int 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 ("message-to-me",
+                                          "message-to-me-type",
+                                          "only", negated);
+                       break;
+               } else if (restriction_is_delegation (rn)) {
+                       part = message_is ("special-message",
+                                          "special-message-type",
+                                          "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:
+       {
+               int 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 ("body", "body-type", match_type,
+                                     "word", 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 ("subject", "subject-type", match_type,
+                                     "subject", 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 ("full-headers", "full-headers-type",
+                                     match_type, "word", 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 ("special-message",
+                                                  "special-message-type",
+                                                  "oof", negated);
+                       } else if ((fuzzy_level == E2K_FL_PREFIX) &&
+                                  !strcmp (pv->value, "IPM.Schedule.Meeting")) {
+                               part = message_is ("special-message",
+                                                  "special-message-type",
+                                                  "meeting-request", negated);
+                       } else
+                               return FALSE;
+
+                       break;
+
+               default:
+                       return FALSE;
+               }
+               break;
+       }
+
+       case E2K_RESTRICTION_PROPERTY:
+       {
+               E2kRestrictionRelop relop;
+               const char *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 ("message-to-me",
+                                          "message-to-me-type",
+                                          "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 ("message-to-me",
+                                          "message-to-me-type",
+                                          "cc", negated);
+                       break;
+
+               case E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME:
+               case E2K_PROPTAG_PR_CLIENT_SUBMIT_TIME:
+               {
+                       char *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 ("received-date");
+                       else
+                               part = new_part ("sent-date");
+
+                       value = new_value (part, "date-spec-type", "option", relation);
+                       value = new_value (part, "versus", "datespec", NULL);
+
+                       node = xmlNewChild (value, NULL, "datespec", NULL);
+                       xmlSetProp (node, "type", "1");
+
+                       timestamp = g_strdup_printf ("%lu", (unsigned long)e2k_parse_timestamp (pv->value));
+                       xmlSetProp (node, "value", timestamp);
+                       g_free (timestamp);
+
+                       break;
+               }
+
+               case E2K_PROPTAG_PR_MESSAGE_SIZE:
+                       relation = relop_to_name (relop, negated, size_types);
+                       if (!relation)
+                               return FALSE;
+
+                       part = new_part ("size");
+
+                       new_value (part, "size-type", "option", relation);
+                       new_value_int (part, "versus", "integer", "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 ("importance");
+                       new_value (part, "importance-type", "option", relation);
+                       new_value_int (part, "importance", "option", "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 ("sensitivity");
+                       xmlSetProp (part, "name", "sensitivity");
+                       new_value (part, "sensitivity-type", "option", relation);
+                       new_value_int (part, "sensitivity", "option", "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 ("attachments");
+               if (rn->res.bitmask.bitop == E2K_BMR_NEZ) {
+                       new_value (part, "match-type", "option",
+                                  negated ? "not exist" : "exist");
+               } else {
+                       new_value (part, "match-type", "option",
+                                  negated ? "exist" : "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 char *
+stringify_entryid (guint8 *data, int len)
+{
+       GString *string;
+       char *ret;
+       int 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;
+       char *entryid;
+
+       switch (act->type) {
+       case E2K_ACTION_MOVE:
+       case E2K_ACTION_COPY:
+               part = new_part (act->type == E2K_ACTION_MOVE ? "move-to-folder" : "copy-to-folder");
+               value = new_value (part, "folder", "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, "entryid", entryid);
+               g_free (entryid);
+               break;
+
+       case E2K_ACTION_REPLY:
+       case E2K_ACTION_OOF_REPLY:
+               part = new_part (act->type == E2K_ACTION_REPLY ? "reply" : "oof-reply");
+               value = new_value (part, "template", "message-entryid", NULL);
+               entryid = stringify_entryid (
+                       act->act.reply.entryid->data,
+                       act->act.reply.entryid->len);
+               xmlNewTextChild (value, NULL, "entryid", entryid);
+               g_free (entryid);
+               break;
+
+       case E2K_ACTION_DEFER:
+               part = new_part ("defer");
+               break;
+
+       case E2K_ACTION_BOUNCE:
+               part = new_part ("bounce");
+               switch (act->act.bounce_code) {
+               case E2K_ACTION_BOUNCE_CODE_TOO_LARGE:
+                       new_value (part, "bounce_code", "option", "size");
+                       break;
+               case E2K_ACTION_BOUNCE_CODE_FORM_MISMATCH:
+                       new_value (part, "bounce_code", "option", "form-mismatch");
+                       break;
+               case E2K_ACTION_BOUNCE_CODE_ACCESS_DENIED:
+                       new_value (part, "bounce_code", "option", "permission");
+                       break;
+               }
+               break;
+
+       case E2K_ACTION_FORWARD:
+       case E2K_ACTION_DELEGATE:
+       {
+               int i, j;
+               E2kAddrList *list;
+               E2kAddrEntry *entry;
+               E2kPropValue *pv;
+               const char *display_name, *email;
+               char *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 ? "forward" : "delegate");
+                       value = new_value (part, "recipient", "recipient", NULL);
+                       xmlNewTextChild (value, NULL, "recipient", 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 ("set-importance");
+               new_value_int (part, "importance", "option", "value",
+                              GPOINTER_TO_INT (act->act.proptag.value));
+               break;
+
+       case E2K_ACTION_DELETE:
+               part = new_part ("delete");
+               break;
+
+       case E2K_ACTION_MARK_AS_READ:
+               part = new_part ("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;
+       int i;
+
+       top = xmlNewChild (ruleset, NULL, "rule", NULL);
+
+       xmlSetProp (top, "source", 
+                   (rule->state & E2K_RULE_STATE_ONLY_WHEN_OOF) ?
+                   "oof" : "incoming");
+       xmlSetProp (top, "enabled", (rule->state & E2K_RULE_STATE_ENABLED) ? "1" : "0");
+
+       if (rule->name)
+               xmlNewTextChild (top, NULL, "title", rule->name);
+
+       set = xmlNewChild (top, NULL, "partset", NULL);
+       rn = rule->condition;
+       if (rn) {
+               E2kRestrictionType wrap_type;
+
+               if (rn->type == E2K_RESTRICTION_OR) {
+                       xmlSetProp (top, "grouping", "any");
+                       wrap_type = E2K_RESTRICTION_OR;
+               } else {
+                       xmlSetProp (top, "grouping", "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, "grouping", "all");
+
+       set = xmlNewChild (top, NULL, "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 ("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;
+       int i;
+
+       doc = xmlNewDoc (NULL);
+       top = xmlNewNode (NULL, "filteroptions");
+       xmlDocSetRootElement (doc, top);
+
+       ruleset = xmlNewChild (top, NULL, "ruleset", NULL);
+
+       for (i = 0; i < rules->rules->len; i++)
+               rule_to_xml (rules->rules->pdata[i], ruleset);
+
+       return doc;
+}
diff --git a/servers/exchange/lib/e2k-rule-xml.h b/servers/exchange/lib/e2k-rule-xml.h
new file mode 100644 (file)
index 0000000..2b34697
--- /dev/null
@@ -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/servers/exchange/lib/e2k-rule.c b/servers/exchange/lib/e2k-rule.c
new file mode 100644 (file)
index 0000000..11c979c
--- /dev/null
@@ -0,0 +1,688 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 char *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, int *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, int *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, int *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 char *str)
+{
+       /* FIXME: verify encoding */
+       g_byte_array_append (ba, 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, int *len, char **str)
+{
+       int slen;
+
+       for (slen = 0; slen < *len; slen++) {
+               if ((*ptr)[slen] == '\0') {
+                       *str = g_strdup (*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 char *str)
+{
+       gunichar2 *utf16;
+       int 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, int *len, char **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, int *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, int *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, int *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, (char **)&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)
+{
+       int 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;
+       int 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;
+       int 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/servers/exchange/lib/e2k-rule.h b/servers/exchange/lib/e2k-rule.h
new file mode 100644 (file)
index 0000000..35db35d
--- /dev/null
@@ -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 char *name;
+       guint32     proptag;
+} E2kRuleProp;
+
+void e2k_rule_prop_set (E2kRuleProp *prop, const char *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;
+       int 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;
+               gint32        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 {
+       char           *name;
+       guint32         sequence;
+       guint32         state;
+       guint32         user_flags;
+       guint32         level;
+       guint32         condition_lcid;
+       E2kRestriction *condition;
+       GPtrArray      *actions;
+       char           *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, int *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, int *len,
+                                    guint16 *val);
+
+void     e2k_rule_append_byte       (GByteArray *ba, guint8 val);
+gboolean e2k_rule_extract_byte      (guint8 **ptr, int *len,
+                                    guint8 *val);
+
+void     e2k_rule_append_string     (GByteArray *ba, const char *str);
+gboolean e2k_rule_extract_string    (guint8 **ptr, int *len,
+                                    char **str);
+
+void     e2k_rule_append_unicode    (GByteArray *ba, const char *str);
+gboolean e2k_rule_extract_unicode   (guint8 **ptr, int *len,
+                                    char **str);
+
+void     e2k_rule_append_binary     (GByteArray *ba, GByteArray *data);
+gboolean e2k_rule_extract_binary    (guint8 **ptr, int *len,
+                                    GByteArray **data);
+
+void     e2k_rule_append_proptag    (GByteArray *ba, E2kRuleProp *prop);
+gboolean e2k_rule_extract_proptag   (guint8 **ptr, int *len,
+                                    E2kRuleProp *prop);
+
+void     e2k_rule_append_propvalue  (GByteArray *ba, E2kPropValue *pv);
+gboolean e2k_rule_extract_propvalue (guint8 **ptr, int *len,
+                                    E2kPropValue *pv);
+void     e2k_rule_free_propvalue    (E2kPropValue *pv);
+
+#endif /* __E2K_RULE_H__ */
diff --git a/servers/exchange/lib/e2k-security-descriptor.c b/servers/exchange/lib/e2k-security-descriptor.c
new file mode 100644 (file)
index 0000000..bb21064
--- /dev/null
@@ -0,0 +1,962 @@
+/* -*- 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 int 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 int
+ace_compar (E2k_ACE *ace1, E2k_ACE *ace2, E2kSecurityDescriptor *sd)
+{
+       E2kSidType t1;
+       E2kSidType t2;
+       int 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 char *name)
+{
+       for (node = node->xmlChildrenNode; node; node = node->next) {
+               if (node->name && !strcmp (node->name, name))
+                       return node;
+       }
+       return NULL;
+}
+
+static void
+extract_sids (E2kSecurityDescriptor *sd, xmlNodePtr node)
+{
+       xmlNodePtr string_sid_node, type_node, display_name_node;
+       char *string_sid, *content, *display_name;
+       const guint8 *bsid;
+       E2kSid *sid;
+       E2kSidType type;
+
+       for (; node; node = node->next) {
+               if (strcmp (node->name, "sid") != 0) {
+                       if (node->xmlChildrenNode)
+                               extract_sids (sd, node->xmlChildrenNode);
+                       continue;
+               }
+
+               string_sid_node = find_child (node, "string_sid");
+               type_node = find_child (node, "type");
+               display_name_node = find_child (node, "display_name");
+               if (!string_sid_node || !type_node)
+                       continue;
+
+               string_sid = xmlNodeGetContent (string_sid_node);
+
+               content = xmlNodeGetContent (type_node);
+               if (!content || !strcmp (content, "user"))
+                       type = E2K_SID_TYPE_USER;
+               else if (!strcmp (content, "group"))
+                       type = E2K_SID_TYPE_GROUP;
+               else if (!strcmp (content, "well_known_group"))
+                       type = E2K_SID_TYPE_WELL_KNOWN_GROUP;
+               else if (!strcmp (content, "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, string_sid,
+                                                  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, (char *)bsid, sid);
+       }
+}
+
+static gboolean
+parse_sid (E2kSecurityDescriptor *sd, GByteArray *binsd, guint16 *off,
+          E2kSid **sid)
+{
+       int 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;
+       int 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)) {
+                       int 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,
+                            (char *)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;
+       int 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;
+       int 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;
+       int 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;
+       int 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;
+       int 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;
+       int 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) {
+               int size = g_hash_table_size (sd->priv->sid_order);
+
+               g_hash_table_insert (sd->priv->sids, (char *)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);
+       }
+}
+
+
+struct {
+       const char *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 char *
+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)
+{
+       int 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/servers/exchange/lib/e2k-security-descriptor.h b/servers/exchange/lib/e2k-security-descriptor.h
new file mode 100644 (file)
index 0000000..492387a
--- /dev/null
@@ -0,0 +1,81 @@
+/* -*- 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 char        *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/servers/exchange/lib/e2k-sid.c b/servers/exchange/lib/e2k-sid.c
new file mode 100644 (file)
index 0000000..e7e7b12
--- /dev/null
@@ -0,0 +1,318 @@
+/* -*- 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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;
+       char *string_sid;
+       char *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 char *display_name,
+                 const char *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 char *string_sid,
+                            const char *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 char   *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 char *
+e2k_sid_get_string_sid (E2kSid *sid)
+{
+       g_return_val_if_fail (E2K_IS_SID (sid), NULL);
+
+       if (!sid->priv->string_sid) {
+               GString *string;
+               int 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",
+                                               (unsigned long) 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) {
+               int sa, subauth_count;
+               guint32 subauthority;
+               char *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 char *
+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/servers/exchange/lib/e2k-sid.h b/servers/exchange/lib/e2k-sid.h
new file mode 100644 (file)
index 0000000..382ddc9
--- /dev/null
@@ -0,0 +1,69 @@
+/* -*- 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 char    *string_sid,
+                                          const char    *display_name);
+E2kSid       *e2k_sid_new_from_binary_sid (E2kSidType     type,
+                                          const guint8  *binary_sid,
+                                          const char    *display_name);
+
+E2kSidType    e2k_sid_get_sid_type        (E2kSid        *sid);
+const char   *e2k_sid_get_string_sid      (E2kSid        *sid);
+const guint8 *e2k_sid_get_binary_sid      (E2kSid        *sid);
+const char   *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/servers/exchange/lib/e2k-types.h b/servers/exchange/lib/e2k-types.h
new file mode 100644 (file)
index 0000000..dfa5983
--- /dev/null
@@ -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/servers/exchange/lib/e2k-uri.c b/servers/exchange/lib/e2k-uri.c
new file mode 100644 (file)
index 0000000..ffe8b5a
--- /dev/null
@@ -0,0 +1,419 @@
+/* -*- 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 char *uri_string)
+{
+       E2kUri *uri;
+       const char *end, *hash, *colon, *semi, *at, *slash;
+       const char *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 ((unsigned char)*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) {
+                       char *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 &&
+                           !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 char *cur, *p, *eq;
+                       char *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 char *
+e2k_uri_get_param (E2kUri *uri, const char *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 (char *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 int 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 char *in,
+                       gboolean wss_encode, const char *extra_enc_chars)
+{
+       const unsigned char *s = (const unsigned char *)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", (int)*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
+ **/
+char *
+e2k_uri_encode (const char *in, gboolean wss_encode,
+               const char *extra_enc_chars)
+{
+       GString *string;
+       char *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 char *
+e2k_uri_path (const char *uri_string)
+{
+       const char *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
+ **/
+char *
+e2k_uri_concat (const char *uri_prefix, const char *tail)
+{
+       const char *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 char *
+e2k_uri_relative (const char *uri_prefix, const char *uri)
+{
+       int prefix_len = strlen (uri_prefix);
+
+       if (!strncmp (uri_prefix, uri, prefix_len)) {
+               uri += prefix_len;
+               while (*uri == '/')
+                       uri++;
+       }
+
+       return uri;
+}
diff --git a/servers/exchange/lib/e2k-uri.h b/servers/exchange/lib/e2k-uri.h
new file mode 100644 (file)
index 0000000..77fe5a3
--- /dev/null
@@ -0,0 +1,42 @@
+/* -*- 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 {
+       char  *protocol;
+       char  *user;
+       char  *domain;
+       char  *authmech;
+       char  *passwd;
+       char  *host;
+       int    port;
+       char  *path;
+       GData *params;
+       char  *query;
+       char  *fragment;
+} E2kUri;
+
+
+E2kUri *    e2k_uri_new       (const char *uri_string);
+void        e2k_uri_free      (E2kUri *uri);
+const char *e2k_uri_get_param (E2kUri *uri, const char *name);
+
+void        e2k_uri_decode         (char *part);
+char *      e2k_uri_encode         (const char *in,
+                                   gboolean    wss_encode,
+                                   const char *extra_enc_chars);
+void        e2k_uri_append_encoded (GString    *str,
+                                   const char *in,
+                                   gboolean    wss_encode,
+                                   const char *extra_enc_chars);
+
+const char *e2k_uri_path      (const char *uri_string);
+
+char       *e2k_uri_concat    (const char *uri_prefix, const char *tail);
+const char *e2k_uri_relative  (const char *uri_prefix, const char *uri);
+
+#endif /* E2K_URI_H */
diff --git a/servers/exchange/lib/e2k-user-dialog.c b/servers/exchange/lib/e2k-user-dialog.c
new file mode 100644 (file)
index 0000000..739e329
--- /dev/null
@@ -0,0 +1,257 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e2k-user-dialog.h"
+#include "e2k-types.h"
+
+#include <bonobo-activation/bonobo-activation.h>
+#include <bonobo/bonobo-exception.h>
+#include <bonobo/bonobo-widget.h>
+//#include <libedatserver/e-gtk-utils.h>
+//#include <libedatserver/e-dialog-utils.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkvbox.h>
+#include <libedataserverui/e-name-selector.h>
+#include "e2k-xml-utils.h"
+
+struct _E2kUserDialogPrivate {
+       char *section_name;
+       ENameSelector *name_selector;
+       GtkWidget *entry, *parent_window;
+};
+
+#define PARENT_TYPE GTK_TYPE_DIALOG
+static GtkDialogClass *parent_class;
+
+static void parent_window_destroyed (gpointer dialog, GObject *where_parent_window_was);
+
+static void
+finalize (GObject *object)
+{
+       E2kUserDialog *dialog = E2K_USER_DIALOG (object);
+
+       g_free (dialog->priv->section_name);
+       g_free (dialog->priv);
+
+       G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+dispose (GObject *object)
+{
+       E2kUserDialog *dialog = E2K_USER_DIALOG (object);
+
+       if (dialog->priv->name_selector != NULL) {
+               g_object_unref (dialog->priv->name_selector);
+               dialog->priv->name_selector = NULL;
+       }
+
+       if (dialog->priv->parent_window) {
+               g_object_weak_unref (G_OBJECT (dialog->priv->parent_window),
+                                    parent_window_destroyed, dialog);
+               dialog->priv->parent_window = NULL;
+       }
+
+       G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+class_init (E2kUserDialogClass *class)
+{
+       GObjectClass *object_class = (GObjectClass *) class;
+
+       parent_class = g_type_class_ref (GTK_TYPE_DIALOG);
+
+       object_class->dispose = dispose;
+       object_class->finalize = finalize;
+}
+
+static void
+init (E2kUserDialog *dialog)
+{
+       dialog->priv = g_new0 (E2kUserDialogPrivate, 1);
+
+       gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6);
+}
+
+E2K_MAKE_TYPE (e2k_user_dialog, E2kUserDialog, class_init, init, PARENT_TYPE)
+
+
+
+static void
+parent_window_destroyed (gpointer user_data, GObject *where_parent_window_was)
+{
+       E2kUserDialog *dialog = user_data;
+
+       dialog->priv->parent_window = NULL;
+       gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+}
+
+static void
+addressbook_dialog_response (ENameSelectorDialog *name_selector_dialog, gint response, gpointer data)
+{
+       gtk_widget_hide (GTK_WIDGET (name_selector_dialog));
+}
+
+static void
+addressbook_clicked_cb (GtkWidget *widget, gpointer data)
+{
+       E2kUserDialog *dialog = data;
+       E2kUserDialogPrivate *priv;
+       ENameSelectorDialog *name_selector_dialog;
+
+       priv = dialog->priv;
+
+       name_selector_dialog = e_name_selector_peek_dialog (priv->name_selector);
+       gtk_window_set_modal (GTK_WINDOW (dialog), FALSE);
+       gtk_widget_show (GTK_WIDGET (name_selector_dialog));
+}
+
+static gboolean
+e2k_user_dialog_construct (E2kUserDialog *dialog,
+                          GtkWidget *parent_window,
+                          const char *label_text,
+                          const char *section_name)
+{
+       E2kUserDialogPrivate *priv;
+       GtkWidget *hbox, *vbox, *label, *button;
+       ENameSelectorModel *name_selector_model;
+       ENameSelectorDialog *name_selector_dialog;
+
+       gtk_window_set_title (GTK_WINDOW (dialog), _("Select User"));
+       gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+                               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                               GTK_STOCK_OK, GTK_RESPONSE_OK,
+                               NULL);
+
+       // SURF :e_dialog_set_transient_for (GTK_WINDOW (dialog), parent_window);
+
+       priv = dialog->priv;
+       priv->section_name = g_strdup (section_name);
+
+       priv->parent_window = parent_window;
+       g_object_weak_ref (G_OBJECT (parent_window),
+                          parent_window_destroyed, dialog);
+
+       /* Set up the actual select names bits */
+       priv->name_selector = e_name_selector_new ();
+
+       /* Listen for responses whenever the dialog is shown */
+       name_selector_dialog = e_name_selector_peek_dialog (priv->name_selector);
+       g_signal_connect (name_selector_dialog, "response",
+                         G_CALLBACK (addressbook_dialog_response), dialog);
+
+       name_selector_model = e_name_selector_peek_model (priv->name_selector);
+       /* FIXME Limit to one user */
+       e_name_selector_model_add_section (name_selector_model, section_name, section_name, NULL);
+
+       hbox = gtk_hbox_new (FALSE, 6);
+
+       label = gtk_label_new (label_text);
+       gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6);
+
+       /* The vbox is a workaround for bug 43315 */
+       vbox = gtk_vbox_new (FALSE, 0);
+       priv->entry = GTK_WIDGET (e_name_selector_peek_section_entry (priv->name_selector, section_name));
+       gtk_box_pack_start (GTK_BOX (vbox), priv->entry, TRUE, FALSE, 0);
+       gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 6);
+
+       button = gtk_button_new_with_label (_("Addressbook..."));
+       g_signal_connect (button, "clicked",
+                         G_CALLBACK (addressbook_clicked_cb),
+                         dialog);
+       gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 6);
+
+       gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
+                           TRUE, TRUE, 6);
+       gtk_widget_show_all (hbox);
+
+       return TRUE;
+}
+
+/**
+ * e2k_user_dialog_new:
+ * @parent_window: The window invoking the dialog.
+ * @label_text: Text to label the entry in the initial dialog with
+ * @section_name: The section name for the select-names dialog
+ *
+ * Creates a new user selection dialog.
+ *
+ * Return value: A newly-created user selection dialog, or %NULL if
+ * the dialog could not be created.
+ **/
+GtkWidget *
+e2k_user_dialog_new (GtkWidget *parent_window,
+                    const char *label_text, const char *section_name)
+{
+       E2kUserDialog *dialog;
+
+       g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+       g_return_val_if_fail (label_text != NULL, NULL);
+       g_return_val_if_fail (section_name != NULL, NULL);
+
+       dialog = g_object_new (E2K_TYPE_USER_DIALOG, NULL);
+       if (!e2k_user_dialog_construct (dialog, parent_window,
+                                       label_text, section_name)) {
+               gtk_widget_destroy (GTK_WIDGET (dialog));
+               return NULL;
+       }
+
+       return GTK_WIDGET (dialog);
+}
+
+/**
+ * e2k_user_dialog_get_user:
+ * @dialog: the dialog
+ *
+ * Gets the email address of the selected user from the dialog.
+ *
+ * Return value: the email address, which must be freed with g_free().
+ **/
+char *
+e2k_user_dialog_get_user (E2kUserDialog *dialog)
+{
+       E2kUserDialogPrivate *priv;
+       EDestinationStore *destination_store;
+       GList *destinations;
+       EDestination *destination;
+       gchar *result = NULL;
+
+       g_return_val_if_fail (E2K_IS_USER_DIALOG (dialog), NULL);
+
+       priv = dialog->priv;
+
+       destination_store = e_name_selector_entry_peek_destination_store (E_NAME_SELECTOR_ENTRY (priv->entry));
+       destinations = e_destination_store_list_destinations (destination_store);
+       if (!destinations)
+               return NULL;
+
+       destination = destinations->data;
+       result = g_strdup (e_destination_get_email (destination));
+       g_list_free (destinations);
+
+       return result;
+}
diff --git a/servers/exchange/lib/e2k-user-dialog.h b/servers/exchange/lib/e2k-user-dialog.h
new file mode 100644 (file)
index 0000000..b212172
--- /dev/null
@@ -0,0 +1,37 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 2001-2004 Novell, Inc. */
+
+#ifndef __E2K_USER_DIALOG_H__
+#define __E2K_USER_DIALOG_H__
+
+#include <gtk/gtkdialog.h>
+
+#define E2K_TYPE_USER_DIALOG           (e2k_user_dialog_get_type ())
+#define E2K_USER_DIALOG(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), E2K_TYPE_USER_DIALOG, E2kUserDialog))
+#define E2K_USER_DIALOG_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), E2K_TYPE_USER_DIALOG,        \
+                                        E2kUserDialogClass))
+#define E2K_IS_USER_DIALOG(obj)                (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E2K_TYPE_USER_DIALOG))
+#define E2K_IS_USER_DIALOG_CLASS(klass)        (G_TYPE_CHECK_CLASS_TYPE ((klass), E2K_TYPE_USER_DIALOG))
+
+typedef struct _E2kUserDialog          E2kUserDialog;
+typedef struct _E2kUserDialogClass     E2kUserDialogClass;
+typedef struct _E2kUserDialogPrivate   E2kUserDialogPrivate;
+
+struct _E2kUserDialog {
+       GtkDialog parent;
+
+       /* Private data */
+       E2kUserDialogPrivate *priv;
+};
+
+struct _E2kUserDialogClass {
+       GtkDialogClass parent_class;
+};
+
+GType      e2k_user_dialog_get_type (void);
+GtkWidget *e2k_user_dialog_new      (GtkWidget     *parent_window,
+                                    const char    *label_text,
+                                    const char    *section_name);
+char      *e2k_user_dialog_get_user (E2kUserDialog *dialog);
+
+#endif /* __E2K_USER_DIALOG_H__ */
diff --git a/servers/exchange/lib/e2k-utils.c b/servers/exchange/lib/e2k-utils.c
new file mode 100644 (file)
index 0000000..bb2cace
--- /dev/null
@@ -0,0 +1,665 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 char *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 char *timestamp)
+{
+       struct tm tm;
+
+       tm.tm_year = strtoul (timestamp, (char **)&timestamp, 10) - 1900;
+       if (*timestamp++ != '-')
+               return -1;
+       tm.tm_mon = strtoul (timestamp, (char **)&timestamp, 10) - 1;
+       if (*timestamp++ != '-')
+               return -1;
+       tm.tm_mday = strtoul (timestamp, (char **)&timestamp, 10);
+       if (*timestamp++ != 'T')
+               return -1;
+       tm.tm_hour = strtoul (timestamp, (char **)&timestamp, 10);
+       if (*timestamp++ != ':')
+               return -1;
+       tm.tm_min = strtoul (timestamp, (char **)&timestamp, 10);
+       if (*timestamp++ != ':')
+               return -1;
+       tm.tm_sec = strtoul (timestamp, (char **)&timestamp, 10);
+       if (*timestamp != '.' && *timestamp != 'Z')
+               return -1;
+
+       return e_mktime_utc (&tm);
+}
+
+/**
+ * e2k_make_timestamp:
+ * @when: the %time_t to convert to an ISO8601 timestamp
+ *
+ * Creates an ISO8601 timestamp (in an format acceptable to Exchange)
+ * corresponding to @when.
+ *
+ * Return value: the timestamp, which the caller must free.
+ **/
+char *
+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.
+ **/
+char *
+e2k_make_timestamp_rfc822 (time_t when)
+{
+       struct tm tm;
+       int 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.
+ **/
+char *
+e2k_lf_to_crlf (const char *in)
+{
+       int len;
+       const char *s;
+       char *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.
+ **/
+char *
+e2k_crlf_to_lf (const char *in)
+{
+       int len;
+       const char *s;
+       char *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
+ **/
+char *
+e2k_strdup_with_trailing_slash (const char *path)
+{
+       char *p;
+
+       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 char *
+e2k_entryid_to_dn (GByteArray *entryid)
+{
+       char *p;
+
+       p = ((char *)entryid->data) + entryid->len - 1;
+       if (*p == 0) {
+               while (*(p - 1) && p > (char *)entryid->data)
+                       p--;
+               if (*p == '/')
+                       return p;
+       }
+       return NULL;
+}
+
+static void
+append_permanenturl_section (GString *url, guint8 *entryid)
+{
+       int 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.
+ **/
+char *
+e2k_entryid_to_permanenturl (GByteArray *entryid, const char *base_uri)
+{
+       GString *url;
+       char *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 char **permanenturl)
+{
+       const char *p;
+       char buf[44], byte;
+       int 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 = buf;
+       while (p < 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 char *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 unsigned char *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)
+{
+       int 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 char 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 char *display_name, const char *email, gboolean unicode)
+{
+       GByteArray *entryid;
+
+       entryid = g_byte_array_new ();
+
+       e2k_rule_append_uint32 (entryid, 0);
+       g_byte_array_append (entryid, 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 char 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 char *exchange_dn)
+{
+       GByteArray *entryid;
+
+       entryid = g_byte_array_new ();
+
+       e2k_rule_append_uint32 (entryid, 0);
+       g_byte_array_append (entryid, 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 char 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, int nth_address)
+{
+       GByteArray *entryid;
+
+       entryid = g_byte_array_new ();
+
+       e2k_rule_append_uint32 (entryid, 0);
+       g_byte_array_append (entryid, 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 char *addrtype, const char *address)
+{
+       GByteArray *search_key;
+       guint8 *p;
+
+       search_key = g_byte_array_new ();
+       g_byte_array_append (search_key, addrtype, strlen (addrtype));
+       g_byte_array_append (search_key, ":", 1);
+       g_byte_array_append (search_key, address, strlen (address));
+       g_byte_array_append (search_key, "", 1);
+
+       for (p = search_key->data; *p; p++)
+               *p = g_ascii_toupper (*p);
+
+       return search_key;
+}
diff --git a/servers/exchange/lib/e2k-utils.h b/servers/exchange/lib/e2k-utils.h
new file mode 100644 (file)
index 0000000..cf8e216
--- /dev/null
@@ -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 char *timestamp);
+char   *e2k_make_timestamp        (time_t when);
+char   *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);
+
+char *e2k_lf_to_crlf (const char *in);
+char *e2k_crlf_to_lf (const char *in);
+
+char *e2k_strdup_with_trailing_slash (const char *path);
+
+const char *e2k_entryid_to_dn           (GByteArray *entryid);
+
+char       *e2k_entryid_to_permanenturl (GByteArray *entryid,
+                                        const char *base_uri);
+GByteArray *e2k_permanenturl_to_entryid (const char *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 char *display_name,
+                                         const char *email,
+                                         gboolean    unicode);
+GByteArray *e2k_entryid_generate_local   (const char *exchange_dn);
+GByteArray *e2k_entryid_generate_contact (GByteArray *contact_entryid,
+                                         int         nth_address);
+GByteArray *e2k_search_key_generate      (const char *addrtype,
+                                         const char *address);
+
+#endif /* E2K_UTILS_H */
diff --git a/servers/exchange/lib/e2k-validate.h b/servers/exchange/lib/e2k-validate.h
new file mode 100644 (file)
index 0000000..b53623e
--- /dev/null
@@ -0,0 +1,26 @@
+/* -*- 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_
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+typedef struct {
+        char *host;
+        char *ad_server;
+        char *mailbox;
+        char *owa_path;
+       gboolean is_ntlm;
+}ExchangeParams;
+
+gboolean e2k_validate_user (const char *owa_url, char *user, ExchangeParams *exchange_params, gboolean *remember_password);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_VALIDATE_H_ */
diff --git a/servers/exchange/lib/e2k-xml-utils.c b/servers/exchange/lib/e2k-xml-utils.c
new file mode 100644 (file)
index 0000000..f523dbc
--- /dev/null
@@ -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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 (void *ctx, const char *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 char *buf, int 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 char *buf, int 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 */
+       char *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 char *value)
+{
+       while (*value) {
+               switch (*value) {
+               case '<':
+                       g_string_append (string, "&lt;");
+                       break;
+               case '>':
+                       g_string_append (string, "&gt;");
+                       break;
+               case '&':
+                       g_string_append (string, "&amp;");
+                       break;
+               case '"':
+                       g_string_append (string, "&quot;");
+                       break;
+
+               default:
+                       g_string_append_c (string, *value);
+                       break;
+               }
+               value++;
+       }
+}
+
+/**
+ * e2k_xml_find:
+ * @node: a node of an xml document
+ * @name: the name of the element to find
+ *
+ * Starts or continues a pre-order depth-first search of an xml
+ * document for an element named @name. @node is used as the starting
+ * point of the search, but is not examined itself.
+ *
+ * To search the complete document, pass the root node of the document
+ * as @node on the first call, and then on each successive call,
+ * pass the previous match as @node.
+ *
+ * Return value: the first matching element after @node, or %NULL when
+ * there are no more matches.
+ **/
+xmlNode *
+e2k_xml_find (xmlNode *node, const char *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 char *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 (node->name, name))
+                       return node;
+       }
+
+       return NULL;
+}
diff --git a/servers/exchange/lib/e2k-xml-utils.h b/servers/exchange/lib/e2k-xml-utils.h
new file mode 100644 (file)
index 0000000..c26be08
--- /dev/null
@@ -0,0 +1,24 @@
+/* -*- 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 char *buf, int len);
+xmlDoc *e2k_parse_html (const char *buf, int len);
+
+#define E2K_IS_NODE(node, nspace, nname) (!strcmp (node->name, nname) && node->ns && !strcmp (node->ns->href, nspace))
+
+void  e2k_g_string_append_xml_escaped (GString *string, const char *value);
+
+xmlNode *e2k_xml_find    (xmlNode *node, const char *name);
+xmlNode *e2k_xml_find_in (xmlNode *node, xmlNode *top, const char *name);
+
+#endif
diff --git a/servers/exchange/lib/mapi-properties b/servers/exchange/lib/mapi-properties
new file mode 100644 (file)
index 0000000..45a8cf9
--- /dev/null
@@ -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/servers/exchange/lib/mapi.h b/servers/exchange/lib/mapi.h
new file mode 100644 (file)
index 0000000..d5aaefc
--- /dev/null
@@ -0,0 +1,127 @@
+/* -*- 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__
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+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;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __MAPI_H__ */
diff --git a/servers/exchange/storage/Makefile.am b/servers/exchange/storage/Makefile.am
new file mode 100644 (file)
index 0000000..ae83547
--- /dev/null
@@ -0,0 +1,122 @@
+INCLUDES =                                                     \
+       -DG_LOG_DOMAIN=\"evolution-exchange-storage\"           \
+       -DPREFIX=\"$(prefix)\"                                  \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                        \
+       -DDATADIR=\""$(datadir)"\"                              \
+       -DLIBDIR=\""$(datadir)"\"                               \
+       -DCONNECTOR_GLADEDIR=\""$(gladedir)"\"                  \
+       -DCONNECTOR_IMAGESDIR=\""$(imagesdir)"\"                \
+       -DCONNECTOR_UIDIR=\""$(uidir)"\"                        \
+       -DCONNECTOR_LOCALEDIR=\""$(localedir)\""                \
+       $(KRB5_CFLAGS)                                          \
+       $(LDAP_CFLAGS)                                          \
+       $(SOUP_CFLAGS)                                          \
+       $(E_DATA_SERVER_CFLAGS)                                 \
+       $(E_DATA_SERVER_UI_CFLAGS)                              \
+       -I$(top_srcdir)                                         \
+       -I$(top_srcdir)/servers/exchange/lib                    \
+       -I$(top_srcdir)/servers/exchange/xntlm                                  
+
+lib_LTLIBRARIES =                              \
+       libexchange-storage-1.2.la
+
+libexchange_storage_1_2_la_SOURCES =                           \
+       $(MARSHAL_GENERATED)                                    \
+       e-folder.c                                              \
+       e-folder.h                                              \
+       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-storage.c                                             \
+       e-storage.h                                             \
+       exchange-account.c                                      \
+       exchange-account.h                                      \
+       exchange-folder-size.c                                  \
+       exchange-folder-size.h                                  \
+       exchange-hierarchy-favorites.c                          \
+       exchange-hierarchy-favorites.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-types.h                                        
+
+e-shell-marshal.h: e-shell-marshal.list
+       ( @GLIB_GENMARSHAL@ --prefix=e_shell_marshal $(srcdir)/e-shell-marshal.list --header > e-shell-marshal.h.tmp \
+       && mv e-shell-marshal.h.tmp e-shell-marshal.h ) \
+       || ( rm -f e-shell-marshal.h.tmp && exit 1 )
+
+e-shell-marshal.c: e-shell-marshal.h
+       ( (echo '#include "e-shell-marshal.h"'; @GLIB_GENMARSHAL@ --prefix=e_shell_marshal $(srcdir)/e-shell-marshal.list --body) > e-shell-marshal.c.tmp \
+       && mv e-shell-marshal.c.tmp e-shell-marshal.c ) \
+       || ( rm -f e-shell-marshal.c.tmp && exit 1 )
+
+MARSHAL_GENERATED = e-shell-marshal.c e-shell-marshal.h
+
+BUILT_SOURCES = $(MARSHAL_GENERATED)
+CLEANFILES = $(MARSHAL_GENERATED)
+NODIST_FILES = $(MARSHAL_GENERATED)
+
+libexchange_storage_1_2_la_LIBADD =                            \
+       $(E_DATA_SERVER_LIBS)                                   \
+       $(E_DATA_SERVER_UI_LIBS)                                \
+       $(KRB5_LIBS)                                            \
+       $(LDAP_LIBS)                                            \
+       $(SOUP_LIBS)
+
+libexchange_storage_1_2_la_LDFLAGS =                           \
+       $(top_builddir)/servers/exchange/lib/libexchange.la     \
+       $(top_builddir)/servers/exchange/xntlm/libxntlm.la                      
+       -version-info 1:2:0
+
+libexchange_storageincludedir = $(privincludedir)/exchange
+
+libexchange_storageinclude_HEADERS = \
+       e-folder.h                                              \
+       e-folder-exchange.h                                     \
+       e-storage.h                                             \
+       exchange-account.h                                      \
+       exchange-constants.h                                    \
+       exchange-folder-size.h                                  \
+       exchange-hierarchy.h                                    \
+       exchange-types.h                                        
+
+#imagesdir = $(CONNECTOR_DATADIR)/images
+#images_DATA =                         \
+#      connector.png                   \
+#      connector-mini.png              \
+#      exchange-delegates-48.png       \
+#      exchange-oof-48.png
+
+#gladedir   = $(CONNECTOR_DATADIR)/glade
+#glade_DATA =                                  \
+       #exchange-autoconfig-wizard.glade       \
+       #exchange-change-password.glade         \
+       #exchange-delegates.glade               \
+       #exchange-oof.glade                     \
+       #exchange-permissions-dialog.glade      \
+       #exchange-folder-tree.glade             \
+       #exchange-passwd-expiry.glade
+
+#uidir   = $(CONNECTOR_DATADIR)/ui
+#ui_DATA = ximian-connector.xml
+
+%-$(API_VERSION).pc: %.pc
+        cp $< $@
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libexchange-storage-$(API_VERSION).pc
+
+EXTRA_DIST = $(pkgconfig_DATA:-$(API_VERSION).pc=.pc.in)
+
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+#EXTRA_DIST =                          
+
diff --git a/servers/exchange/storage/e-folder-exchange.c b/servers/exchange/storage/e-folder-exchange.c
new file mode 100644 (file)
index 0000000..da5a173
--- /dev/null
@@ -0,0 +1,997 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-folder-exchange.h"
+#include "exchange-account.h"
+#include "exchange-hierarchy.h"
+#include "e2k-uri.h"
+#include "e2k-path.h"
+//#include "exchange-config-listener.h"
+
+#include <libedataserver/e-util.h>
+#include <libedataserver/e-xml-hash-utils.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libedataserver/e-source-list.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+struct _EFolderExchangePrivate {
+       ExchangeHierarchy *hier;
+       char *internal_uri, *permanent_uri;
+       char *outlook_class, *storage_dir;
+       const char *path;
+       long long int folder_size;
+       gboolean has_subfolders;
+};
+
+#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);
+}
+
+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);
+
+       G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+E2K_MAKE_TYPE (e_folder_exchange, EFolderExchange, class_init, init, PARENT_TYPE)
+
+/**
+// SURF : Added from gal/util/e-util.c
+ * e_mkdir_hier:
+ * @path: a directory path
+ * @mode: a mode, as for mkdir(2)
+ *
+ * This creates the named directory with the given @mode, creating
+ * any necessary intermediate directories (with the same @mode).
+ *
+ * Return value: 0 on success, -1 on error, in which case errno will
+ * be set as for mkdir(2).
+ **/
+static int
+e_mkdir_hier(const char *path, mode_t mode)
+{
+       char *copy, *p;
+
+       if (path[0] == '/') {
+               p = copy = g_strdup (path);
+       } else {
+               gchar *current_dir = g_get_current_dir();
+               // SURF : p = copy = g_concat_dir_and_file (current_dir, path);
+               p = copy = g_strdup_printf ("%s/%s", current_dir, path);
+       }
+
+       do {
+               p = strchr (p + 1, '/');
+               if (p)
+                       *p = '\0';
+               if (access (copy, F_OK) == -1) {
+                       if (mkdir (copy, mode) == -1) {
+                               g_free (copy);
+                               return -1;
+                       }
+               }
+               if (p)
+                       *p = '/';
+       } while (p);
+
+       g_free (copy);
+       return 0;
+}
+
+/**
+ * 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 char *name,
+                      const char *type, const char *outlook_class,
+                      const char *physical_uri, const char *internal_uri)
+{
+       EFolderExchange *efe;
+       EFolder *ef;
+
+       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);
+
+       efe = g_object_new (E_TYPE_FOLDER_EXCHANGE, NULL);
+       ef = (EFolder *)efe;
+
+       e_folder_construct (ef, name, type, "");
+       e_folder_set_physical_uri (ef, physical_uri);
+
+       efe->priv->hier = hier;
+       g_object_ref (hier);
+       efe->priv->internal_uri = g_strdup (internal_uri);
+       efe->priv->path = e2k_uri_path (e_folder_get_physical_uri (ef));
+       efe->priv->outlook_class = g_strdup (outlook_class);
+
+#if 0
+SURF :
+       /* 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);
+               }
+       }
+#endif 
+       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 char *
+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 char *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 char *
+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 char *
+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 char *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
+ **/
+long long int
+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, long long int 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_outlook_class:
+ * @folder: an #EFolderExchange
+ *
+ * Return value: @folder's Outlook IPM class
+ **/
+const char *
+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.
+ **/
+char *
+e_folder_exchange_get_storage_file (EFolder *folder, const char *filename)
+{
+       EFolderExchange *efe;
+       char *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);
+               e_mkdir_hier (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 char *filename)
+{
+       xmlDoc *doc;
+       xmlNode *root;
+       const char *name, *type, *outlook_class;
+       const char *physical_uri, *internal_uri, *permanent_uri;
+       const char *folder_size;
+       long long int fsize;
+       int 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);
+       if ((fsize = e_folder_exchange_get_folder_size (folder)) >= 0)
+               folder_size = g_strdup_printf ("%llu", fsize);
+       else
+               return FALSE;
+
+       g_return_val_if_fail (name && type && physical_uri && internal_uri,
+                             FALSE);
+
+       doc = xmlNewDoc ("1.0");
+       root = xmlNewDocNode (doc, NULL, "connector-folder", NULL);
+       xmlNewProp (root, "version", "1");
+       xmlDocSetRootElement (doc, root);
+
+       xmlNewChild (root, NULL, "displayname", name);
+       xmlNewChild (root, NULL, "type", type);
+       xmlNewChild (root, NULL, "outlook_class", outlook_class);
+       xmlNewChild (root, NULL, "physical_uri", physical_uri);
+       xmlNewChild (root, NULL, "internal_uri", internal_uri);
+       xmlNewChild (root, NULL, "folder_size", folder_size);
+       if (permanent_uri)
+               xmlNewChild (root, NULL, "permanent_uri", permanent_uri);
+
+       status = xmlSaveFile (filename, doc);
+       xmlFreeDoc (doc);
+       if (status < 0)
+               unlink (filename);
+
+       return status == 0;
+}
+
+/* Taken this from the old GAL code */
+static xmlNode *
+e_xml_get_child_by_name (const xmlNode *parent, const xmlChar *child_name)
+{
+       xmlNode *child;
+
+       g_return_val_if_fail (parent != NULL, NULL);
+       g_return_val_if_fail (child_name != NULL, NULL);
+
+       for (child = parent->xmlChildrenNode; child != NULL; child = child->next) {
+               if (xmlStrcmp (child->name, child_name) == 0) {
+                       return child;
+               }       
+       }
+       return NULL;
+}
+
+/**
+ * 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 char *filename)
+{
+       EFolder *folder = NULL;
+       xmlDoc *doc;
+       xmlNode *root, *node;
+       char *version, *display_name = NULL;
+       char *type = NULL, *outlook_class = NULL;
+       char *physical_uri = NULL, *internal_uri = NULL;
+       char *permanent_uri = NULL;
+       char *folder_size = NULL;
+
+       doc = xmlParseFile (filename);
+       if (!doc)
+               return NULL;
+
+       root = xmlDocGetRootElement (doc);
+       if (root == NULL || strcmp (root->name, "connector-folder") != 0)
+               return NULL;
+       version = xmlGetProp (root, "version");
+       if (!version)
+               return NULL;
+       if (strcmp (version, "1") != 0) {
+               xmlFree (version);
+               return NULL;
+       }
+       xmlFree (version);
+
+       node = e_xml_get_child_by_name (root, "displayname");
+       if (!node)
+               goto done;
+       display_name = xmlNodeGetContent (node);
+
+       node = e_xml_get_child_by_name (root, "type");
+       if (!node)
+               goto done;
+       type = xmlNodeGetContent (node);
+
+       node = e_xml_get_child_by_name (root, "outlook_class");
+       if (!node)
+               goto done;
+       outlook_class = xmlNodeGetContent (node);
+
+       node = e_xml_get_child_by_name (root, "physical_uri");
+       if (!node)
+               goto done;
+       physical_uri = xmlNodeGetContent (node);
+
+       node = e_xml_get_child_by_name (root, "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, display_name,
+                                       type, outlook_class,
+                                       physical_uri, internal_uri);
+
+       node = e_xml_get_child_by_name (root, "permanent_uri");
+       if (node) {
+               permanent_uri = xmlNodeGetContent (node);
+               e_folder_exchange_set_permanent_uri (folder, permanent_uri);
+       }
+
+       node = e_xml_get_child_by_name (root, "folder_size");
+       if (node) {
+               folder_size = xmlNodeGetContent (node);
+               e_folder_exchange_set_folder_size (folder, atoi (folder_size));
+       }
+
+ done:
+       xmlFree (display_name);
+       xmlFree (type);
+       xmlFree (outlook_class);
+       xmlFree (physical_uri);
+       xmlFree (internal_uri);
+       xmlFree (permanent_uri);
+
+       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 char **props, int nprops,
+                           E2kResult **results, int *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 char **hrefs, int nhrefs,
+                                  const char **props, int 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 char **props, int nprops,
+                               E2kRestriction *rn, const char *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, int 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)
+{
+       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. */
+       E2kContext *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 char *object_name, 
+                          E2kContextTestCallback test_callback,
+                          gpointer user_data,
+                          const char *content_type,
+                          const char *body, int length,
+                          char **location, char **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 char *object_name,
+                                E2kContextTestCallback test_callback,
+                                gpointer user_data,
+                                E2kProperties *props,
+                                char **location, char **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 char **hrefs, int 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 char **hrefs, int 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,
+                        char **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 char *folder_type, *physical_uri;
+
+       g_return_val_if_fail (E_IS_FOLDER_EXCHANGE (folder), E2K_HTTP_MALFORMED);
+#if 0
+SURF :
+       /* 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);
+               }
+       }
+#endif
+       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,
+                               char **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/servers/exchange/storage/e-folder-exchange.h b/servers/exchange/storage/e-folder-exchange.h
new file mode 100644 (file)
index 0000000..56149a9
--- /dev/null
@@ -0,0 +1,158 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char            *name,
+                                            const char            *type,
+                                            const char            *outlook_class,
+                                            const char            *phys_uri,
+                                            const char            *int_uri);
+
+EFolder    *e_folder_exchange_new_from_file (ExchangeHierarchy     *hier,
+                                            const char            *filename);
+gboolean    e_folder_exchange_save_to_file  (EFolder               *folder,
+                                            const char            *filename);
+
+
+const char *e_folder_exchange_get_internal_uri     (EFolder    *folder);
+void        e_folder_exchange_set_internal_uri     (EFolder    *folder,
+                                                   const char *internal_uri);
+
+const char *e_folder_exchange_get_path             (EFolder    *folder);
+
+const char *e_folder_exchange_get_permanent_uri    (EFolder    *folder);
+void        e_folder_exchange_set_permanent_uri    (EFolder    *folder,
+                                                   const char *permanent_uri);
+
+long long int  e_folder_exchange_get_folder_size (EFolder *folder);
+void           e_folder_exchange_set_folder_size (EFolder *folder, long long int folder_size);
+
+gboolean    e_folder_exchange_get_has_subfolders   (EFolder    *folder);
+void        e_folder_exchange_set_has_subfolders   (EFolder    *folder,
+                                                   gboolean   has_subfolders);
+
+const char *e_folder_exchange_get_outlook_class    (EFolder    *folder);
+
+char       *e_folder_exchange_get_storage_file     (EFolder    *folder,
+                                                   const char *filename);
+
+ExchangeHierarchy *e_folder_exchange_get_hierarchy (EFolder    *folder);
+
+
+/* E2kContext wrappers */
+E2kHTTPStatus  e_folder_exchange_propfind          (EFolder *folder,
+                                                   E2kOperation *op,
+                                                   const char **props,
+                                                   int nprops,
+                                                   E2kResult **results,
+                                                   int *nresults);
+E2kResultIter *e_folder_exchange_bpropfind_start   (EFolder *folder,
+                                                   E2kOperation *op,
+                                                   const char **hrefs,
+                                                   int nhrefs,
+                                                   const char **props,
+                                                   int nprops);
+
+E2kResultIter *e_folder_exchange_search_start      (EFolder *folder,
+                                                   E2kOperation *op,
+                                                   const char **props,
+                                                   int nprops,
+                                                   E2kRestriction *rn,
+                                                   const char *orderby,
+                                                   gboolean ascending);
+
+void           e_folder_exchange_subscribe         (EFolder *folder,
+                                                   E2kContextChangeType,
+                                                   int 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 char *object_name,
+                                                   E2kContextTestCallback,
+                                                   gpointer user_data,
+                                                   const char *content_type,
+                                                   const char *body,
+                                                   int length,
+                                                   char **location,
+                                                   char **repl_uid);
+
+E2kHTTPStatus  e_folder_exchange_proppatch_new     (EFolder *folder,
+                                                   E2kOperation *op,
+                                                   const char *object_name,
+                                                   E2kContextTestCallback,
+                                                   gpointer user_data,
+                                                   E2kProperties *props,
+                                                   char **location,
+                                                   char **repl_uid);
+
+E2kResultIter *e_folder_exchange_bproppatch_start  (EFolder *folder,
+                                                   E2kOperation *op,
+                                                   const char **hrefs,
+                                                   int nhrefs,
+                                                   E2kProperties *props,
+                                                   gboolean create);
+
+E2kResultIter *e_folder_exchange_bdelete_start     (EFolder *folder,
+                                                   E2kOperation *op,
+                                                   const char **hrefs,
+                                                   int nhrefs);
+
+E2kHTTPStatus  e_folder_exchange_mkcol             (EFolder *folder,
+                                                   E2kOperation *op,
+                                                   E2kProperties *props,
+                                                   char **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,
+                                                   char **permanent_url);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E_FOLDER_EXCHANGE_H__ */
diff --git a/servers/exchange/storage/e-folder-tree.c b/servers/exchange/storage/e-folder-tree.c
new file mode 100644 (file)
index 0000000..cf0df4c
--- /dev/null
@@ -0,0 +1,450 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder-set.c
+ *
+ * Copyright (C) 2000, 2001 Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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;
+       char *path;
+       void *data;
+       GList *subfolders;
+};
+typedef struct Folder Folder;
+
+struct EFolderTree {
+       GHashTable *path_to_folder;
+       GHashTable *data_to_path;
+
+       EFolderDestroyNotify folder_destroy_notify;
+       void *folder_destroy_notify_closure;
+};
+
+/* Utility functions.  */
+
+static char *
+get_parent_path (const char *path)
+{
+       const char *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,
+                 void *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 char *path,
+           void *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,
+                  void *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 char *path,
+                  void *data)
+{
+       Folder *parent_folder;
+       Folder *folder;
+       const char *existing_path;
+       char *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 char *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 char *path,
+            void *data,
+            void *closure)
+{
+       int *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
+ **/
+int
+e_folder_tree_get_count (EFolderTree *folder_tree)
+{
+       int 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.
+ **/
+void *
+e_folder_tree_get_folder (EFolderTree *folder_tree,
+                         const char *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 char *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,
+                      void *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 char *
+e_folder_tree_get_path_for_data  (EFolderTree *folder_tree,
+                                 const void *data)
+{
+       g_return_val_if_fail (folder_tree != NULL, NULL);
+       g_return_val_if_fail (data != NULL, NULL);
+
+       return (const char *) g_hash_table_lookup (folder_tree->data_to_path, data);
+}
diff --git a/servers/exchange/storage/e-folder-tree.h b/servers/exchange/storage/e-folder-tree.h
new file mode 100644 (file)
index 0000000..02df249
--- /dev/null
@@ -0,0 +1,58 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder-tree.h
+ *
+ * Copyright (C) 2000  Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 char *path, void *data, void *closure);
+typedef void (* EFolderTreeForeachFunc) (EFolderTree *tree, const char *path, void *data, void *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 char             *path,
+                                             void                   *data);
+gboolean    e_folder_tree_remove             (EFolderTree            *folder_tree,
+                                             const char             *path);
+
+int         e_folder_tree_get_count          (EFolderTree            *folder_tree);
+
+void       *e_folder_tree_get_folder         (EFolderTree            *folder_tree,
+                                             const char             *path);
+GList      *e_folder_tree_get_subfolders     (EFolderTree            *folder_tree,
+                                             const char             *path);
+                                           
+void        e_folder_tree_foreach            (EFolderTree            *folder_tree,
+                                             EFolderTreeForeachFunc  foreach_func,
+                                             void                   *data);
+
+const char *e_folder_tree_get_path_for_data  (EFolderTree            *folder_tree,
+                                             const void             *data);
+
+#endif /* _E_FOLDER_TREE_H_ */
diff --git a/servers/exchange/storage/e-folder-type-registry.c b/servers/exchange/storage/e-folder-type-registry.c
new file mode 100644 (file)
index 0000000..e735588
--- /dev/null
@@ -0,0 +1,413 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder-type-registry.c
+ *
+ * Copyright (C) 2000, 2001 Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-folder-type-registry.h"
+
+// SURF : #include <gal/util/e-util.h>
+
+// SURF : #include <shell/e-shell-utils.h>
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+typedef struct {
+       char *name;
+       char *icon_name;
+
+       char *display_name;
+       char *description;
+
+       gboolean user_creatable;
+
+       GList *accepted_dnd_types; /* char * */
+
+       GObject *handler;
+
+} FolderType;
+
+struct EFolderTypeRegistryPrivate {
+       GHashTable *name_to_type;
+};
+
+/* FolderType handling.  */
+
+static FolderType *
+folder_type_new (const char *name,
+                const char *icon_name,
+                const char *display_name,
+                const char *description,
+                gboolean user_creatable,
+                int num_accepted_dnd_types,
+                const char **accepted_dnd_types)
+{
+       FolderType *new;
+       int 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 char *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 char *name,
+                     const char *icon_name,
+                     const char *display_name,
+                     const char *description,
+                     gboolean user_creatable,
+                     int num_accepted_dnd_types,
+                     const char **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 char *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 char *type_name,
+                                     const char *icon_name,
+                                     const char *display_name,
+                                     const char *description,
+                                     gboolean user_creatable,
+                                     int num_accepted_dnd_types,
+                                     const char **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 char *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 char *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 char *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 (void *key,
+                           void *value,
+                           void *data)
+{
+       GList **type_name_list;
+
+       type_name_list = (GList **) data;
+
+       *type_name_list = g_list_prepend (*type_name_list, g_strdup ((const char *) 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 char *
+e_folder_type_registry_get_icon_name_for_type (EFolderTypeRegistry *folder_type_registry,
+                                              const char *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 char *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 char          *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 char *
+e_folder_type_registry_get_display_name_for_type (EFolderTypeRegistry *folder_type_registry,
+                                                 const char *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 FALSE;
+
+       return folder_type->display_name;
+}
+
+const char *
+e_folder_type_registry_get_description_for_type (EFolderTypeRegistry *folder_type_registry,
+                                                const char *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 FALSE;
+
+       return folder_type->description;
+}
+
+GList *
+e_folder_type_registry_get_accepted_dnd_types_for_type (EFolderTypeRegistry *folder_type_registry,
+                                                       const char *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;
+}
+// SURF : E_MAKE_TYPE (e_folder_type_registry, "EFolderTypeRegistry", EFolderTypeRegistry, class_init, init, PARENT_TYPE)
+G_DEFINE_TYPE (EFolderTypeRegistry, e_folder_type_registry, G_TYPE_OBJECT)
diff --git a/servers/exchange/storage/e-folder-type-registry.h b/servers/exchange/storage/e-folder-type-registry.h
new file mode 100644 (file)
index 0000000..66a61e4
--- /dev/null
@@ -0,0 +1,95 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder-type-registry.h
+ *
+ * Copyright (C) 2000  Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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>
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char                    *type_name,
+                                                       const char                    *icon_name,
+                                                       const char                    *display_name,
+                                                       const char                    *description,
+                                                       gboolean                       user_creatable,
+                                                       int                            num_accepted_dnd_types,
+                                                       const char                   **accepted_dnd_types);
+gboolean  e_folder_type_registry_set_handler_for_type  (EFolderTypeRegistry           *folder_type_registry,
+                                                       const char                    *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 char          *type_name);
+void      e_folder_type_registry_unregister_type  (EFolderTypeRegistry *folder_type_registry,
+                                                  const char          *type_name);
+
+const char                    *e_folder_type_registry_get_icon_name_for_type     (EFolderTypeRegistry *folder_type_registry,
+                                                                                 const char          *type_name);
+GObject                       *e_folder_type_registry_get_handler_for_type       (EFolderTypeRegistry *folder_type_registry,
+                                                                                 const char          *type_name);
+gboolean                       e_folder_type_registry_type_is_user_creatable     (EFolderTypeRegistry *folder_type_registry,
+                                                                                 const char          *type_name);
+const char                    *e_folder_type_registry_get_display_name_for_type  (EFolderTypeRegistry *folder_type_registry,
+                                                                                 const char          *type_name);
+const char                    *e_folder_type_registry_get_description_for_type   (EFolderTypeRegistry *folder_type_registry,
+                                                                                 const char          *type_name);
+
+GList *e_folder_type_registry_get_accepted_dnd_types_for_type (EFolderTypeRegistry *folder_type_registry,
+                                                              const char *type_name);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _E_FOLDER_TYPE_REGISTRY_H_ */
diff --git a/servers/exchange/storage/e-folder.c b/servers/exchange/storage/e-folder.c
new file mode 100644 (file)
index 0000000..12719a7
--- /dev/null
@@ -0,0 +1,465 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder.c
+ *
+ * Copyright (C) 2000, 2001, 2002  Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-folder.h"
+#include "e-shell-marshal.h"
+
+#include <string.h>
+#include <glib.h>
+#include <libedataserver/e-util.h>
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+struct EFolderPrivate {
+       char *name;
+       char *type;
+       char *description;
+       char *physical_uri;
+
+       int child_highlight;
+       int 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.  */
+       int sorting_priority;
+
+       unsigned int self_highlight : 1;
+       unsigned int is_stock : 1;
+       unsigned int can_sync_offline : 1;
+       unsigned int has_subfolders : 1;
+
+       /* Custom icon for this folder; if NULL the folder will just use the
+          icon for its type.  */
+       char *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 char *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,
+                                        e_shell_marshal_NONE__NONE,
+                                        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,
+                                             e_shell_marshal_NONE__NONE,
+                                             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 char *name,
+                   const char *type,
+                   const char *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 char *name,
+             const char *type,
+             const char *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 char *
+e_folder_get_name (EFolder *folder)
+{
+       g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+       return folder->priv->name;
+}
+
+const char *
+e_folder_get_type_string (EFolder *folder)
+{
+       g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+       return folder->priv->type;
+}
+
+const char *
+e_folder_get_description (EFolder *folder)
+{
+       g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+       return folder->priv->description;
+}
+
+const char *
+e_folder_get_physical_uri (EFolder *folder)
+{
+       g_return_val_if_fail (E_IS_FOLDER (folder), NULL);
+
+       return folder->priv->physical_uri;
+}
+
+int
+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 char *
+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.
+ **/
+int
+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 char *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 char *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 char *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 char *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 char *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,
+                              int 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 char *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);
+}
+
+// SURF : E_MAKE_TYPE (e_folder, "EFolder", EFolder, class_init, init, PARENT_TYPE)
+G_DEFINE_TYPE (EFolder, e_folder, G_TYPE_OBJECT)
diff --git a/servers/exchange/storage/e-folder.h b/servers/exchange/storage/e-folder.h
new file mode 100644 (file)
index 0000000..e9c4bb9
--- /dev/null
@@ -0,0 +1,105 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-folder.h
+ *
+ * Copyright (C) 2000, 2001, 2002  Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifndef _E_FOLDER_H_
+#define _E_FOLDER_H_
+
+#include <glib-object.h>
+#include <gtk/gtkdnd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char *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 char *name,
+                             const char *type,
+                             const char *description);
+EFolder *e_folder_new        (const char *name,
+                             const char *type,
+                             const char *description);
+
+const char *e_folder_get_name            (EFolder *folder);
+const char *e_folder_get_type_string     (EFolder *folder);
+const char *e_folder_get_description     (EFolder *folder);
+const char *e_folder_get_physical_uri    (EFolder *folder);
+int         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 char *e_folder_get_custom_icon_name (EFolder *folder);
+int         e_folder_get_sorting_priority (EFolder *folder);
+
+void  e_folder_set_name              (EFolder *folder, const char *name);
+void  e_folder_set_type_string       (EFolder *folder, const char *type);
+void  e_folder_set_description       (EFolder *folder, const char *description);
+void  e_folder_set_physical_uri      (EFolder *folder, const char *physical_uri);
+void  e_folder_set_unread_count      (EFolder *folder, int 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 char *icon_name);
+void  e_folder_set_sorting_priority  (EFolder *folder, int sorting_priority);
+
+gboolean e_folder_accept_drop        (EFolder *folder, GdkDragContext *context,
+                                     const char *target_type,
+                                     GtkSelectionData *selection_data);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _E_FOLDER_H_ */
diff --git a/servers/exchange/storage/e-storage.c b/servers/exchange/storage/e-storage.c
new file mode 100644 (file)
index 0000000..cd8fa12
--- /dev/null
@@ -0,0 +1,831 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-storage.c
+ *
+ * Copyright (C) 2000, 2001 Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-storage.h"
+
+#include "e-folder-tree.h"
+#include "e-shell-marshal.h"
+
+#include <glib/gi18n-lib.h>
+#include <libedataserver/e-util.h>
+
+#include <string.h>
+
+#define PARENT_TYPE G_TYPE_OBJECT
+static GObjectClass *parent_class = NULL;
+
+struct EStoragePrivate {
+       /* The set of folders we have in this storage.  */
+       EFolderTree *folder_tree;
+
+       /* Internal name of the storage */
+       char *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 char *path,
+                      void *data,
+                      void *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,
+                  void *data)
+{
+       EStorage *storage;
+       EStoragePrivate *priv;
+       const char *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) {
+                       char *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 char *path)
+{
+       EStoragePrivate *priv;
+
+       priv = storage->priv;
+
+       return e_folder_tree_get_subfolders (priv->folder_tree, path);
+}
+
+static EFolder *
+impl_get_folder (EStorage *storage,
+                const char *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 char *
+impl_get_name (EStorage *storage)
+{
+       return storage->priv->name;
+}
+
+static void
+impl_async_create_folder (EStorage *storage,
+                         const char *path,
+                         const char *type,
+                         EStorageResultCallback callback,
+                         void *data)
+{
+       (* callback) (storage, E_STORAGE_NOTIMPLEMENTED, data);
+}
+
+static void
+impl_async_remove_folder (EStorage *storage,
+                         const char *path,
+                         EStorageResultCallback callback,
+                         void *data)
+{
+       (* callback) (storage, E_STORAGE_NOTIMPLEMENTED, data);
+}
+
+static void
+impl_async_xfer_folder (EStorage *storage,
+                       const char *source_path,
+                       const char *destination_path,
+                       gboolean remove_source,
+                       EStorageResultCallback callback,
+                       void *data)
+{
+       (* callback) (storage, E_STORAGE_NOTIMPLEMENTED, data);
+}
+
+static void
+impl_async_open_folder (EStorage *storage,
+                       const char *path,
+                       EStorageDiscoveryCallback callback,
+                       void *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 char *parent_path, *source_path;
+       int 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 char *owner,
+                                  const char *folder_name,
+                                  EStorageDiscoveryCallback callback,
+                                  void *data)
+{
+       (* callback) (storage, E_STORAGE_NOTIMPLEMENTED, NULL, data);
+}
+
+static void
+impl_async_remove_shared_folder (EStorage *storage,
+                                const char *path,
+                                EStorageResultCallback callback,
+                                void *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,
+                             e_shell_marshal_NONE__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,
+                             e_shell_marshal_NONE__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,
+                             e_shell_marshal_NONE__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 char *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 char *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 char *path)
+{
+       g_return_val_if_fail (path != NULL, FALSE);
+
+       return *path == '/';
+}
+
+gboolean
+e_storage_path_is_relative (const char *path)
+{
+       g_return_val_if_fail (path != NULL, FALSE);
+
+       return *path != '/';
+}
+
+GList *
+e_storage_get_subfolder_paths (EStorage *storage,
+                              const char *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 char *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 char *
+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 char *path,
+                              const char *type,
+                              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 (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 char            *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 char *source_path,
+                            const char *destination_path,
+                            const gboolean remove_source,
+                            EStorageResultCallback callback,
+                            void *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) {
+               int destination_len;
+               int 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 char *path,
+                            EStorageDiscoveryCallback callback,
+                            void *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 char *owner,
+                                       const char *folder_name,
+                                       EStorageDiscoveryCallback callback,
+                                       void *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 char *owner,
+                                        const char *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 char *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));
+
+       (* E_STORAGE_GET_CLASS (storage)->async_remove_shared_folder) (storage, path, callback, data);
+}
+
+const char *
+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 char *physical_uri;
+       char *retval;
+} GetPathForPhysicalUriForeachData;
+
+static void
+get_path_for_physical_uri_foreach (EFolderTree *folder_tree,
+                                  const char *path,
+                                  void *path_data,
+                                  void *user_data)
+{
+       GetPathForPhysicalUriForeachData *foreach_data;
+       const char *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.
+ **/
+char *
+e_storage_get_path_for_physical_uri (EStorage *storage,
+                                    const char *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 char *path, const char *except)
+{
+       EStoragePrivate *priv;
+       GList *subfolders, *f;
+       const char *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);
+       }
+       // SURF : e_free_string_list (subfolders);
+       for (f = subfolders; f != NULL; f = f->next)
+               g_free (f->data);
+
+       g_list_free (subfolders);
+}
+
+gboolean
+e_storage_new_folder (EStorage *storage,
+                     const char *path,
+                     EFolder *e_folder)
+{
+       EStoragePrivate *priv;
+       char *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 char *path,
+                                 const char *message)
+{
+       EStoragePrivate *priv;
+       EFolder *parent, *pseudofolder;
+       char *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 char *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 char *path)
+{
+       EStoragePrivate *priv;
+       EFolder *folder;
+       const char *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;
+               char *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;
+}
+
+// SURF : E_MAKE_TYPE (e_storage, "EStorage", EStorage, class_init, init, PARENT_TYPE)
+G_DEFINE_TYPE (EStorage, e_storage, G_TYPE_OBJECT)
diff --git a/servers/exchange/storage/e-storage.h b/servers/exchange/storage/e-storage.h
new file mode 100644 (file)
index 0000000..64f68de
--- /dev/null
@@ -0,0 +1,213 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-storage.h
+ *
+ * Copyright (C) 2000  Ximian, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifndef _E_STORAGE_H_
+#define _E_STORAGE_H_
+
+#include <glib-object.h>
+#include "e-folder.h"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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, void *data);
+typedef void (* EStorageDiscoveryCallback) (EStorage *storage, EStorageResult result, const char *path, void *data);
+
+struct EStorage {
+       GObject parent;
+
+       EStoragePrivate *priv;
+};
+
+struct EStorageClass {
+       GObjectClass parent_class;
+
+       /* Signals.  */
+
+       void (* new_folder)     (EStorage *storage, const char *path);
+       void (* updated_folder) (EStorage *storage, const char *path);
+       void (* removed_folder) (EStorage *storage, const char *path);
+
+       /* Virtual methods.  */
+
+       GList      * (* get_subfolder_paths)     (EStorage *storage,
+                                                 const char *path);
+       EFolder    * (* get_folder)              (EStorage *storage,
+                                                 const char *path);
+       const char * (* get_name)                (EStorage *storage);
+
+       void         (* async_create_folder)  (EStorage *storage,
+                                              const char *path,
+                                              const char *type,
+                                              EStorageResultCallback callback,
+                                              void *data);
+
+       void         (* async_remove_folder)  (EStorage *storage,
+                                              const char *path,
+                                              EStorageResultCallback callback,
+                                              void *data);
+
+       void         (* async_xfer_folder)    (EStorage *storage,
+                                              const char *source_path,
+                                              const char *destination_path,
+                                              const gboolean remove_source,
+                                              EStorageResultCallback callback,
+                                              void *data);
+
+       void         (* async_open_folder)    (EStorage *storage,
+                                              const char *path,
+                                              EStorageDiscoveryCallback callback,
+                                              void *data);
+
+       gboolean     (* will_accept_folder)   (EStorage *storage,
+                                              EFolder *new_parent,
+                                              EFolder *source);
+
+       void         (* async_discover_shared_folder)  (EStorage *storage,
+                                                       const char *owner,
+                                                       const char *folder_name,
+                                                       EStorageDiscoveryCallback callback,
+                                                       void *data);
+       void         (* cancel_discover_shared_folder) (EStorage *storage,
+                                                       const char *owner,
+                                                       const char *folder_name);
+       void         (* async_remove_shared_folder)    (EStorage *storage,
+                                                       const char *path,
+                                                       EStorageResultCallback callback,
+                                                       void *data);
+};
+
+GType       e_storage_get_type                (void);
+void        e_storage_construct               (EStorage   *storage,
+                                              const char *name,
+                                              EFolder    *root_folder);
+EStorage   *e_storage_new                     (const char *name,
+                                              EFolder    *root_folder);
+
+gboolean    e_storage_path_is_relative        (const char *path);
+gboolean    e_storage_path_is_absolute        (const char *path);
+
+GList      *e_storage_get_subfolder_paths     (EStorage   *storage,
+                                              const char *path);
+EFolder    *e_storage_get_folder              (EStorage   *storage,
+                                              const char *path);
+
+const char *e_storage_get_name                (EStorage *storage);
+
+/* Folder operations.  */
+
+void  e_storage_async_create_folder  (EStorage               *storage,
+                                     const char             *path,
+                                     const char             *type,
+                                     EStorageResultCallback  callback,
+                                     void                   *data);
+void  e_storage_async_remove_folder  (EStorage               *storage,
+                                     const char             *path,
+                                     EStorageResultCallback  callback,
+                                     void                   *data);
+void  e_storage_async_xfer_folder    (EStorage               *storage,
+                                     const char             *source_path,
+                                     const char             *destination_path,
+                                     const gboolean          remove_source,
+                                     EStorageResultCallback  callback,
+                                     void                   *data);
+void  e_storage_async_open_folder    (EStorage                  *storage,
+                                     const char                *path,
+                                     EStorageDiscoveryCallback  callback,
+                                     void                      *data);
+
+const char *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 char               *owner,
+                                                    const char               *folder_name,
+                                                    EStorageDiscoveryCallback callback,
+                                                    void                     *data);
+void        e_storage_cancel_discover_shared_folder (EStorage                 *storage,
+                                                    const char               *owner,
+                                                    const char               *folder_name);
+void        e_storage_async_remove_shared_folder    (EStorage                 *storage,
+                                                    const char               *path,
+                                                    EStorageResultCallback    callback,
+                                                    void                     *data);
+
+/* Utility functions.  */
+
+char *e_storage_get_path_for_physical_uri  (EStorage   *storage,
+                                           const char *physical_uri);
+
+/* FIXME: Need to rename these.  */
+
+gboolean e_storage_new_folder             (EStorage   *storage,
+                                          const char *path,
+                                          EFolder    *folder);
+gboolean e_storage_removed_folder         (EStorage   *storage,
+                                          const char *path);
+
+gboolean e_storage_declare_has_subfolders (EStorage   *storage,
+                                          const char *path,
+                                          const char *message);
+gboolean e_storage_get_has_subfolders     (EStorage   *storage,
+                                          const char *path);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _E_STORAGE_H_ */
diff --git a/servers/exchange/storage/exchange-account.c b/servers/exchange/storage/exchange-account.c
new file mode 100644 (file)
index 0000000..0558332
--- /dev/null
@@ -0,0 +1,1989 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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-change-password.h"
+//#include "exchange-component.h"
+#include "exchange-hierarchy-webdav.h"
+#include "exchange-hierarchy-favorites.h"
+// SURF :#include "exchange-hierarchy-foreign.h"
+#include "exchange-hierarchy-gal.h"
+//#include "exchange-constants.h"
+#include "e-folder-exchange.h"
+#include "e2k-autoconfig.h"
+#include "e2k-encoding-utils.h"
+#include "e2k-kerberos.h"
+#include "e2k-marshal.h"
+#include "e2k-propnames.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+//#include <libedataserverui/e-dialog-utils.h>
+#include <libedataserverui/e-passwords.h>
+
+//#include <gal/util/e-util.h>
+#include <libgnome/gnome-util.h>
+
+#include <glade/glade-xml.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtklabel.h>
+
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define d(x) x
+#define ADS_UF_DONT_EXPIRE_PASSWORD 0x10000
+#define ONE_HUNDRED_NANOSECOND 0.000000100
+#define SECONDS_IN_DAY 86400
+#define PASSWD_EXPIRY_NOTIFICATION_PERIOD 7
+#define FILENAME CONNECTOR_GLADEDIR "/exchange-passwd-expiry.glade"
+#define ROOTNODE "passwd_exp_dialog"
+
+struct _ExchangeAccountPrivate {
+       E2kContext *ctx;
+       E2kGlobalCatalog *gc;
+       GHashTable *standard_uris;
+
+       GMutex *connect_lock;
+       gboolean connecting, connected, account_online;
+
+       GPtrArray *hierarchies;
+       GHashTable *hierarchies_by_folder, *foreign_hierarchies;
+       ExchangeHierarchy *favorites_hierarchy;
+       GHashTable *folders;
+       char *uri_authority, *http_uri_schema;
+       gboolean uris_use_email, offline_sync;
+
+       char *identity_name, *identity_email, *source_uri, *password_key;
+       char *username, *password, *windows_domain, *nt_domain, *ad_server;
+       char *owa_url;
+       E2kAutoconfigAuthPref auth_pref;
+       int ad_limit, passwd_exp_warn_period;
+
+       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,
+                             e2k_marshal_NONE__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,
+                             e2k_marshal_NONE__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,
+                             e2k_marshal_NONE__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);
+       account->priv->discover_data_lock = g_mutex_new ();
+       account->priv->account_online = TRUE;
+       account->priv->nt_domain = NULL;
+}
+
+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);
+       int 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->hierarchies_by_folder) {
+               g_hash_table_destroy (account->priv->hierarchies_by_folder);
+               account->priv->hierarchies_by_folder = 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;
+       }
+
+       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_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_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)
+{
+       int i;
+
+       g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+       for (i = 0; i < account->priv->hierarchies->len; i++)
+               exchange_hierarchy_rescan (account->priv->hierarchies->pdata[i]);
+}
+
+/*
+ * ExchangeHierarchy folder creation/deletion/xfer notifications
+ */
+
+static void
+hierarchy_new_folder (ExchangeHierarchy *hier, EFolder *folder,
+                     ExchangeAccount *account)
+{
+       int table_updated = 0;
+       const char *permanent_uri =
+               e_folder_exchange_get_permanent_uri (folder);
+
+       /* This makes the cleanup easier. We just unref it each time
+        * we find it in account->priv->folders.
+        */
+       if (!g_hash_table_lookup (account->priv->folders, 
+                               e_folder_exchange_get_path (folder))) {
+               /* 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,
+                                    (char *)e_folder_exchange_get_path (folder),
+                                    folder);
+               table_updated = 1;
+       }
+       if (!g_hash_table_lookup (account->priv->folders, 
+                               e_folder_get_physical_uri (folder))) {
+               /* 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,
+                                    (char *)e_folder_get_physical_uri (folder),
+                                    folder);
+               table_updated = 1;
+       }
+       if (!g_hash_table_lookup (account->priv->folders, 
+                               e_folder_exchange_get_internal_uri (folder))) {
+               /* 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,
+                                    (char *)e_folder_exchange_get_internal_uri (folder),
+                                    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,
+                                    (char *)permanent_uri,
+                                    folder);
+               table_updated = 1;
+       }
+
+       if (table_updated)
+       {
+               g_hash_table_insert (account->priv->hierarchies_by_folder, 
+                                       folder, hier);
+
+               g_signal_emit (account, signals[NEW_FOLDER], 0, folder);
+       }
+}
+
+static void
+hierarchy_removed_folder (ExchangeHierarchy *hier, EFolder *folder,
+                         ExchangeAccount *account)
+{
+       if (!g_hash_table_lookup (account->priv->folders, 
+                                       e_folder_exchange_get_path (folder)))
+               return;
+
+       g_hash_table_remove (account->priv->folders, 
+                                       e_folder_exchange_get_path (folder));
+       g_hash_table_remove (account->priv->folders, 
+                                       e_folder_get_physical_uri (folder));
+       /* Dont remove this for favorites, as the internal_uri is shared 
+               by the public folder as well */
+       if (hier->type != EXCHANGE_HIERARCHY_FAVORITES) {
+               g_hash_table_remove (account->priv->folders, 
+                                       e_folder_exchange_get_internal_uri (folder));
+       }
+       g_hash_table_remove (account->priv->hierarchies_by_folder, folder);
+       g_signal_emit (account, signals[REMOVED_FOLDER], 0, folder);
+
+       if (folder == hier->toplevel)
+               remove_hierarchy (account, hier);
+
+       g_object_unref (folder);
+       g_object_unref (folder);
+       if (hier->type != EXCHANGE_HIERARCHY_FAVORITES) {
+               g_object_unref (folder);
+       }
+}
+
+static gboolean
+get_folder (ExchangeAccount *account, const char *path,
+           EFolder **folder, ExchangeHierarchy **hier)
+{
+       *folder = g_hash_table_lookup (account->priv->folders, path);
+       if (!*folder)
+               return FALSE;
+       *hier = g_hash_table_lookup (account->priv->hierarchies_by_folder,
+                                    *folder);
+       if (!*hier)
+               return FALSE;
+       return TRUE;
+}
+
+static gboolean
+get_parent_and_name (ExchangeAccount *account, const char **path,
+                    EFolder **parent, ExchangeHierarchy **hier)
+{
+       char *name, *parent_path;
+
+       name = strrchr (*path + 1, '/');
+       if (!name)
+               return FALSE;
+
+       parent_path = g_strndup (*path, name - *path);
+       *parent = g_hash_table_lookup (account->priv->folders, parent_path);
+       g_free (parent_path);
+
+       if (!*parent)
+               return FALSE;
+
+       *hier = g_hash_table_lookup (account->priv->hierarchies_by_folder,
+                                    *parent);
+       if (!*hier)
+               return FALSE;
+
+       *path = name + 1;
+       return TRUE;
+}
+
+ExchangeAccountFolderResult
+exchange_account_create_folder (ExchangeAccount *account,
+                               const char *path, const char *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);
+}
+
+ExchangeAccountFolderResult
+exchange_account_remove_folder (ExchangeAccount *account, const char *path)
+{
+       ExchangeHierarchy *hier;
+       EFolder *folder;
+       const char *name;
+
+       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;
+       
+       name = e_folder_get_name (folder);
+       if (exchange_account_get_standard_uri (account, name))
+               return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION;
+
+       return exchange_hierarchy_remove_folder (hier, folder);
+}
+
+ExchangeAccountFolderResult
+exchange_account_xfer_folder (ExchangeAccount *account,
+                             const char *source_path,
+                             const char *dest_path,
+                             gboolean remove_source)
+{
+       EFolder *source, *dest_parent;
+       ExchangeHierarchy *source_hier, *dest_hier;
+       const char *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)
+{
+       int 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,
+                            (char *)hier->owner_email, hier);
+       setup_hierarchy (account, hier);
+}
+
+struct discover_data {
+       const char *user, *folder_name;
+       E2kOperation op;
+};
+
+static ExchangeHierarchy *
+get_hierarchy_for (ExchangeAccount *account, E2kGlobalCatalogEntry *entry)
+{
+       ExchangeHierarchy *hier;
+       char *hierarchy_name, *source;
+       char *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);
+
+#if 0
+SURF : This should move to plugins.
+       hier = exchange_hierarchy_foreign_new (account, hierarchy_name,
+                                              physical_uri_prefix,
+                                              internal_uri_prefix,
+                                              entry->display_name,
+                                              entry->email, source);
+#endif
+       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 char *user,
+                                        const char *folder_name,
+                                        EFolder **folder)
+{
+       struct discover_data dd;
+       ExchangeHierarchy *hier;
+       char *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_UNSUPPORTED_OPERATION;
+
+       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);
+               // SURF : return exchange_hierarchy_foreign_add_folder (hier, folder_name, folder);
+               return EXCHANGE_ACCOUNT_FOLDER_OK;
+       }
+
+       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) {
+               return (status == E2K_GLOBAL_CATALOG_ERROR) ?
+                       EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR :
+                       EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
+       }
+
+       hier = get_hierarchy_for (account, entry);
+       // SURF : return exchange_hierarchy_foreign_add_folder (hier, folder_name, folder);
+       return EXCHANGE_ACCOUNT_FOLDER_OK;
+}
+
+void
+exchange_account_cancel_discover_shared_folder (ExchangeAccount *account,
+                                               const char *user,
+                                               const char *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 char *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 0
+SURF :
+       if (!EXCHANGE_IS_HIERARCHY_FOREIGN (hier))
+               return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION;
+#endif
+
+       return exchange_hierarchy_remove_folder (hier, folder);
+}
+
+ExchangeAccountFolderResult
+exchange_account_open_folder (ExchangeAccount *account, const char *path)
+{
+       ExchangeHierarchy *hier;
+       EFolder *folder;
+       int offline;
+
+       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;
+
+       exchange_account_is_offline (account, &offline);
+       if (offline == 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, (offline == OFFLINE_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);
+}
+
+static void
+context_redirect (E2kContext *ctx, E2kHTTPStatus status,
+                 const char *old_uri, const char *new_uri,
+                 ExchangeAccount *account)
+{
+       EFolder *folder;
+
+       folder = g_hash_table_lookup (account->priv->folders, old_uri);
+       if (!folder)
+               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,
+                            (char *)e_folder_exchange_get_internal_uri (folder),
+                            folder);
+}
+
+static void
+set_sf_prop (const char *propname, E2kPropType type,
+            gpointer href, gpointer user_data)
+{
+       ExchangeAccount *account = user_data;
+
+       propname = strrchr (propname, ':');
+       if (!propname++)
+               return;
+
+       g_hash_table_insert (account->priv->standard_uris,
+                            g_strdup (propname),
+                            e2k_strdup_with_trailing_slash (href));
+}
+
+static const char *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 int 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;
+}
+
+static gboolean
+get_password (ExchangeAccount *account, E2kAutoconfig *ac, const char *errmsg)
+{
+       char *password;
+       gboolean remember, oldremember;
+
+       if (*errmsg)
+               e_passwords_forget_password ("Exchange", account->priv->password_key);
+
+       password = e_passwords_get_password ("Exchange", account->priv->password_key);
+
+       // SURF : if (exchange_component_is_interactive (global_exchange_component)) {
+               if (!password) {
+                       char *prompt;
+
+                       prompt = g_strdup_printf (_("%sEnter password for %s"),
+                                                 errmsg, 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);
+               }
+       // SURF : }
+
+       if (password) {
+               e2k_autoconfig_set_password (ac, password);
+               memset (password, 0, strlen (password));
+               return TRUE;
+       } else
+               return FALSE;
+}
+
+/* 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.
+ */
+static gboolean
+is_password_expired (ExchangeAccount *account, E2kAutoconfig *ac)
+{
+       char *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);
+}
+
+#if 0
+static void
+change_passwd_cb (GtkWidget *button, ExchangeAccount *account)
+{
+       char *current_passwd, *new_passwd;
+
+       gtk_widget_hide (gtk_widget_get_toplevel(button));
+       current_passwd = exchange_account_get_password (account);
+       new_passwd = exchange_get_new_password (current_passwd, TRUE);
+       exchange_account_set_password (account, current_passwd, new_passwd);
+       g_free (current_passwd);
+       g_free (new_passwd);
+}
+#endif
+
+static void
+display_passwd_expiry_message (int max_passwd_age, ExchangeAccount *account)
+{
+       GladeXML *xml;
+       GtkWidget *top_widget, *change_passwd_button;
+       GtkResponseType response;
+       GtkLabel *warning_msg_label;
+       char *passwd_expiry_msg = 
+               g_strdup_printf ("Your password will expire in next %d days\n",
+                                 max_passwd_age);
+
+       xml = glade_xml_new (FILENAME, ROOTNODE, NULL);
+       g_return_if_fail (xml != NULL);
+       top_widget = glade_xml_get_widget (xml, ROOTNODE);
+       g_return_if_fail (top_widget != NULL);
+
+       warning_msg_label = GTK_LABEL (glade_xml_get_widget (xml, 
+                                               "passwd_exp_label"));
+       gtk_label_set_text (warning_msg_label, passwd_expiry_msg);
+
+       change_passwd_button = glade_xml_get_widget (xml, 
+                                               "change_passwd_button");
+       gtk_widget_set_sensitive (change_passwd_button, TRUE);
+/*
+       g_signal_connect (change_passwd_button, 
+                         "clicked", 
+                         G_CALLBACK (change_passwd_cb), 
+                         account);
+*/
+       response = gtk_dialog_run (GTK_DIALOG (top_widget));
+
+       gtk_widget_destroy (top_widget);
+       g_object_unref (xml);
+       g_free (passwd_expiry_msg);
+}
+
+static void
+find_passwd_exp_period (ExchangeAccount *account, E2kGlobalCatalogEntry *entry)
+{
+       double max_pwd_age = 0;
+       int 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;
+
+       /* 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;
+       
+       if (entry->user_account_control & ADS_UF_DONT_EXPIRE_PASSWORD) 
+               return;         /* 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) {
+                       display_passwd_expiry_message (max_pwd_age_days, 
+                                                      account);
+               }
+       } 
+}
+
+char *
+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);
+}
+
+#ifdef HAVE_KRB5
+void
+exchange_account_set_password (ExchangeAccount *account, char *old_pass, char *new_pass)
+{
+       E2kKerberosResult result;
+       char *domain;
+
+       g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+       g_return_if_fail (old_pass != NULL);
+       g_return_if_fail (new_pass != NULL);
+
+       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 */
+               // SURF : e_notice (NULL, GTK_MESSAGE_ERROR, _("Cannot change password due to configuration problems"));
+               return;
+       }
+
+       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:
+               // SURF : e_notice (NULL, GTK_MESSAGE_ERROR, _("Server rejected password because it is too weak.\nTry again with a different password."));
+               break;
+
+       case E2K_KERBEROS_FAILED:
+       default:
+               // SURF : e_notice (NULL, GTK_MESSAGE_ERROR, _("Could not change password"));
+               break;
+       }
+}
+#endif
+
+/**
+ * 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);
+
+       if (account->priv->ctx) {
+               g_object_unref (account->priv->ctx);
+               account->priv->ctx = NULL;
+       }
+
+       account->priv->account_online = FALSE;
+       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)
+{
+       E2kContext *ctx;
+       g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), FALSE);
+
+       if (!account->priv->account_online) {
+               ctx = exchange_account_connect (account);
+               return ctx ? TRUE : FALSE;
+       } else {
+               return TRUE;
+       }
+}
+#if 0
+SURF :
+/**
+ * exchange_account_is_offline:
+ * @account: an #ExchangeAccount
+ *
+ * Return value: Returns TRUE if account is offline
+ **/
+void
+exchange_account_is_offline (ExchangeAccount *account, int *state)
+{
+       *state = UNSUPPORTED_MODE;
+
+       g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
+
+       exchange_component_is_offline (global_exchange_component, state);
+}
+#endif
+void
+exchange_account_is_offline (ExchangeAccount *account, int *state)
+{
+       // SURF : Dummy
+       *state = ONLINE_MODE;
+}
+
+// SURF : Picked this from gal/util/e-util.c
+/* This only makes a filename safe for usage as a filename.  It still may have shell meta-characters in it. */
+static void
+e_filename_make_safe (gchar *string)
+{
+       gchar *p, *ts;
+       gunichar c;
+       
+       g_return_if_fail (string != NULL);
+       p = string;
+
+       while(p && *p) {
+               c = g_utf8_get_char (p);
+               ts = p;
+               p = g_utf8_next_char (p);
+               if (!g_unichar_isprint(c) || ( c < 0xff && strchr (" /'\"`&();|<>$%{}!", c&0xff ))) {
+                       while (ts<p)    
+                               *ts++ = '_';
+               }
+       }
+}
+
+
+static gboolean
+setup_account_hierarchies (ExchangeAccount *account)
+{
+       ExchangeHierarchy *hier, *personal_hier;
+       ExchangeAccountFolderResult fresult;
+       char *phys_uri_prefix, *dir;
+       DIR *d;
+       struct dirent *dent;
+       int offline;
+
+       exchange_account_is_offline (account, &offline);
+
+       if (offline == UNSUPPORTED_MODE)
+               return FALSE;
+
+       /* 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);
+       personal_hier = hier;
+
+       /* 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 */
+#if 0
+SURF :
+       d = opendir (account->storage_dir);
+       if (d) {
+               while ((dent = readdir (d))) {
+                       if (!strchr (dent->d_name, '@'))
+                               continue;
+                       dir = g_strdup_printf ("%s/%s", account->storage_dir,
+                                              dent->d_name);
+                       hier = exchange_hierarchy_foreign_new_from_dir (account, dir);
+                       g_free (dir);
+                       if (!hier)
+                               continue;
+
+                       setup_hierarchy_foreign (account, hier);
+               }
+               closedir (d);
+       }
+#endif
+
+       /* Scan the personal and favorite folders so we can resolve references
+        * to the Calendar, Contacts, etc even if the tree isn't
+        * opened.
+        */
+       fresult = exchange_hierarchy_scan_subtree (personal_hier,
+                                                  personal_hier->toplevel,
+                                                  (offline == OFFLINE_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,
+               (offline == OFFLINE_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)
+{
+       E2kAutoconfig *ac;
+       E2kAutoconfigResult result;
+       E2kHTTPStatus status;
+       char *errmsg = "";
+       gboolean redirected = FALSE;
+       E2kResult *results;
+       int nresults;
+       GByteArray *entryid;
+       const char *timezone;
+       char *old_password, *new_password;
+       E2kGlobalCatalogStatus gcstatus;
+       E2kGlobalCatalogEntry *entry;
+       E2kOperation gcop;
+       const char *quota_msg = NULL;
+       char *user_name = NULL;
+       int offline;
+
+       g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+       exchange_account_is_offline (account, &offline);
+       g_mutex_lock (account->priv->connect_lock);
+
+       if ((account->priv->connecting) || (offline == OFFLINE_MODE)){
+               g_mutex_unlock (account->priv->connect_lock);
+               if (offline == OFFLINE_MODE) {
+                       setup_account_hierarchies (account);
+                       // SURF : e_notice (NULL, GTK_MESSAGE_ERROR, _("Exchange Account is offline. Cannot display folders\n"));
+               }
+               return NULL;
+       } else if (account->priv->ctx) {
+               g_mutex_unlock (account->priv->connect_lock);
+               return account->priv->ctx;
+       }
+
+       account->priv->connecting = TRUE;
+       g_mutex_unlock (account->priv->connect_lock);
+
+       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);
+
+ try_password_again:
+       if (!get_password (account, ac, errmsg)) {
+               account->priv->connecting = FALSE;
+               return NULL;
+       }
+
+ 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)) {
+                       old_password = exchange_account_get_password (account);
+                       //new_password = exchange_get_new_password (old_password, 0);
+
+                       if (new_password) {
+                               exchange_account_set_password (account, old_password, new_password);
+                               e2k_autoconfig_set_password (ac, new_password);
+                               g_free (old_password);
+                               g_free (new_password);
+                               goto try_connect_again;
+                       }
+
+                       g_free (old_password);
+                       result = E2K_AUTOCONFIG_CANCELLED;
+               }
+#endif
+               switch (result) {
+               case E2K_AUTOCONFIG_AUTH_ERROR:
+                       errmsg = _("Could not authenticate to server. "
+                                  "(Password incorrect?)\n\n");
+                       goto try_password_again;
+
+               case E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN:
+                       errmsg = _("Could not authenticate to server."
+                                  "\n\nThis probably means that your "
+                                  "server requires you\nto specify "
+                                  "the Windows domain name as part "
+                                  "of your\nusername (eg, "
+                                  "\"DOMAIN\\user\").\n\nOr you "
+                                  "might have just typed your "
+                                  "password wrong.\n\n");
+                       goto try_password_again;
+
+               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;
+
+               // SURF : if (!exchange_component_is_interactive (global_exchange_component))
+                       // SURF : return NULL;
+
+               switch (result) {
+               case E2K_AUTOCONFIG_REDIRECT:
+               case E2K_AUTOCONFIG_TRY_SSL:
+                       errmsg = g_strdup_printf (
+                               _("Mailbox for %s is not on this server."),
+                               account->priv->username);
+                       break;
+               case E2K_AUTOCONFIG_EXCHANGE_5_5:
+                       errmsg = g_strdup (
+                               _("The server '%s' is running Exchange 5.5 "
+                                 "and is\ntherefore not compatible with "
+                                 "Ximian Connector"));
+                       break;
+               case E2K_AUTOCONFIG_NOT_EXCHANGE:
+               case E2K_AUTOCONFIG_NO_OWA:
+                       errmsg = g_strdup_printf (
+                               _("Could not find Exchange Web Storage System "
+                                 "at %s.\nIf OWA is running on a different "
+                                 "path, you must specify that in the\n"
+                                 "account configuration dialog."),
+                               account->home_uri);
+                       break;
+               case E2K_AUTOCONFIG_NO_MAILBOX:
+                       errmsg = g_strdup_printf (
+                               _("No mailbox for user %s on %s.\n"),
+                               account->priv->username,
+                               account->exchange_server);
+                       break;
+               case E2K_AUTOCONFIG_CANT_RESOLVE:
+                       errmsg = g_strdup_printf (
+                               _("Could not connect to server %s: %s"),
+                               account->exchange_server,
+                               _("Could not resolve hostname"));
+                       break;
+               case E2K_AUTOCONFIG_CANT_CONNECT:
+                       errmsg = g_strdup_printf (
+                               _("Could not connect to server %s: %s"),
+                               account->exchange_server,
+                               _("Network error"));
+                       break;
+               case E2K_AUTOCONFIG_CANCELLED:
+                       return NULL;
+               default:
+                       errmsg = g_strdup_printf (
+                               _("Could not connect to server %s: %s"),
+                               account->exchange_server,
+                               _("Unknown error"));
+                       break;
+               }
+
+               // SURF : e_notice (NULL, GTK_MESSAGE_ERROR, errmsg);
+               g_free (errmsg);
+               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;
+               return NULL;
+       }
+
+       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));
+
+               timezone = e2k_properties_get_prop (results[0].props, E2K_PR_EXCHANGE_TIMEZONE);
+               if (timezone)
+                       account->default_timezone = g_strdup (timezone);
+       }
+
+       if (!setup_account_hierarchies (account)) 
+               return NULL;
+
+       /* Find the password expiery peripod and display warning */
+       find_passwd_exp_period(account, entry);
+       
+       /* Check for quota warnings */
+       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: quota warnings are not yet marked for translation!! */
+       /* FIXME: warning message should have quota limit value and optionally current
+        * usage 
+        */
+       if (gcstatus == E2K_GLOBAL_CATALOG_OK) {
+
+               if (entry->quota_norecv && account->mbox_size >= entry->quota_norecv) {
+                       quota_msg = g_strdup_printf ("You have exceeded your quota for storing mails on this server. Your current usage is : %d . You will not be able to either send or recieve mails now\n", entry->quota_norecv);
+               } else if (entry->quota_nosend && account->mbox_size >= entry->quota_nosend) {
+                       quota_msg = g_strdup_printf ("You are nearing your quota available for storing mails on this server. Your current usage is : %d . You will not be able to send mails till you clear up some space by deleting some mails.\n", entry->quota_nosend);
+               } else if (entry->quota_warn && account->mbox_size >= entry->quota_warn) {
+                       quota_msg = g_strdup_printf ("You are nearing your quota available for storing mails on this server. Your current usage is : %d . Try to clear up some space by deleting some mails.\n", entry->quota_warn);
+               }
+               
+       // SURF :       if (quota_msg)
+                       // SURF : e_notice (NULL, GTK_MESSAGE_INFO, quota_msg);
+       }
+       
+       account->priv->connected = TRUE;
+       account->priv->account_online = TRUE;
+       account->priv->connecting = FALSE;
+
+       g_signal_connect (account->priv->ctx, "redirect",
+                         G_CALLBACK (context_redirect), account);
+
+       g_signal_emit (account, signals[CONNECTED], 0, account->priv->ctx);
+       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, int *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_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 char *
+exchange_account_get_standard_uri (ExchangeAccount *account, const char *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.
+ **/
+char *
+exchange_account_get_standard_uri_for (ExchangeAccount *account,
+                                      const char *home_uri,
+                                      const char *std_uri_prop)
+{
+       char *foreign_uri, *prop;
+       E2kHTTPStatus status;
+       E2kResult *results;
+       int 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.
+ **/
+char *
+exchange_account_get_foreign_uri (ExchangeAccount *account,
+                                 E2kGlobalCatalogEntry *entry,
+                                 const char *std_uri_prop)
+{
+       char *home_uri, *foreign_uri;
+
+       g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+
+       if (account->priv->uris_use_email) {
+               char *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;
+}
+
+/**
+ * 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 char *path_or_uri)
+{
+       g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
+       printf ("path or uri : %s\n", path_or_uri);
+       
+       return g_hash_table_lookup (account->priv->folders, path_or_uri);
+}
+
+static int
+folder_comparator (const void *a, const void *b)
+{
+       EFolder **fa = (EFolder **)a;
+       EFolder **fb = (EFolder **)b;
+
+       return strcmp (e_folder_exchange_get_path (*fa),
+                      e_folder_exchange_get_path (*fb));
+}
+
+static void
+add_folder (gpointer key, gpointer value, gpointer folders)
+{
+       EFolder *folder = value;
+
+       /* 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);
+}
+
+/**
+ * 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_hash_table_foreach (account->priv->folders, add_folder, folders);
+
+       qsort (folders->pdata, folders->len,
+              sizeof (EFolder *), folder_comparator);
+
+       return folders;
+}      
+
+char *
+exchange_account_get_username (ExchangeAccount *account)
+{
+       g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 
+                               EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
+       
+       return account->priv->username;
+}
+
+/**
+ * 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;
+       char *enc_user, *mailbox;
+       const char *param, *proto="http", *owa_path, *pf_server, *owa_url; 
+       const char *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);
+       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, "@/;:");
+       account->priv->uri_authority = g_strdup_printf ("%s@%s", enc_user,
+                                                       uri->host);
+       g_free (enc_user);
+
+       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.
+        */
+       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);
+       }
+       
+       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;
+}
diff --git a/servers/exchange/storage/exchange-account.h b/servers/exchange/storage/exchange-account.h
new file mode 100644 (file)
index 0000000..fe47c01
--- /dev/null
@@ -0,0 +1,146 @@
+/* -*- 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>
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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.
+        */
+       char *account_name, *account_filename, *storage_dir;
+       char *exchange_server, *home_uri, *public_uri;
+       char *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 *);
+};
+
+#if 0
+enum {
+       UNSUPPORTED_MODE = 0,
+        OFFLINE_MODE,
+        ONLINE_MODE
+};
+#endif
+
+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);
+
+const char            *exchange_account_get_standard_uri     (ExchangeAccount                *acct,
+                                                             const char                     *item);
+
+char                  *exchange_account_get_standard_uri_for (ExchangeAccount                *acct,
+                                                             const char                     *home_uri,
+                                                             const char                     *std_uri_prop);
+char                  *exchange_account_get_foreign_uri      (ExchangeAccount                *acct,
+                                                             E2kGlobalCatalogEntry          *entry,
+                                                             const char                     *std_uri_prop);
+
+E2kContext            *exchange_account_connect              (ExchangeAccount                *acct);
+
+EFolder               *exchange_account_get_folder           (ExchangeAccount                *acct,
+                                                             const char                     *path_or_uri);
+GPtrArray             *exchange_account_get_folders          (ExchangeAccount                *acct);
+
+void                   exchange_account_rescan_tree          (ExchangeAccount                *acct);
+
+char                  *exchange_account_get_password (ExchangeAccount *acct);
+
+void                  exchange_account_set_password (ExchangeAccount *acct,
+                                                       char *old_password,
+                                                       char *new_password);
+void                 exchange_account_forget_password (ExchangeAccount *acct);
+
+gboolean                exchange_account_set_offline (ExchangeAccount *account);
+
+gboolean                exchange_account_set_online (ExchangeAccount *account);
+
+void            exchange_account_is_offline (ExchangeAccount *account, int *mode);
+
+void           exchange_account_is_offline_sync_set (ExchangeAccount *account, int *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_GENERIC_ERROR
+} ExchangeAccountFolderResult;
+
+ExchangeAccountFolderResult exchange_account_create_folder (ExchangeAccount *account,
+                                                           const char      *path,
+                                                           const char      *type);
+ExchangeAccountFolderResult exchange_account_remove_folder (ExchangeAccount *account,
+                                                           const char      *path);
+ExchangeAccountFolderResult exchange_account_xfer_folder   (ExchangeAccount *account,
+                                                           const char      *source_path,
+                                                           const char      *dest_path,
+                                                           gboolean         remove_source);
+ExchangeAccountFolderResult exchange_account_open_folder   (ExchangeAccount *account,
+                                                           const char      *path);
+
+ExchangeAccountFolderResult exchange_account_discover_shared_folder  (ExchangeAccount *account,
+                                                                     const char      *user,
+                                                                     const char      *folder_name,
+                                                                     EFolder        **folder);
+void                  exchange_account_cancel_discover_shared_folder (ExchangeAccount *account,
+                                                                     const char      *user,
+                                                                     const char      *folder);
+ExchangeAccountFolderResult exchange_account_remove_shared_folder    (ExchangeAccount *account,
+                                                                     const char      *path);
+
+ExchangeAccountFolderResult exchange_account_add_favorite            (ExchangeAccount *account,
+                                                                     EFolder         *folder);
+ExchangeAccountFolderResult exchange_account_remove_favorite         (ExchangeAccount *account,
+                                                                     EFolder         *folder);
+
+char * exchange_account_get_username (ExchangeAccount *account);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_ACCOUNT_H__ */
diff --git a/servers/exchange/storage/exchange-constants.h b/servers/exchange/storage/exchange-constants.h
new file mode 100644 (file)
index 0000000..1492636
--- /dev/null
@@ -0,0 +1,27 @@
+/* -*- 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;
+
+
+#define EXCHANGE_COMPONENT_FACTORY_IID  "OAFIID:GNOME_Evolution_Exchange_Component_Factory:" BASE_VERSION
+#define EXCHANGE_COMPONENT_IID         "OAFIID:GNOME_Evolution_Exchange_Component:" BASE_VERSION
+#define EXCHANGE_CALENDAR_FACTORY_ID   "OAFIID:GNOME_Evolution_Exchange_Connector_CalFactory:" BASE_VERSION
+#define EXCHANGE_ADDRESSBOOK_FACTORY_ID        "OAFIID:GNOME_Evolution_Exchange_Connector_BookFactory:" BASE_VERSION
+#define EXCHANGE_AUTOCONFIG_WIZARD_ID  "OAFIID:GNOME_Evolution_Exchange_Connector_Startup_Wizard:" BASE_VERSION
+
+#endif
diff --git a/servers/exchange/storage/exchange-folder-size.c b/servers/exchange/storage/exchange-folder-size.c
new file mode 100644 (file)
index 0000000..1f82ca5
--- /dev/null
@@ -0,0 +1,343 @@
+/* -*- 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* ExchangeFolderSize: Display the folder tree with the folder sizes */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+// SURF : #include <e-util/e-dialog-utils.h>
+#include <glade/glade-xml.h>
+#include <gtk/gtkbox.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkliststore.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtktreeview.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;
+
+typedef struct {
+        char *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_fsize_table (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_fsize_table, 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
+class_init (GObjectClass *object_class)
+{
+       parent_class = g_type_class_ref (PARENT_TYPE);
+
+       /* override virtual methods */
+       object_class->dispose = dispose;
+       object_class->finalize = finalize;
+
+}
+
+static void
+init (GObject *object)
+{
+       ExchangeFolderSize *fsize = EXCHANGE_FOLDER_SIZE (object);
+
+       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);
+}
+
+E2K_MAKE_TYPE (exchange_folder_size, ExchangeFolderSize, class_init, init, PARENT_TYPE)
+
+/**
+ * exchange_folder_size_new:
+ * @display_name: the delegate's (UTF8) display name
+ *
+ * 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 char *folder_name,
+                               gdouble folder_size)
+{
+       folder_info *f_info, *cached_info;
+       ExchangeFolderSizePrivate *priv;
+       GHashTable *folder_size_table;
+       GtkTreeRowReference *row;
+       GtkTreeIter iter;
+       GtkTreePath *path;
+
+       g_return_if_fail (EXCHANGE_IS_FOLDER_SIZE (fsize));
+
+       priv = fsize->priv;
+       folder_size_table = priv->table;
+
+       cached_info = g_hash_table_lookup (folder_size_table, 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_size_table, 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 char *folder_name)
+{
+       ExchangeFolderSizePrivate *priv;
+       GHashTable *folder_size_table;
+       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_size_table = priv->table;
+
+       cached_info = g_hash_table_lookup (folder_size_table, 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_size_table, 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 char *folder_name)
+{
+       ExchangeFolderSizePrivate *priv;
+       GHashTable *folder_size_table;
+       folder_info *cached_info;
+
+       g_return_val_if_fail (EXCHANGE_IS_FOLDER_SIZE (fsize), -1);
+       
+       priv = fsize->priv;
+       folder_size_table = priv->table;
+
+       cached_info = g_hash_table_lookup (folder_size_table, folder_name);
+       if (cached_info)  {
+               return cached_info->folder_size;
+       }
+       return -1;
+}
+
+static void
+format_size_func (GtkTreeViewColumn *col,
+                  GtkCellRenderer   *renderer,
+                  GtkTreeModel      *model,
+                  GtkTreeIter       *iter,
+                 gpointer           user_data)
+{
+       GtkCellRendererText *cell = (GtkCellRendererText *)renderer;
+       gdouble folder_size;
+       char * new_text;
+       
+       gtk_tree_model_get(model, iter, COLUMN_SIZE, &folder_size, -1);
+       
+       if (folder_size)
+               new_text = g_strdup_printf ("%.2f", folder_size);
+       else
+               new_text = g_strdup ("0");
+
+       g_object_set (cell, "text", new_text, NULL);
+       g_free (new_text);
+}
+
+static void
+parent_destroyed (gpointer dialog, GObject *ex_parent)
+{
+       gtk_dialog_response (dialog, GTK_RESPONSE_CANCEL);
+}
+
+void
+exchange_folder_size_display (EFolder *folder, GtkWidget *parent)
+{
+        ExchangeFolderSizePrivate *priv;
+       ExchangeFolderSize *fsize;
+        ExchangeHierarchy *hier;
+        GtkTreeViewColumn *column;
+       GtkTreeSortable *sortable;
+       GtkCellRenderer *cell;
+        GHashTable *folder_size_table;
+        GladeXML *xml;
+        GtkWidget *dialog, *table;
+       GList *l;
+       char *col_name;
+        int response;
+
+        g_return_if_fail (GTK_IS_WIDGET (parent));
+
+        hier = e_folder_exchange_get_hierarchy (folder);
+       if (!hier)
+               return;
+       /* FIXME: This should be a more generic query and not just 
+       specifically for webdav */
+        fsize = exchange_hierarchy_webdav_get_folder_size (EXCHANGE_HIERARCHY_WEBDAV (hier));
+       if (!fsize)
+               return;
+       priv = fsize->priv;
+       folder_size_table = priv->table;
+
+       if (!g_hash_table_size (folder_size_table))
+               return;
+
+        xml = glade_xml_new (CONNECTOR_GLADEDIR "/exchange-folder-tree.glade", NULL, NULL);
+        g_return_if_fail (xml != NULL);
+        dialog = glade_xml_get_widget (xml, "folder_tree");
+        table = glade_xml_get_widget (xml, "folder_treeview");
+
+        // SURF : e_dialog_set_transient_for (GTK_WINDOW (dialog), parent);
+       /* fsize->parent = parent; */
+        g_object_weak_ref (G_OBJECT (parent), parent_destroyed, dialog);
+
+        /* Set up the table */
+       sortable = GTK_TREE_SORTABLE (priv->model);
+       gtk_tree_sortable_set_sort_column_id (sortable, COLUMN_SIZE, GTK_SORT_DESCENDING);
+
+        column = gtk_tree_view_column_new_with_attributes (
+                _("Folder Name"), gtk_cell_renderer_text_new (), "text", COLUMN_NAME, NULL);
+        gtk_tree_view_append_column (GTK_TREE_VIEW (table),
+                                     column);
+
+       col_name = g_strdup_printf ("%s (KB)", _("Folder Size"));
+        column = gtk_tree_view_column_new_with_attributes (
+                col_name, gtk_cell_renderer_text_new (), "text", COLUMN_SIZE, NULL);
+       g_free (col_name);
+       
+       l = gtk_tree_view_column_get_cell_renderers (column);
+       cell = (GtkCellRenderer *)l->data;
+       gtk_tree_view_column_set_cell_data_func (column, cell, format_size_func, NULL, NULL );
+       g_list_free (l);
+
+        gtk_tree_view_append_column (GTK_TREE_VIEW (table),
+                                     column);
+        gtk_tree_view_set_model (GTK_TREE_VIEW (table),
+                                 GTK_TREE_MODEL (priv->model));
+       response = gtk_dialog_run (GTK_DIALOG (dialog));
+        gtk_widget_destroy (dialog);
+        g_object_unref (xml);
+}
diff --git a/servers/exchange/storage/exchange-folder-size.h b/servers/exchange/storage/exchange-folder-size.h
new file mode 100644 (file)
index 0000000..13b1e6b
--- /dev/null
@@ -0,0 +1,55 @@
+/* -*- 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/gtkwidget.h>
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char *folder_name,
+                                               gdouble folder_size);
+void exchange_folder_size_remove (ExchangeFolderSize *fsize, const char *folder_name);
+
+gdouble exchange_folder_size_get (ExchangeFolderSize *fsize, const char *folder_name);
+
+void exchange_folder_size_display (EFolder *folder, GtkWidget *parent);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_FOLDER_SIZE_H__ */
diff --git a/servers/exchange/storage/exchange-hierarchy-favorites.c b/servers/exchange/storage/exchange-hierarchy-favorites.c
new file mode 100644 (file)
index 0000000..21eb16f
--- /dev/null
@@ -0,0 +1,322 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 "exchange-constants.h"
+#include "e-folder-exchange.h"
+#include "e2k-propnames.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+//#include "exchange-config-listener.h"
+
+#include <libedataserver/e-source-list.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct _ExchangeHierarchyFavoritesPrivate {
+       char *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, (char *)e2k_uri_path (e_folder_exchange_get_internal_uri (folder)));
+}
+
+static const char *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 int 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 char *prop = E2K_PR_DAV_HREF, *shortcut_uri;
+       char *perm_url, *folder_uri;
+       int i, nresults, 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;
+}      
+
+static ExchangeAccountFolderResult
+remove_folder (ExchangeHierarchy *hier, EFolder *folder)
+{
+       ExchangeHierarchyFavorites *hfav =
+               EXCHANGE_HIERARCHY_FAVORITES (hier);
+       const char *folder_uri, *shortcut_uri;
+       E2kHTTPStatus status;
+       const char *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);
+       }
+
+#if 0
+SURF : Find a proper palce to fix this
+       /* 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);
+       }
+       
+#endif
+       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 char *folder_uri, *permanent_uri;
+       char *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 char *hierarchy_name,
+                                 const char *physical_uri_prefix,
+                                 const char *home_uri,
+                                 const char *public_uri,
+                                 const char *owner_name,
+                                 const char *owner_email,
+                                 const char *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,
+                                            FALSE);
+       return hier;
+}
diff --git a/servers/exchange/storage/exchange-hierarchy-favorites.h b/servers/exchange/storage/exchange-hierarchy-favorites.h
new file mode 100644 (file)
index 0000000..933f17e
--- /dev/null
@@ -0,0 +1,49 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char *hierarchy_name,
+                                                    const char *physical_uri_prefix,
+                                                    const char *home_uri,
+                                                    const char *public_uri,
+                                                    const char *owner_name,
+                                                    const char *owner_email,
+                                                    const char *source_uri);
+
+ExchangeAccountFolderResult  exchange_hierarchy_favorites_add_folder (ExchangeHierarchy *hier,
+                                                                     EFolder *folder);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_HIERARCHY_FAVORITES_H__ */
diff --git a/servers/exchange/storage/exchange-hierarchy-gal.c b/servers/exchange/storage/exchange-hierarchy-gal.c
new file mode 100644 (file)
index 0000000..9ee7693
--- /dev/null
@@ -0,0 +1,72 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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-config-listener.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 char *hierarchy_name,
+                           const char *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);
+#if 0
+SURF :
+       /* Add ESource */
+       add_folder_esource (hier->account, EXCHANGE_CONTACTS_FOLDER, 
+                           hierarchy_name, physical_uri_prefix);
+#endif
+       
+       g_object_unref (toplevel);
+
+       return hier;
+}
diff --git a/servers/exchange/storage/exchange-hierarchy-gal.h b/servers/exchange/storage/exchange-hierarchy-gal.h
new file mode 100644 (file)
index 0000000..1cc9717
--- /dev/null
@@ -0,0 +1,40 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char *hierarchy_name,
+                                                      const char *physical_uri_prefix);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_HIERARCHY_GAL_H__ */
diff --git a/servers/exchange/storage/exchange-hierarchy-somedav.c b/servers/exchange/storage/exchange-hierarchy-somedav.c
new file mode 100644 (file)
index 0000000..2ff1a39
--- /dev/null
@@ -0,0 +1,255 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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-marshal.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,
+                                                gboolean offline);
+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,
+                             e2k_marshal_NONE__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)
+{
+       char *access;
+
+       access = e2k_properties_get_prop (props, PR_ACCESS);
+       return !access || !atoi (access);
+}
+
+static const char *folder_props[] = {
+       E2K_PR_EXCHANGE_FOLDER_CLASS,
+       E2K_PR_HTTPMAIL_UNREAD_COUNT,
+       E2K_PR_DAV_DISPLAY_NAME,
+       PR_ACCESS
+};
+static const int n_folder_props = sizeof (folder_props) / sizeof (folder_props[0]);
+
+static ExchangeAccountFolderResult
+scan_subtree (ExchangeHierarchy *hier, EFolder *folder, gboolean offline)
+{
+       ExchangeHierarchySomeDAV *hsd = EXCHANGE_HIERARCHY_SOMEDAV (hier);
+       GPtrArray *hrefs;
+       E2kResultIter *iter;
+       E2kResult *result;
+       int folders_returned=0, folders_added=0, i, mode;
+       E2kHTTPStatus status;
+       ExchangeAccountFolderResult folder_result;
+       EFolder *iter_folder = NULL;
+
+       if (hsd->priv->scanned || folder != hier->toplevel)
+               return EXCHANGE_ACCOUNT_FOLDER_OK;
+       hsd->priv->scanned = TRUE;
+
+       if (offline)
+               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 char **)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 char *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 char *uri)
+{
+       ExchangeHierarchyWebDAV *hwd;
+       ExchangeHierarchy *hier;
+       E2kContext *ctx;
+       E2kHTTPStatus status;
+       E2kResult *results;
+       int nresults;
+       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))
+               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/servers/exchange/storage/exchange-hierarchy-somedav.h b/servers/exchange/storage/exchange-hierarchy-somedav.h
new file mode 100644 (file)
index 0000000..1ac6626
--- /dev/null
@@ -0,0 +1,52 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char *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 char *uri);
+
+/* signal emitter */
+void exchange_hierarchy_somedav_href_unreadable (ExchangeHierarchySomeDAV *hsd,
+                                                const char *href);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_HIERARCHY_SOMEDAV_H__ */
diff --git a/servers/exchange/storage/exchange-hierarchy-webdav.c b/servers/exchange/storage/exchange-hierarchy-webdav.c
new file mode 100644 (file)
index 0000000..1cc085f
--- /dev/null
@@ -0,0 +1,928 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 "exchange-hierarchy-webdav.h"
+#include "exchange-account.h"
+//#include "exchange-constants.h"
+#include "e-folder-exchange.h"
+#include "e2k-context.h"
+#include "e2k-propnames.h"
+#include "e2k-restriction.h"
+#include "e2k-uri.h"
+#include "e2k-utils.h"
+//#include "exchange-config-listener.h"
+#include "exchange-folder-size.h"
+
+#include <libedataserverui/e-passwords.h>
+#include "e2k-path.h"
+#include <libedataserver/e-source-list.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct _ExchangeHierarchyWebDAVPrivate {
+       GHashTable *folders_by_internal_path;
+       gboolean deep_searchable;
+       char *trash_path;
+       ExchangeFolderSize *foldersize;
+       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,
+                                                 gboolean offline);
+static ExchangeAccountFolderResult create_folder (ExchangeHierarchy *hier,
+                                                 EFolder *parent,
+                                                 const char *name,
+                                                 const char *type);
+static ExchangeAccountFolderResult remove_folder (ExchangeHierarchy *hier,
+                                                 EFolder *folder);
+static ExchangeAccountFolderResult xfer_folder   (ExchangeHierarchy *hier,
+                                                 EFolder *source,
+                                                 EFolder *dest_parent,
+                                                 const char *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 (g_str_hash, g_str_equal);
+       hwd->priv->foldersize = exchange_folder_size_new ();
+       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)
+{
+       ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (object);
+
+       if (hwd->priv->foldersize) {
+               g_object_unref (hwd->priv->foldersize);
+               hwd->priv->foldersize = NULL;
+       }
+
+       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 {
+       char *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)
+{
+       int 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,
+                                    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 char *internal_uri ;
+       char *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,
+                               (char *)e2k_uri_path (internal_uri)))
+               return;
+
+       g_hash_table_insert (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->folders_by_internal_path,
+                            (char *)e2k_uri_path (internal_uri), 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 char *internal_uri = e_folder_exchange_get_internal_uri (folder);
+       char *mf_path;
+
+       g_hash_table_remove (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->folders_by_internal_path,
+                            (char *)e2k_uri_path (internal_uri));
+
+       mf_path = e_folder_exchange_get_storage_file (folder, "connector-metadata.xml");
+       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 char *internal_uri,
+                    EFolder *parent, const char *name, const char *type,
+                    const char *outlook_class, int unread,
+                    gboolean offline_supported)
+{
+       EFolder *folder;
+       char *real_type, *http_uri, *physical_uri;
+
+       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);
+
+       if (strchr (name, '/')) {
+               char *fixed_name, *p;
+
+               /* We can't have a '/' in the path, so we replace it with
+                * a '\' and just hope the user doesn't have another
+                * folder with that name.
+                */
+               fixed_name = g_strdup (name);
+               for (p = fixed_name; *p; p++) {
+                       if (*p == '/')
+                               *p = '\\';
+               }
+
+               physical_uri = e2k_uri_concat (e_folder_get_physical_uri (parent), fixed_name);
+               g_free (fixed_name);
+       } else
+               physical_uri = e2k_uri_concat (e_folder_get_physical_uri (parent), name);
+
+       if (internal_uri) {
+               folder = e_folder_exchange_new (hier, name,
+                                               real_type, outlook_class,
+                                               physical_uri, internal_uri);
+       } else {
+               char *temp_name;
+
+               /* appending "/" here, so that hash table lookup in rescan() succeeds */
+               if (*(name + (strlen (name) - 1)) != '/')
+                       temp_name = g_strdup_printf ("%s/", name);
+               else
+                       temp_name = g_strdup (name);
+
+               http_uri = e2k_uri_concat (e_folder_exchange_get_internal_uri (parent), temp_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 char *name, const char *type)
+{
+       EFolder *dest;
+       E2kProperties *props;
+       E2kHTTPStatus status;
+       char *permanent_url = NULL;
+       int 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_folder_size_update (
+                               EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->foldersize,
+                               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;
+       int 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_folder_size_remove (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->foldersize,
+                                       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 char *dest_name,
+            gboolean remove_source)
+{
+       E2kHTTPStatus status;
+       EFolder *dest;
+       char *permanent_url = NULL, *physical_uri, *source_parent;
+       const char *folder_type = NULL, *source_folder_name;
+       ExchangeAccountFolderResult ret_code;
+       int offline;
+       gdouble f_size;
+
+       exchange_account_is_offline (hier->account, &offline);
+        if (offline != 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, (offline == OFFLINE_MODE));
+               physical_uri = (char *) e_folder_get_physical_uri (source);
+               g_object_unref (dest);
+               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 
+                        */
+                       f_size = exchange_folder_size_get (
+                                               EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->foldersize,
+                                               source_folder_name+1);
+                       exchange_folder_size_remove (
+                                               EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->foldersize,
+                                               source_folder_name+1);
+                       if (f_size >= 0)
+                               exchange_folder_size_update (
+                                               EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->foldersize,
+                                               dest_name, f_size);
+               }
+               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 0
+SURF :
+       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);
+               }
+       }
+#endif
+       if (physical_uri)
+               g_free (physical_uri);
+       return ret_code;
+}
+
+static void
+add_href (gpointer path, gpointer folder, gpointer hrefs)
+{
+       const char *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 char *rescan_props[] = {
+       PR_MESSAGE_SIZE_EXTENDED,
+       E2K_PR_HTTPMAIL_UNREAD_COUNT
+};
+static const int n_rescan_props = sizeof (rescan_props) / sizeof (rescan_props[0]);
+
+static void
+rescan (ExchangeHierarchy *hier)
+{
+       ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
+       const char *prop = E2K_PR_HTTPMAIL_UNREAD_COUNT;
+       const char *folder_size, *folder_name;
+       GPtrArray *hrefs;
+       E2kResultIter *iter;
+       E2kResult *result;
+       EFolder *folder;
+       int unread, offline;
+       gboolean personal = ( hier->type == EXCHANGE_HIERARCHY_PERSONAL );
+       gdouble fsize_d;
+
+       exchange_account_is_offline (hier->account, &offline);
+       if ( (offline != 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 char **)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,
+                                               PR_MESSAGE_SIZE_EXTENDED);
+               if (folder_size) {
+                       folder_name = e_folder_get_name (folder);
+                       fsize_d = g_ascii_strtod (folder_size, NULL)/1024;
+                       exchange_folder_size_update (hwd->priv->foldersize, 
+                                               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;
+}
+
+ExchangeFolderSize *
+exchange_hierarchy_webdav_get_folder_size (ExchangeHierarchyWebDAV *hwd)
+{
+       g_return_val_if_fail (EXCHANGE_IS_HIERARCHY_WEBDAV (hwd), NULL);
+
+       return hwd->priv->foldersize;
+}
+
+EFolder *
+exchange_hierarchy_webdav_parse_folder (ExchangeHierarchyWebDAV *hwd,
+                                       EFolder *parent,
+                                       E2kResult *result)
+{
+       EFolder *folder;
+       ExchangeFolderType *folder_type;
+       const char *name, *prop, *outlook_class, *permanenturl;
+       int 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 char *folder_props[] = {
+       E2K_PR_EXCHANGE_FOLDER_CLASS,
+       E2K_PR_HTTPMAIL_UNREAD_COUNT,
+       E2K_PR_DAV_DISPLAY_NAME,
+       E2K_PR_EXCHANGE_PERMANENTURL,
+       PR_MESSAGE_SIZE_EXTENDED,
+       E2K_PR_DAV_HAS_SUBS
+};
+static const int n_folder_props = sizeof (folder_props) / sizeof (folder_props[0]);
+
+static ExchangeAccountFolderResult
+scan_subtree (ExchangeHierarchy *hier, EFolder *parent, gboolean offline)
+{
+       static E2kRestriction *folders_rn;
+       ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
+       GSList *subtrees = NULL;
+       E2kResultIter *iter;
+       E2kResult *result;
+       E2kHTTPStatus status;
+       EFolder *folder, *tmp;
+       GPtrArray *folders;
+       int i;
+       gdouble fsize_d;
+       const char *name, *folder_size;
+       gboolean personal = ( EXCHANGE_HIERARCHY (hwd)->type == EXCHANGE_HIERARCHY_PERSONAL );
+
+       if (offline) {
+               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);
+       }
+
+       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, folder);
+               }
+               exchange_hierarchy_new_folder (hier, folder);
+
+               /* Check the folder size here */
+               name = e2k_properties_get_prop (result->props,
+                                                       E2K_PR_DAV_DISPLAY_NAME);
+               folder_size = e2k_properties_get_prop (result->props,
+                                                       PR_MESSAGE_SIZE_EXTENDED);
+
+               /* FIXME : Find a better way of doing this */
+               fsize_d = g_ascii_strtod (folder_size, NULL)/1024 ;
+               exchange_folder_size_update (hwd->priv->foldersize, 
+                                               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);
+
+       while (subtrees) {
+               folder = subtrees->data;
+               subtrees = g_slist_remove (subtrees, folder);
+               scan_subtree (hier, folder, offline);
+       }
+
+       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 char *physical_path, const char *path, gpointer data)
+{
+       struct scan_offline_data *sod = data;
+       EFolder *folder;
+       char *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) {
+               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 char *path;
+       char *dir, *prefix;
+       int 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 char *hierarchy_name,
+                                    const char *physical_uri_prefix,
+                                    const char *internal_uri_prefix,
+                                    const char *owner_name,
+                                    const char *owner_email,
+                                    const char *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 char *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 char *hierarchy_name,
+                              const char *physical_uri_prefix,
+                              const char *internal_uri_prefix,
+                              const char *owner_name,
+                              const char *owner_email,
+                              const char *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/servers/exchange/storage/exchange-hierarchy-webdav.h b/servers/exchange/storage/exchange-hierarchy-webdav.h
new file mode 100644 (file)
index 0000000..078e2c0
--- /dev/null
@@ -0,0 +1,77 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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 char *hierarchy_name,
+                                                 const char *physical_uri_prefix,
+                                                 const char *internal_uri_prefix,
+                                                 const char *owner_name,
+                                                 const char *owner_email,
+                                                 const char *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 char *hierarchy_name,
+                                           const char *physical_uri_prefix,
+                                           const char *internal_uri_prefix,
+                                           const char *owner_name,
+                                           const char *owner_email,
+                                           const char *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);
+
+//ExchangeFolderSize * exchange_hierarchy_webdav_get_folder_size (ExchangeHierarchyWebDAV *hwd);
+
+gdouble exchange_hierarchy_webdav_get_total_folder_size (ExchangeHierarchyWebDAV *hwd);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_HIERARCHY_WEBDAV_H__ */
diff --git a/servers/exchange/storage/exchange-hierarchy.c b/servers/exchange/storage/exchange-hierarchy.c
new file mode 100644 (file)
index 0000000..e5a5299
--- /dev/null
@@ -0,0 +1,384 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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"
+#include "e2k-marshal.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,
+                                                 gboolean offline);
+static void                        rescan        (ExchangeHierarchy *hier);
+static ExchangeAccountFolderResult create_folder (ExchangeHierarchy *hier,
+                                                 EFolder *parent,
+                                                 const char *name,
+                                                 const char *type);
+static ExchangeAccountFolderResult remove_folder (ExchangeHierarchy *hier,
+                                                 EFolder *folder);
+static ExchangeAccountFolderResult xfer_folder   (ExchangeHierarchy *hier,
+                                                 EFolder *source,
+                                                 EFolder *dest_parent,
+                                                 const char *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,
+                             e2k_marshal_NONE__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,
+                             e2k_marshal_NONE__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 char *name, const char *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 char *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 char *name, const char *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 char *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, gboolean offline)
+{
+       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, gboolean offline)
+{
+       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, offline);
+}
+
+
+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 char *owner_name,
+                             const char *owner_email,
+                             const char *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/servers/exchange/storage/exchange-hierarchy.h b/servers/exchange/storage/exchange-hierarchy.h
new file mode 100644 (file)
index 0000000..5de5acb
--- /dev/null
@@ -0,0 +1,114 @@
+/* -*- 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"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#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))
+
+typedef enum {
+       EXCHANGE_HIERARCHY_PERSONAL,
+       EXCHANGE_HIERARCHY_FAVORITES,
+       EXCHANGE_HIERARCHY_PUBLIC,
+       EXCHANGE_HIERARCHY_GAL,
+       EXCHANGE_HIERARCHY_FOREIGN
+} ExchangeHierarchyType;
+
+struct _ExchangeHierarchy {
+       GObject parent;
+
+       ExchangeAccount *account;
+       ExchangeHierarchyType type;
+       EFolder *toplevel;
+
+       char *owner_name;
+       char *owner_email;
+       char *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,
+                                                     gboolean offline);
+
+       ExchangeAccountFolderResult (*create_folder) (ExchangeHierarchy *hier,
+                                                     EFolder *parent,
+                                                     const char *name,
+                                                     const char *type);
+       ExchangeAccountFolderResult (*remove_folder) (ExchangeHierarchy *hier,
+                                                     EFolder *folder);
+       ExchangeAccountFolderResult (*xfer_folder)   (ExchangeHierarchy *hier,
+                                                     EFolder *source,
+                                                     EFolder *dest_parent,
+                                                     const char *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 char        *owner_name,
+                                                const char        *owner_email,
+                                                const char        *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,
+                                                              gboolean           offline);
+
+ExchangeAccountFolderResult exchange_hierarchy_create_folder (ExchangeHierarchy *hier,
+                                                             EFolder           *parent,
+                                                             const char        *name,
+                                                             const char        *type);
+ExchangeAccountFolderResult exchange_hierarchy_remove_folder (ExchangeHierarchy *hier,
+                                                             EFolder           *folder);
+ExchangeAccountFolderResult exchange_hierarchy_xfer_folder   (ExchangeHierarchy *hier,
+                                                             EFolder           *source,
+                                                             EFolder           *dest_parent,
+                                                             const char        *dest_name,
+                                                             gboolean           remove_source);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_HIERARCHY_H__ */
diff --git a/servers/exchange/storage/exchange-types.h b/servers/exchange/storage/exchange-types.h
new file mode 100644 (file)
index 0000000..2612973
--- /dev/null
@@ -0,0 +1,60 @@
+/* -*- 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;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __EXCHANGE_TYPES_H__ */
diff --git a/servers/exchange/storage/libexchange-storage.pc.in b/servers/exchange/storage/libexchange-storage.pc.in
new file mode 100644 (file)
index 0000000..e1368eb
--- /dev/null
@@ -0,0 +1,16 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+idldir=@idldir@
+IDL_INCLUDES=-I ${idldir} @IDL_INCLUDES@
+
+privincludedir=@privincludedir@
+
+Name: libexchange
+Description: Client library for accessing Exchange server through webdav.
+Version: @VERSION@
+Requires: libsoup-2.4 >= @LIBSOUP_REQUIRED@ 
+Libs: -L${libdir} -lexchange-storage-1.2
+Cflags: -I${privincludedir}/exchange
diff --git a/servers/exchange/xntlm/ChangeLog b/servers/exchange/xntlm/ChangeLog
new file mode 100644 (file)
index 0000000..6b07afc
--- /dev/null
@@ -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/servers/exchange/xntlm/Makefile.am b/servers/exchange/xntlm/Makefile.am
new file mode 100644 (file)
index 0000000..ef8df16
--- /dev/null
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libxntlm.la
+
+libxntlm_la_SOURCES =  \
+       xntlm.c         \
+       xntlm.h         \
+       xntlm-des.c     \
+       xntlm-des.h     \
+       xntlm-md4.c     \
+       xntlm-md4.h
+
+INCLUDES =             \
+       -I$(top_srcdir) \
+       $(GLIB_CFLAGS)
diff --git a/servers/exchange/xntlm/xntlm-des.c b/servers/exchange/xntlm/xntlm-des.c
new file mode 100644 (file)
index 0000000..2c93a96
--- /dev/null
@@ -0,0 +1,348 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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, unsigned char 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 unsigned char 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 unsigned char totrot[] = {
+       1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28
+};
+
+/* permuted choice key (table) */
+static unsigned char 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 int 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 unsigned char *key, int decrypt)
+{
+       unsigned char pc1m[56];         /* place to modify pc1 into */
+       unsigned char pcr[56];          /* place to rotate pc1 into */
+       register int i,j,l;
+       int m;
+       unsigned char 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/servers/exchange/xntlm/xntlm-des.h b/servers/exchange/xntlm/xntlm-des.h
new file mode 100644 (file)
index 0000000..f95e8f7
--- /dev/null
@@ -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/gtypes.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 unsigned char *key, int decrypt);
+
+void xntlm_des (XNTLM_DES_KS ks, unsigned char block[8]);
+
+#endif /* _XNTLM_DES_H */
diff --git a/servers/exchange/xntlm/xntlm-md4.c b/servers/exchange/xntlm/xntlm-md4.c
new file mode 100644 (file)
index 0000000..eadca39
--- /dev/null
@@ -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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gtypes.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 unsigned char *M,
+             guint32 *AA, guint32 *BB,
+             guint32 *CC, guint32 *DD)
+{
+       guint32 A, B, C, D, X[16];
+       int 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 unsigned char *in, int nbytes, unsigned char digest[16])
+{
+       unsigned char M[128];
+       guint32 A, B, C, D;
+       int pbytes, nbits = nbytes * 8, remaining_bytes;
+       int 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/servers/exchange/xntlm/xntlm-md4.h b/servers/exchange/xntlm/xntlm-md4.h
new file mode 100644 (file)
index 0000000..70c91b0
--- /dev/null
@@ -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 unsigned char *in, int nbytes,
+                  unsigned char digest[16]);
+
+#endif /* _XNTLM_MD4_H */
diff --git a/servers/exchange/xntlm/xntlm.c b/servers/exchange/xntlm/xntlm.c
new file mode 100644 (file)
index 0000000..69abd21
--- /dev/null
@@ -0,0 +1,328 @@
+/* -*- 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 unsigned char 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 char *
+strip_dup (unsigned char *mem, int len)
+{
+       char *buf = g_malloc (len / 2 + 1), *p = buf;
+
+       while (len > 0) {
+               *p = (char)*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, int len, char **nonce,
+                      char **nt_domain, char **w2k_domain)
+{
+       unsigned char *chall = (unsigned char *)challenge;
+       int 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, int offset, const char *data, int 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, data, len);
+}
+
+static void ntlm_lanmanager_hash (const char *password, char hash[21]);
+static void ntlm_nt_hash         (const char *password, char hash[21]);
+static void ntlm_calc_response   (const guchar key[21],
+                                 const guchar plaintext[8],
+                                 guchar results[24]);
+
+static unsigned char 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 char *nonce, const char *domain,
+                   const char *user, const char *password,
+                   const char *workstation)
+{
+       GByteArray *message;
+       guchar hash[21], lm_resp[24], nt_resp[24];
+
+       if (!workstation)
+               workstation = "";
+
+       message = g_byte_array_new ();
+
+       ntlm_lanmanager_hash (password, hash);
+       ntlm_calc_response (hash, nonce, lm_resp);
+       ntlm_nt_hash (password, hash);
+       ntlm_calc_response (hash, 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,
+                        lm_resp, sizeof (lm_resp));
+       ntlm_set_string (message, NTLM_RESPONSE_NT_RESP_OFFSET,
+                        nt_resp, sizeof (nt_resp));
+
+       return message;
+}
+
+
+static void
+setup_schedule (const guchar *key_56, XNTLM_DES_KS ks)
+{
+       guchar key[8];
+       int 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 unsigned char 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 char *password, char hash[21])
+{
+       guchar lm_password [15];
+       XNTLM_DES_KS ks;
+       unsigned int i;
+
+       for (i = 0; i < 14 && password [i]; i++)
+               lm_password [i] = toupper ((unsigned char) 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, hash);
+
+       setup_schedule (lm_password + 7, ks);
+       xntlm_des (ks, hash + 8);
+}
+
+static void
+ntlm_nt_hash (const char *password, char hash[21])
+{
+       unsigned char *buf, *p;
+
+       p = buf = g_malloc (strlen (password) * 2);
+
+       while (*password) {
+               *p++ = *password++;
+               *p++ = '\0';
+       }
+
+       xntlm_md4sum (buf, p - buf, 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/servers/exchange/xntlm/xntlm.h b/servers/exchange/xntlm/xntlm.h
new file mode 100644 (file)
index 0000000..3b2e278
--- /dev/null
@@ -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, int len, char **nonce,
+                                  char **nt_domain, char **w2k_domain);
+GByteArray *xntlm_authenticate    (const char *nonce, const char *domain,
+                                  const char *user, const char *password,
+                                  const char *workstation);
+
+#endif /* _XNTLM_H */