From: Ahmed Sarfraaz Date: Fri, 10 Jun 2005 10:51:10 +0000 (+0000) Subject: First movement of Exchange server communication code to e-d-s HEAD. X-Git-Tag: upstream/3.7.4~7375 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0714bbbd9d9aa362ba60d4285ce10fd9ca30e924;p=platform%2Fupstream%2Fevolution-data-server.git First movement of Exchange server communication code to e-d-s HEAD. --- diff --git a/servers/exchange/ChangeLog b/servers/exchange/ChangeLog new file mode 100644 index 0000000..eaf597f --- /dev/null +++ b/servers/exchange/ChangeLog @@ -0,0 +1,48 @@ +2005-06-10 Sarfraaz Ahmed + + First movement of exchange server communication code into e-d-s HEAD. + +2005-06-07 Sarfraaz Ahmed + + * 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 + + * storage/e-shell-marshal.list : New file + +2005-06-02 Sarfraaz Ahmed + + * 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 + + * 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 index 0000000..c004e72 --- /dev/null +++ b/servers/exchange/Makefile.am @@ -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 index 0000000..61a7635 --- /dev/null +++ b/servers/exchange/lib/Makefile.am @@ -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 index 0000000..99a5768 --- /dev/null +++ b/servers/exchange/lib/e2k-action.c @@ -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 +#endif + +#include + +#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 index 0000000..6011c7f --- /dev/null +++ b/servers/exchange/lib/e2k-action.h @@ -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 index 0000000..02f6697 --- /dev/null +++ b/servers/exchange/lib/e2k-autoconfig.c @@ -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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 +//#include +#include +#include +#include +//#include +#include +#include +#include + +#include + +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 index 0000000..a2a0376 --- /dev/null +++ b/servers/exchange/lib/e2k-autoconfig.h @@ -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 index 0000000..35a3c0c --- /dev/null +++ b/servers/exchange/lib/e2k-context.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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, ""); + + 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, ""); + } + 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, "", 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, "\r\n"); + + /* If this is a BPROPPATCH, add the section. */ + if (hrefs) { + g_string_append (propxml, "\r\n"); + for (i = 0; i < nhrefs; i++) { + g_string_append_printf (propxml, "%s", + hrefs[i]); + } + g_string_append (propxml, "\r\n\r\n"); + } + + /* Add properties. */ + subxml = NULL; + e2k_properties_foreach (props, add_set_props, &subxml); + if (subxml) { + g_string_append (propxml, "\r\n"); + g_string_append (propxml, subxml->str); + g_string_append (propxml, "\r\n"); + g_string_free (subxml, TRUE); + } + + /* Add properties. */ + subxml = NULL; + e2k_properties_foreach_removed (props, add_remove_props, &subxml); + if (subxml) { + g_string_append (propxml, "\r\n"); + g_string_append (propxml, subxml->str); + g_string_append (propxml, "\r\n"); + g_string_free (subxml, TRUE); + } + + /* Finish it up */ + g_string_append (propxml, "\r\n"); + + /* 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, "\r\n"); + + if (hrefs) { + g_string_append (propxml, "\r\n"); + for (i = 0; i < nhrefs; i++) { + g_string_append_printf (propxml, "%s", + hrefs[i]); + } + g_string_append (propxml, "\r\n\r\n"); + } + + g_string_append (propxml, "\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\r\n"); + + 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, "\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, ""); + + 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 ""); + + for (i = 0; i < nhrefs; i++) { + g_string_append (xml, ""); + e2k_g_string_append_xml_escaped (xml, hrefs[i]); + g_string_append (xml, ""); + } + + g_string_append (xml, ""); + + 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 ? ""); + for (i = 0; i < nhrefs; i++) { + g_string_append (xml, ""); + e2k_g_string_append_xml_escaped (xml, source_hrefs[i]); + g_string_append (xml, ""); + } + g_string_append (xml, "" : "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 index 0000000..0da58d2 --- /dev/null +++ b/servers/exchange/lib/e2k-context.h @@ -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 +#include + +#include + +#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 index 0000000..3dbba46 --- /dev/null +++ b/servers/exchange/lib/e2k-encoding-utils.c @@ -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 + +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 index 0000000..78c44f8 --- /dev/null +++ b/servers/exchange/lib/e2k-encoding-utils.h @@ -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 + +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 index 0000000..b22c9bf --- /dev/null +++ b/servers/exchange/lib/e2k-freebusy.c @@ -0,0 +1,555 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* Copyright (C) 2002-2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU 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 +#endif + +#include "e2k-freebusy.h" +#include "e2k-propnames.h" +#include "e2k-restriction.h" +#include "e2k-uri.h" +#include "e2k-utils.h" + +#include +#include + +#include + +/** + * 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 index 0000000..70d9503 --- /dev/null +++ b/servers/exchange/lib/e2k-freebusy.h @@ -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 index 0000000..868eaf9 --- /dev/null +++ b/servers/exchange/lib/e2k-global-catalog.c @@ -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 +#include +#include +#include + +#include + +#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 index 0000000..8bf1fc0 --- /dev/null +++ b/servers/exchange/lib/e2k-global-catalog.h @@ -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 +#include +#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 index 0000000..83d74ff --- /dev/null +++ b/servers/exchange/lib/e2k-http-utils.c @@ -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 + +#include +#include +#include + +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 index 0000000..e03b5c3 --- /dev/null +++ b/servers/exchange/lib/e2k-http-utils.h @@ -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 +#include + +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 index 0000000..396204b --- /dev/null +++ b/servers/exchange/lib/e2k-kerberos.c @@ -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 +#endif + +#include +#include +#include +#include +#include + +#include "e2k-kerberos.h" +#include + +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 index 0000000..51e5c56 --- /dev/null +++ b/servers/exchange/lib/e2k-kerberos.h @@ -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 index 0000000..4a9ea3f --- /dev/null +++ b/servers/exchange/lib/e2k-operation.c @@ -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 +#endif + +#include + +#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 index 0000000..ce2b92d --- /dev/null +++ b/servers/exchange/lib/e2k-operation.h @@ -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 +#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 index 0000000..d394aca --- /dev/null +++ b/servers/exchange/lib/e2k-path.c @@ -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 + +#include +#include +#include +#include +#include +#include + +#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 index 0000000..58c1287 --- /dev/null +++ b/servers/exchange/lib/e2k-path.h @@ -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 + +/* 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 index 0000000..9875f21 --- /dev/null +++ b/servers/exchange/lib/e2k-properties.c @@ -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 +#include +#include + +#include + +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 index 0000000..fdfc7df --- /dev/null +++ b/servers/exchange/lib/e2k-properties.h @@ -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 +#include + +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 index 0000000..c120233 --- /dev/null +++ b/servers/exchange/lib/e2k-propnames.c.in @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* Copyright (C) 2001-2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU 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 index 0000000..6d91ee4 --- /dev/null +++ b/servers/exchange/lib/e2k-propnames.h.in @@ -0,0 +1,224 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2002-2004 Novell, Inc. */ + +#ifndef __E2K_PROPNAMES_H__ +#define __E2K_PROPNAMES_H__ + +#define E2K_NS_DAV "DAV:" +#define E2K_PR_DAV_CONTENT_CLASS "DAV:contentclass" +#define E2K_PR_DAV_CONTENT_LENGTH "DAV:getcontentlength" +#define E2K_PR_DAV_CONTENT_TYPE "DAV:getcontenttype" +#define E2K_PR_DAV_CREATION_DATE "DAV:creationdate" +#define E2K_PR_DAV_DISPLAY_NAME "DAV:displayname" +#define E2K_PR_DAV_LAST_MODIFIED "DAV:getlastmodified" +#define E2K_PR_DAV_HAS_SUBS "DAV:hassubs" +#define E2K_PR_DAV_HREF "DAV:href" +#define E2K_PR_DAV_IS_COLLECTION "DAV:iscollection" +#define E2K_PR_DAV_IS_HIDDEN "DAV:ishidden" +#define E2K_PR_DAV_LOCATION "DAV:location" +#define E2K_PR_DAV_UID "DAV:uid" +#define E2K_PR_DAV_VISIBLE_COUNT "DAV:visiblecount" + +#define E2K_NS_CALENDAR "urn:schemas:calendar:" +#define E2K_PR_CALENDAR_BUSY_STATUS E2K_NS_CALENDAR "busystatus" +#define E2K_PR_CALENDAR_DTEND E2K_NS_CALENDAR "dtend" +#define E2K_PR_CALENDAR_DTSTART E2K_NS_CALENDAR "dtstart" +#define E2K_PR_CALENDAR_INSTANCE_TYPE E2K_NS_CALENDAR "instancetype" +#define E2K_PR_CALENDAR_LAST_MODIFIED E2K_NS_CALENDAR "lastmodifiedtime" +#define E2K_PR_CALENDAR_UID E2K_NS_CALENDAR "uid" +#define E2K_PR_CALENDAR_URL E2K_NS_CALENDAR "locationurl" +#define E2K_PR_CALENDAR_FREEBUSY_URL E2K_NS_CALENDAR "fburl" +#define E2K_PR_CALENDAR_REMINDER_NEXT_TIME E2K_NS_CALENDAR "remindernexttime" + +#define E2K_NS_CONTACTS "urn:schemas:contacts:" +#define E2K_PR_CONTACTS_FULL_NAME E2K_NS_CONTACTS "cn" +#define E2K_PR_CONTACTS_FAMILY_NAME E2K_NS_CONTACTS "sn" +#define E2K_PR_CONTACTS_GIVEN_NAME E2K_NS_CONTACTS "givenName" +#define E2K_PR_CONTACTS_ADDITIONAL_NAME E2K_NS_CONTACTS "middlename" +#define E2K_PR_CONTACTS_NAME_SUFFIX E2K_NS_CONTACTS "namesuffix" +#define E2K_PR_CONTACTS_TITLE E2K_NS_CONTACTS "title" +#define E2K_PR_CONTACTS_ORG E2K_NS_CONTACTS "o" +#define E2K_PR_CONTACTS_FILE_AS E2K_NS_CONTACTS "fileas" + +#define E2K_PR_CONTACTS_PHONE_CALLBACK E2K_NS_CONTACTS "callbackphone" +#define E2K_PR_CONTACTS_PHONE_BUSINESS_FAX E2K_NS_CONTACTS "facsimiletelephonenumber" +#define E2K_PR_CONTACTS_PHONE_HOME_FAX E2K_NS_CONTACTS "homefax" +#define E2K_PR_CONTACTS_PHONE_HOME E2K_NS_CONTACTS "homePhone" +#define E2K_PR_CONTACTS_PHONE_HOME_2 E2K_NS_CONTACTS "homephone2" +#define E2K_PR_CONTACTS_PHONE_ISDN E2K_NS_CONTACTS "internationalisdnnumber" +#define E2K_PR_CONTACTS_PHONE_MOBILE E2K_NS_CONTACTS "mobile" +#define E2K_PR_CONTACTS_PHONE_COMPANY E2K_NS_CONTACTS "organizationmainphone" +#define E2K_PR_CONTACTS_PHONE_OTHER_FAX E2K_NS_CONTACTS "otherfax" +#define E2K_PR_CONTACTS_PHONE_PAGER E2K_NS_CONTACTS "pager" +#define E2K_PR_CONTACTS_PHONE_BUSINESS E2K_NS_CONTACTS "telephoneNumber" +#define E2K_PR_CONTACTS_PHONE_BUSINESS_2 E2K_NS_CONTACTS "telephonenumber2" +#define E2K_PR_CONTACTS_PHONE_TELEX E2K_NS_CONTACTS "telexnumber" +#define E2K_PR_CONTACTS_PHONE_TTYTDD E2K_NS_CONTACTS "ttytddphone" +#define E2K_PR_CONTACTS_PHONE_ASSISTANT E2K_NS_CONTACTS "secretaryphone" +#define E2K_PR_CONTACTS_PHONE_CAR E2K_NS_CONTACTS "othermobile" +#define E2K_PR_CONTACTS_PHONE_OTHER E2K_NS_CONTACTS "otherTelephone" + +#define E2K_PR_CONTACTS_EMAIL1 E2K_NS_CONTACTS "email1" +#define E2K_PR_CONTACTS_EMAIL2 E2K_NS_CONTACTS "email2" +#define E2K_PR_CONTACTS_EMAIL3 E2K_NS_CONTACTS "email3" + +#define E2K_PR_CONTACTS_ADDRESS_WORK E2K_NS_CONTACTS "workaddress" +#define E2K_PR_CONTACTS_WORK_STREET E2K_NS_CONTACTS "street" +#define E2K_PR_CONTACTS_WORK_PO_BOX E2K_NS_CONTACTS "postofficebox" +#define E2K_PR_CONTACTS_WORK_CITY E2K_NS_CONTACTS "l" +#define E2K_PR_CONTACTS_WORK_STATE E2K_NS_CONTACTS "st" +#define E2K_PR_CONTACTS_WORK_ZIP E2K_NS_CONTACTS "postalcode" +#define E2K_PR_CONTACTS_WORK_COUNTRY E2K_NS_CONTACTS "co" +#define E2K_PR_CONTACTS_ADDRESS_HOME E2K_NS_CONTACTS "homepostaladdress" +#define E2K_PR_CONTACTS_HOME_STREET E2K_NS_CONTACTS "homeStreet" +#define E2K_PR_CONTACTS_HOME_PO_BOX E2K_NS_CONTACTS "homePostOfficeBox" +#define E2K_PR_CONTACTS_HOME_CITY E2K_NS_CONTACTS "homeCity" +#define E2K_PR_CONTACTS_HOME_STATE E2K_NS_CONTACTS "homeState" +#define E2K_PR_CONTACTS_HOME_ZIP E2K_NS_CONTACTS "homePostalCode" +#define E2K_PR_CONTACTS_HOME_COUNTRY E2K_NS_CONTACTS "homeCountry" +#define E2K_PR_CONTACTS_ADDRESS_OTHER E2K_NS_CONTACTS "otherpostaladdress" +#define E2K_PR_CONTACTS_OTHER_STREET E2K_NS_CONTACTS "otherstreet" +#define E2K_PR_CONTACTS_OTHER_PO_BOX E2K_NS_CONTACTS "otherpostofficebox" +#define E2K_PR_CONTACTS_OTHER_CITY E2K_NS_CONTACTS "othercity" +#define E2K_PR_CONTACTS_OTHER_STATE E2K_NS_CONTACTS "otherstate" +#define E2K_PR_CONTACTS_OTHER_ZIP E2K_NS_CONTACTS "otherpostalcode" +#define E2K_PR_CONTACTS_OTHER_COUNTRY E2K_NS_CONTACTS "othercountry" + +#define E2K_PR_CONTACTS_HOMEPAGE_URL E2K_NS_CONTACTS "businesshomepage" +#define E2K_PR_CONTACTS_ORG_UNIT E2K_NS_CONTACTS "department" +#define E2K_PR_CONTACTS_OFFICE E2K_NS_CONTACTS "roomnumber" +#define E2K_PR_CONTACTS_ROLE E2K_NS_CONTACTS "profession" +#define E2K_PR_CONTACTS_MANAGER E2K_NS_CONTACTS "manager" +#define E2K_PR_CONTACTS_ASSISTANT E2K_NS_CONTACTS "secretarycn" +#define E2K_PR_CONTACTS_NICKNAME E2K_NS_CONTACTS "nickname" +#define E2K_PR_CONTACTS_SPOUSE E2K_NS_CONTACTS "spousecn" +#define E2K_PR_CONTACTS_BIRTH_DATE E2K_NS_CONTACTS "bday" +#define E2K_PR_CONTACTS_ANNIVERSARY E2K_NS_CONTACTS "weddinganniversary" + +#define E2K_NS_HTTPMAIL "urn:schemas:httpmail:" +#define E2K_PR_HTTPMAIL_DATE E2K_NS_HTTPMAIL "date" +#define E2K_PR_HTTPMAIL_FROM_EMAIL E2K_NS_HTTPMAIL "fromemail" +#define E2K_PR_HTTPMAIL_FROM_NAME E2K_NS_HTTPMAIL "fromname" +#define E2K_PR_HTTPMAIL_HAS_ATTACHMENT E2K_NS_HTTPMAIL "hasattachment" +#define E2K_PR_HTTPMAIL_MESSAGE_FLAG E2K_NS_HTTPMAIL "messageflag" +#define E2K_PR_HTTPMAIL_READ E2K_NS_HTTPMAIL "read" +#define E2K_PR_HTTPMAIL_SUBJECT E2K_NS_HTTPMAIL "subject" +#define E2K_PR_HTTPMAIL_TEXT_DESCRIPTION E2K_NS_HTTPMAIL "textdescription" +#define E2K_PR_HTTPMAIL_THREAD_TOPIC E2K_NS_HTTPMAIL "thread-topic" +#define E2K_PR_HTTPMAIL_UNREAD_COUNT E2K_NS_HTTPMAIL "unreadcount" + +#define E2K_NS_STD_FOLDER "urn:schemas:httpmail:" +#define E2K_PR_STD_FOLDER_CALENDAR E2K_NS_HTTPMAIL "calendar" +#define E2K_PR_STD_FOLDER_CONTACTS E2K_NS_HTTPMAIL "contacts" +#define E2K_PR_STD_FOLDER_DELETED_ITEMS E2K_NS_HTTPMAIL "deleteditems" +#define E2K_PR_STD_FOLDER_DRAFTS E2K_NS_HTTPMAIL "drafts" +#define E2K_PR_STD_FOLDER_INBOX E2K_NS_HTTPMAIL "inbox" +#define E2K_PR_STD_FOLDER_JOURNAL E2K_NS_HTTPMAIL "journal" +#define E2K_PR_STD_FOLDER_ROOT E2K_NS_HTTPMAIL "msgfolderroot" +#define E2K_PR_STD_FOLDER_NOTES E2K_NS_HTTPMAIL "notes" +#define E2K_PR_STD_FOLDER_OUTBOX E2K_NS_HTTPMAIL "outbox" +#define E2K_PR_STD_FOLDER_SENDMSG E2K_NS_HTTPMAIL "sendmsg" +#define E2K_PR_STD_FOLDER_SENT_ITEMS E2K_NS_HTTPMAIL "sentitems" +#define E2K_PR_STD_FOLDER_TASKS E2K_NS_HTTPMAIL "tasks" + +#define E2K_NS_MAILHEADER "urn:schemas:mailheader:" +#define E2K_PR_MAILHEADER_CC E2K_NS_MAILHEADER "cc" +#define E2K_PR_MAILHEADER_DATE E2K_NS_MAILHEADER "date" +#define E2K_PR_MAILHEADER_FROM E2K_NS_MAILHEADER "from" +#define E2K_PR_MAILHEADER_IMPORTANCE E2K_NS_MAILHEADER "importance" +#define E2K_PR_MAILHEADER_IN_REPLY_TO E2K_NS_MAILHEADER "in-reply-to" +#define E2K_PR_MAILHEADER_MESSAGE_ID E2K_NS_MAILHEADER "message-id" +#define E2K_PR_MAILHEADER_RECEIVED E2K_NS_MAILHEADER "received" +#define E2K_PR_MAILHEADER_REFERENCES E2K_NS_MAILHEADER "references" +#define E2K_PR_MAILHEADER_REPLY_BY E2K_NS_MAILHEADER "reply-by" +#define E2K_PR_MAILHEADER_SUBJECT E2K_NS_MAILHEADER "subject" +#define E2K_PR_MAILHEADER_THREAD_INDEX E2K_NS_MAILHEADER "thread-index" +#define E2K_PR_MAILHEADER_TO E2K_NS_MAILHEADER "to" +#define E2K_PR_MAILHEADER_COMPLETED E2K_NS_MAILHEADER "x-message-completed" + + +#define E2K_NS_SUBSCRIPTION "http://schemas.microsoft.com/Exchange/" +#define E2K_PR_SUBSCRIPTION_ID E2K_NS_SUBSCRIPTION "subscriptionID" + +#define E2K_NS_EXCHANGE "http://schemas.microsoft.com/exchange/" +#define E2K_PR_EXCHANGE_MESSAGE_CLASS E2K_NS_EXCHANGE "outlookmessageclass" +#define E2K_PR_EXCHANGE_FOLDER_CLASS E2K_NS_EXCHANGE "outlookfolderclass" +#define E2K_PR_EXCHANGE_KEYWORDS E2K_NS_EXCHANGE "keywords-utf8" +#define E2K_PR_EXCHANGE_SD_BINARY E2K_NS_EXCHANGE "ntsecuritydescriptor" +#define E2K_PR_EXCHANGE_SD_XML E2K_NS_EXCHANGE "security/descriptor" +#define E2K_PR_EXCHANGE_TIMEZONE E2K_NS_EXCHANGE "timezone" +#define E2K_PR_EXCHANGE_PERMANENTURL E2K_NS_EXCHANGE "permanenturl" +#define E2K_PR_EXCHANGE_FOLDER_SIZE E2K_NS_EXCHANGE "foldersize" +#define E2K_PR_EXCHANGE_OOF_STATE E2K_NS_EXCHANGE "oof-state" + +#define E2K_NS_REPL "http://schemas.microsoft.com/repl/" +#define E2K_PR_REPL_UID E2K_NS_REPL "repl-uid" + +#define E2K_NS_SECURITY "http://schemas.microsoft.com/security/" +#define E2K_NS_TYPE "urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" + +#define E2K_NS_MAPI "http://schemas.microsoft.com/mapi/" +#define E2K_PR_MAPI_COMMON_START E2K_NS_MAPI "commonstart" +#define E2K_PR_MAPI_COMMON_END E2K_NS_MAPI "commonend" +#define E2K_PR_MAPI_NO_AUTOARCHIVE E2K_NS_MAPI "agingdontageme" +#define E2K_PR_MAPI_PRIORITY E2K_NS_MAPI "priority" +#define E2K_PR_MAPI_REMINDER_SET E2K_NS_MAPI "reminderset" +#define E2K_PR_MAPI_SENSITIVITY E2K_NS_MAPI "sensitivity" +#define E2K_PR_MAPI_SIDE_EFFECTS E2K_NS_MAPI "sideeffects" +#define E2K_PR_MAPI_EMAIL_1_ENTRYID E2K_NS_MAPI "email1originalentryid" +#define E2K_PR_MAPI_EMAIL_1_ADDRTYPE E2K_NS_MAPI "email1addrtype" +#define E2K_PR_MAPI_EMAIL_1_ADDRESS E2K_NS_MAPI "email1emailaddress" +#define E2K_PR_MAPI_EMAIL_1_DISPLAY_NAME E2K_NS_MAPI "email1originaldisplayname" +#define E2K_PR_MAPI_EMAIL_2_ENTRYID E2K_NS_MAPI "email2originalentryid" +#define E2K_PR_MAPI_EMAIL_2_ADDRTYPE E2K_NS_MAPI "email2addrtype" +#define E2K_PR_MAPI_EMAIL_2_ADDRESS E2K_NS_MAPI "email2emailaddress" +#define E2K_PR_MAPI_EMAIL_2_DISPLAY_NAME E2K_NS_MAPI "email2originaldisplayname" +#define E2K_PR_MAPI_EMAIL_3_ENTRYID E2K_NS_MAPI "email3originalentryid" +#define E2K_PR_MAPI_EMAIL_3_ADDRTYPE E2K_NS_MAPI "email3addrtype" +#define E2K_PR_MAPI_EMAIL_3_ADDRESS E2K_NS_MAPI "email3emailaddress" +#define E2K_PR_MAPI_EMAIL_3_DISPLAY_NAME E2K_NS_MAPI "email3originaldisplayname" +#define E2K_PR_MAPI_EMAIL_LIST_TYPE E2K_NS_MAPI "emaillisttype" +#define E2K_PR_MAPI_EMAIL_ADDRESS_LIST E2K_NS_MAPI "emailaddresslist" + + +#define E2K_NS_MAPI_ID "http://schemas.microsoft.com/mapi/id/" +#define E2K_NS_MAPI_ID_LEN (sizeof (E2K_NS_MAPI_ID) - 1) + +#define E2K_NS_OUTLOOK_APPOINTMENT E2K_NS_MAPI_ID "{00062002-0000-0000-C000-000000000046}/" + +#define E2K_NS_OUTLOOK_TASK E2K_NS_MAPI_ID "{00062003-0000-0000-C000-000000000046}/" +#define E2K_PR_OUTLOOK_TASK_STATUS E2K_NS_OUTLOOK_TASK "0x00008101" +#define E2K_PR_OUTLOOK_TASK_PERCENT E2K_NS_OUTLOOK_TASK "0x00008102" +#define E2K_PR_OUTLOOK_TASK_TEAM_TASK E2K_NS_OUTLOOK_TASK "0x00008103" +#define E2K_PR_OUTLOOK_TASK_START_DT E2K_NS_OUTLOOK_TASK "0x00008104" +#define E2K_PR_OUTLOOK_TASK_DUE_DT E2K_NS_OUTLOOK_TASK "0x00008105" +#define E2K_PR_OUTLOOK_TASK_DONE_DT E2K_NS_OUTLOOK_TASK "0x0000810f" +#define E2K_PR_OUTLOOK_TASK_ACTUAL_WORK E2K_NS_OUTLOOK_TASK "0x00008110" +#define E2K_PR_OUTLOOK_TASK_TOTAL_WORK E2K_NS_OUTLOOK_TASK "0x00008111" +#define E2K_PR_OUTLOOK_TASK_IS_DONE E2K_NS_OUTLOOK_TASK "0x0000811c" +#define E2K_PR_OUTLOOK_TASK_OWNER E2K_NS_OUTLOOK_TASK "0x0000811f" +#define E2K_PR_OUTLOOK_TASK_RECURRING E2K_NS_OUTLOOK_TASK "0x00008126" +#define E2K_PR_OUTLOOK_TASK_ASSIGNMENT E2K_NS_OUTLOOK_TASK "0x00008129" + +#define E2K_NS_OUTLOOK_CONTACT E2K_NS_MAPI_ID "{00062004-0000-0000-C000-000000000046}/" +#define E2K_PR_OUTLOOK_CONTACT_JOURNAL E2K_NS_OUTLOOK_CONTACT "0x00008025" +#define E2K_PR_OUTLOOK_CONTACT_NETMEETING_URL E2K_NS_OUTLOOK_CONTACT "0x00008056" +#define E2K_PR_OUTLOOK_CONTACT_IM_ADDR E2K_NS_OUTLOOK_CONTACT "0x00008062" + +#define E2K_NS_OUTLOOK_COMMON E2K_NS_MAPI_ID "{00062008-0000-0000-C000-000000000046}/" +#define E2K_PR_OUTLOOK_COMMON_CONTACTS E2K_NS_OUTLOOK_COMMON "0x00008586" + + +#define E2K_NS_OUTLOOK_JOURNAL E2K_NS_MAPI_ID "{0006200A-0000-0000-C000-000000000046}/" + +#define E2K_NS_OUTLOOK_STICKYNOTE E2K_NS_MAPI_ID "{0006200E-0000-0000-C000-000000000046}/" +#define E2K_PR_OUTLOOK_STICKYNOTE_COLOR E2K_NS_OUTLOOK_STICKYNOTE "0x00008b00" +#define E2K_PR_OUTLOOK_STICKYNOTE_WIDTH E2K_NS_OUTLOOK_STICKYNOTE "0x00008b02" +#define E2K_PR_OUTLOOK_STICKYNOTE_HEIGHT E2K_NS_OUTLOOK_STICKYNOTE "0x00008b03" + + +#define E2K_NS_MAPI_PROPTAG "http://schemas.microsoft.com/mapi/proptag/" + +@AUTOGENERATE@ + +#endif /* __E2K_PROPNAMES_H__ */ diff --git a/servers/exchange/lib/e2k-proptags.h.in b/servers/exchange/lib/e2k-proptags.h.in new file mode 100644 index 0000000..7891424 --- /dev/null +++ b/servers/exchange/lib/e2k-proptags.h.in @@ -0,0 +1,9 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2003-2004 Novell, Inc. */ + +#ifndef __E2K_PROPTAGS_H__ +#define __E2K_PROPTAGS_H__ + +@AUTOGENERATE@ + +#endif /* __E2K_PROPTAGS_H__ */ diff --git a/servers/exchange/lib/e2k-restriction.c b/servers/exchange/lib/e2k-restriction.c new file mode 100644 index 0000000..e27386b --- /dev/null +++ b/servers/exchange/lib/e2k-restriction.c @@ -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 +#endif + +#include "e2k-restriction.h" +#include "e2k-properties.h" +#include "e2k-rule.h" + +#include +#include + +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 index 0000000..5f0cdac --- /dev/null +++ b/servers/exchange/lib/e2k-restriction.h @@ -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 index 0000000..40227d2 --- /dev/null +++ b/servers/exchange/lib/e2k-result.c @@ -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 +#include + +#include +#include +#include + +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, "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 index 0000000..310556e --- /dev/null +++ b/servers/exchange/lib/e2k-result.h @@ -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 +#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 index 0000000..28a836d --- /dev/null +++ b/servers/exchange/lib/e2k-rule-xml.c @@ -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 + +#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 index 0000000..2b34697 --- /dev/null +++ b/servers/exchange/lib/e2k-rule-xml.h @@ -0,0 +1,12 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2003, 2004 Novell, Inc. */ + +#ifndef __E2K_RULE_XML_H__ +#define __E2K_RULE_XML_H__ + +#include "e2k-types.h" +#include "e2k-rule.h" + +xmlDoc *e2k_rules_to_xml (E2kRules *rules); + +#endif /* __E2K_RULE_XML_H__ */ diff --git a/servers/exchange/lib/e2k-rule.c b/servers/exchange/lib/e2k-rule.c new file mode 100644 index 0000000..11c979c --- /dev/null +++ b/servers/exchange/lib/e2k-rule.c @@ -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 + +#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 index 0000000..35db35d --- /dev/null +++ b/servers/exchange/lib/e2k-rule.h @@ -0,0 +1,251 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2003, 2004 Novell, Inc. */ + +#ifndef __E2K_RULE_H__ +#define __E2K_RULE_H__ + +#include "e2k-types.h" +#include "e2k-properties.h" +#include "e2k-restriction.h" + +/* We define these types here because they're private to libexchange, + * so code that #includes e2k-restriction, etc, shouldn't see them. + */ + +typedef struct { + const 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 index 0000000..bb21064 --- /dev/null +++ b/servers/exchange/lib/e2k-security-descriptor.c @@ -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 +#include +#include +#include +#include + +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 index 0000000..492387a --- /dev/null +++ b/servers/exchange/lib/e2k-security-descriptor.h @@ -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 +#include + +#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 index 0000000..e7e7b12 --- /dev/null +++ b/servers/exchange/lib/e2k-sid.c @@ -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 +#include +#include +#include + +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 index 0000000..382ddc9 --- /dev/null +++ b/servers/exchange/lib/e2k-sid.h @@ -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 +#include + +#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 index 0000000..dfa5983 --- /dev/null +++ b/servers/exchange/lib/e2k-types.h @@ -0,0 +1,93 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2001-2004 Novell, Inc. */ + +#ifndef __E2K_TYPES_H__ +#define __E2K_TYPES_H__ + +#include +#include + +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 index 0000000..ffe8b5a --- /dev/null +++ b/servers/exchange/lib/e2k-uri.c @@ -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 +#endif + +#include +#include +#include +#include + +#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 index 0000000..77fe5a3 --- /dev/null +++ b/servers/exchange/lib/e2k-uri.h @@ -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 + +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 index 0000000..739e329 --- /dev/null +++ b/servers/exchange/lib/e2k-user-dialog.c @@ -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 +#endif + +#include "e2k-user-dialog.h" +#include "e2k-types.h" + +#include +#include +#include +//#include +//#include +#include +#include +#include +#include +#include +#include +#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 index 0000000..b212172 --- /dev/null +++ b/servers/exchange/lib/e2k-user-dialog.h @@ -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 + +#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 index 0000000..bb2cace --- /dev/null +++ b/servers/exchange/lib/e2k-utils.c @@ -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 + +#include +#include +#include + +/* 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 **)×tamp, 10) - 1900; + if (*timestamp++ != '-') + return -1; + tm.tm_mon = strtoul (timestamp, (char **)×tamp, 10) - 1; + if (*timestamp++ != '-') + return -1; + tm.tm_mday = strtoul (timestamp, (char **)×tamp, 10); + if (*timestamp++ != 'T') + return -1; + tm.tm_hour = strtoul (timestamp, (char **)×tamp, 10); + if (*timestamp++ != ':') + return -1; + tm.tm_min = strtoul (timestamp, (char **)×tamp, 10); + if (*timestamp++ != ':') + return -1; + tm.tm_sec = strtoul (timestamp, (char **)×tamp, 10); + if (*timestamp != '.' && *timestamp != 'Z') + return -1; + + return e_mktime_utc (&tm); +} + +/** + * e2k_make_timestamp: + * @when: the %time_t to convert to an ISO8601 timestamp + * + * Creates an ISO8601 timestamp (in an format acceptable to Exchange) + * corresponding to @when. + * + * Return value: the timestamp, which the caller must free. + **/ +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 index 0000000..cf8e216 --- /dev/null +++ b/servers/exchange/lib/e2k-utils.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Copyright (C) 2001-2004 Novell, Inc. */ + +#ifndef E2K_UTILS_H +#define E2K_UTILS_H + +#include +#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 index 0000000..b53623e --- /dev/null +++ b/servers/exchange/lib/e2k-validate.h @@ -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 index 0000000..f523dbc --- /dev/null +++ b/servers/exchange/lib/e2k-xml-utils.c @@ -0,0 +1,255 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* Copyright (C) 2001-2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU 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 +#include +#include +#include + +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, "<"); + break; + case '>': + g_string_append (string, ">"); + break; + case '&': + g_string_append (string, "&"); + break; + case '"': + g_string_append (string, """); + break; + + default: + g_string_append_c (string, *value); + break; + } + value++; + } +} + +/** + * e2k_xml_find: + * @node: a node of an xml document + * @name: the name of the element to find + * + * Starts or continues a pre-order depth-first search of an xml + * document for an element named @name. @node is used as the starting + * point of the search, but is not examined itself. + * + * To search the complete document, pass the root node of the document + * as @node on the first call, and then on each successive call, + * pass the previous match as @node. + * + * Return value: the first matching element after @node, or %NULL when + * there are no more matches. + **/ +xmlNode * +e2k_xml_find (xmlNode *node, const 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 index 0000000..c26be08 --- /dev/null +++ b/servers/exchange/lib/e2k-xml-utils.h @@ -0,0 +1,24 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2001-2004 Novell, Inc. */ + +#ifndef __E2K_XML_UTILS_H__ +#define __E2K_XML_UTILS_H__ + +#include + +#include "e2k-types.h" +#include + +#define E2K_XML_HEADER "" + +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 index 0000000..45a8cf9 --- /dev/null +++ b/servers/exchange/lib/mapi-properties @@ -0,0 +1,1549 @@ +http://schemas.microsoft.com/mapi/proptag/ + +The second half of each property name indicates the type: +0002 PT_SHORT signed 16-bit integer +0003 PT_LONG signed 32-bit integer +000a PT_ERROR invalid type (?) +000b PT_BOOLEAN boolean +000d PT_OBJECT embedded object +0014 PT_LONGLONG signed 64-bit integer +001e PT_STRING8 Locale-encoded string +001f PT_UNICODE UTF8-encoded string +0040 PT_SYSTIME 64bit time value +0048 PT_CLSID GUID +00fd PT_SRESTRICTION rule condition +00fe PT_ACTIONS rule actions +0102 PT_BINARY binary blob +1xxx PT_MV_xxx multivalued + +In general, you can request either the 001e or 001f variant of a +string. + +Not all properties are available via WebDAV. (In particular, none with +type 000d are.) + +(The part below this line is preprocessed into lib/mapi-properties.[ch]) + +x00010003 PR_ACKNOWLEDGEMENT_MODE +x0002000b PR_ALTERNATE_RECIPIENT_ALLOWED +x00030102 PR_AUTHORIZING_USERS +x0004001f PR_AUTO_FORWARD_COMMENT +x0005000b PR_AUTO_FORWARDED +x00060102 PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID +x00070102 PR_CONTENT_CORRELATOR +x0008001f PR_CONTENT_IDENTIFIER +x00090003 PR_CONTENT_LENGTH +x000a000b PR_CONTENT_RETURN_REQUESTED +x000b0102 PR_CONVERSATION_KEY +x000c0102 PR_CONVERSION_EITS +x000d000b PR_CONVERSION_WITH_LOSS_PROHIBITED +x000e0102 PR_CONVERTED_EITS +x000f0040 PR_DEFERRED_DELIVERY_TIME +x00100040 PR_DELIVER_TIME +x00110003 PR_DISCARD_REASON +x0012000b PR_DISCLOSURE_OF_RECIPIENTS +x00130102 PR_DL_EXPANSION_HISTORY +x0014000b PR_DL_EXPANSION_PROHIBITED +x00150040 PR_EXPIRY_TIME +x0016000b PR_IMPLICIT_CONVERSION_PROHIBITED +x00170003 PR_IMPORTANCE +x00180102 PR_IPM_ID +x00190040 PR_LATEST_DELIVERY_TIME +x001a001f PR_MESSAGE_CLASS +x001b0102 PR_MESSAGE_DELIVERY_ID +x001e0102 PR_MESSAGE_SECURITY_LABEL +x001f0102 PR_OBSOLETED_IPMS +x00200102 PR_ORIGINALLY_INTENDED_RECIPIENT_NAME +x00210102 PR_ORIGINAL_EITS +x00220102 PR_ORIGINATOR_CERTIFICATE +x0023000b PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED +x00240102 PR_ORIGINATOR_RETURN_ADDRESS +x00250102 PR_PARENT_KEY +x00260003 PR_PRIORITY +x00270102 PR_ORIGIN_CHECK +x0028000b PR_PROOF_OF_SUBMISSION_REQUESTED +x0029000b PR_READ_RECEIPT_REQUESTED +x002a0040 PR_RECEIPT_TIME +x002b000b PR_RECIPIENT_REASSIGNMENT_PROHIBITED +x002c0102 PR_REDIRECTION_HISTORY +x002d0102 PR_RELATED_IPMS +x002e0003 PR_ORIGINAL_SENSITIVITY +x002f001f PR_LANGUAGES +x00300040 PR_REPLY_TIME +x00310102 PR_REPORT_TAG +x00320040 PR_REPORT_TIME +x0033000b PR_RETURNED_IPM +x00340003 PR_SECURITY +x0035000b PR_INCOMPLETE_COPY +x00360003 PR_SENSITIVITY +x0037001f PR_SUBJECT +x00380102 PR_SUBJECT_IPM +x00390040 PR_CLIENT_SUBMIT_TIME +x003a001f PR_REPORT_NAME +x003b0102 PR_SENT_REPRESENTING_SEARCH_KEY +x003c0102 PR_X400_CONTENT_TYPE +x003d001f PR_SUBJECT_PREFIX +x003e0003 PR_NON_RECEIPT_REASON +x003f0102 PR_RECEIVED_BY_ENTRYID +x0040001f PR_RECEIVED_BY_NAME +x00410102 PR_SENT_REPRESENTING_ENTRYID +x0042001f PR_SENT_REPRESENTING_NAME +x00430102 PR_RCVD_REPRESENTING_ENTRYID +x0044001f PR_RCVD_REPRESENTING_NAME +x00450102 PR_REPORT_ENTRYID +x00460102 PR_READ_RECEIPT_ENTRYID +x00470102 PR_MESSAGE_SUBMISSION_ID +x00470102 PR_MTS_ID +x00470102 PR_MTS_REPORT_ID +x00480040 PR_PROVIDER_SUBMIT_TIME +x0049001f PR_ORIGINAL_SUBJECT +x004a000b PR_DISC_VAL +x004b001f PR_ORIG_MESSAGE_CLASS +x004c0102 PR_ORIGINAL_AUTHOR_ENTRYID +x004d001f PR_ORIGINAL_AUTHOR_NAME +x004e0040 PR_ORIGINAL_SUBMIT_TIME +x004f0102 PR_REPLY_RECIPIENT_ENTRIES +x0050001f PR_REPLY_RECIPIENT_NAMES +x00510102 PR_RECEIVED_BY_SEARCH_KEY +x00520102 PR_RCVD_REPRESENTING_SEARCH_KEY +x00530102 PR_READ_RECEIPT_SEARCH_KEY +x00540102 PR_REPORT_SEARCH_KEY +x00550040 PR_ORIGINAL_DELIVERY_TIME +x00560102 PR_ORIGINAL_AUTHOR_SEARCH_KEY +x0057000b PR_MESSAGE_TO_ME +x0058000b PR_MESSAGE_CC_ME +x0059000b PR_MESSAGE_RECIP_ME +x005a001f PR_ORIGINAL_SENDER_NAME +x005b0102 PR_ORIGINAL_SENDER_ENTRYID +x005c0102 PR_ORIGINAL_SENDER_SEARCH_KEY +x005d001f PR_ORIGINAL_SENT_REPRESENTING_NAME +x005e0102 PR_ORIGINAL_SENT_REPRESENTING_ENTRYID +x005f0102 PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY +x00600040 PR_START_DATE +x00610040 PR_END_DATE +x00620003 PR_OWNER_APPT_ID +x0063000b PR_RESPONSE_REQUESTED +x0064001f PR_SENT_REPRESENTING_ADDRTYPE +x0065001f PR_SENT_REPRESENTING_EMAIL_ADDRESS +x0066001f PR_ORIGINAL_SENDER_ADDRTYPE +x0067001f PR_ORIGINAL_SENDER_EMAIL_ADDRESS +x0068001f PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE +x0069001f PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS +x0070001f PR_CONVERSATION_TOPIC +x00710102 PR_CONVERSATION_INDEX +x0072001f PR_ORIGINAL_DISPLAY_BCC +x0073001f PR_ORIGINAL_DISPLAY_CC +x0074001f PR_ORIGINAL_DISPLAY_TO +x0075001f PR_RECEIVED_BY_ADDRTYPE +x0076001f PR_RECEIVED_BY_EMAIL_ADDRESS +x0077001f PR_RCVD_REPRESENTING_ADDRTYPE +x0078001f PR_RCVD_REPRESENTING_EMAIL_ADDRESS +x0079001f PR_ORIGINAL_AUTHOR_ADDRTYPE +x007a001f PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS +x007b001f PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE +x007c001f PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS +x007d001f PR_TRANSPORT_MESSAGE_HEADERS +x007e0102 PR_DELEGATION +x007f0102 PR_TNEF_CORRELATION_KEY +x0c000102 PR_CONTENT_INTEGRITY_CHECK +x0c010003 PR_EXPLICIT_CONVERSION +x0c02000b PR_IPM_RETURN_REQUESTED +x0c030102 PR_MESSAGE_TOKEN +x0c040003 PR_NDR_REASON_CODE +x0c050003 PR_NDR_DIAG_CODE +x0c06000b PR_NON_RECEIPT_NOTIFICATION_REQUESTED +x0c070003 PR_DELIVERY_POINT +x0c08000b PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED +x0c090102 PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT +x0c0a000b PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY +x0c0b0003 PR_PHYSICAL_DELIVERY_MODE +x0c0c0003 PR_PHYSICAL_DELIVERY_REPORT_REQUEST +x0c0d0102 PR_PHYSICAL_FORWARDING_ADDRESS +x0c0e000b PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED +x0c0f000b PR_PHYSICAL_FORWARDING_PROHIBITED +x0c100102 PR_PHYSICAL_RENDITION_ATTRIBUTES +x0c110102 PR_PROOF_OF_DELIVERY +x0c12000b PR_PROOF_OF_DELIVERY_REQUESTED +x0c130102 PR_RECIPIENT_CERTIFICATE +x0c14001f PR_RECIPIENT_NUMBER_FOR_ADVICE +x0c150003 PR_RECIPIENT_TYPE +x0c160003 PR_REGISTERED_MAIL_TYPE +x0c17000b PR_REPLY_REQUESTED +x0c180003 PR_REQUESTED_DELIVERY_METHOD +x0c190102 PR_SENDER_ENTRYID +x0c1a001f PR_SENDER_NAME +x0c1b001f PR_SUPPLEMENTARY_INFO +x0c1c0003 PR_TYPE_OF_MTS_USER +x0c1d0102 PR_SENDER_SEARCH_KEY +x0c1e001f PR_SENDER_ADDRTYPE +x0c1f001f PR_SENDER_EMAIL_ADDRESS +x0e000014 PR_CURRENT_VERSION +x0e01000b PR_DELETE_AFTER_SUBMIT +x0e02001f PR_DISPLAY_BCC +x0e03001f PR_DISPLAY_CC +x0e04001f PR_DISPLAY_TO +x0e05001f PR_PARENT_DISPLAY +x0e060040 PR_MESSAGE_DELIVERY_TIME +x0e070003 PR_MESSAGE_FLAGS +x0e080003 PR_MESSAGE_SIZE +x0e080014 PR_MESSAGE_SIZE_EXTENDED +x0e090102 PR_PARENT_ENTRYID +x0e0a0102 PR_SENTMAIL_ENTRYID +x0e0c000b PR_CORRELATE +x0e0d0102 PR_CORRELATE_MTSID +x0e0e000b PR_DISCRETE_VALUES +x0e0f000b PR_RESPONSIBILITY +x0e100003 PR_SPOOLER_STATUS +x0e110003 PR_TRANSPORT_STATUS +x0e12000d PR_MESSAGE_RECIPIENTS +x0e13000d PR_MESSAGE_ATTACHMENTS +x0e140003 PR_SUBMIT_FLAGS +x0e150003 PR_RECIPIENT_STATUS +x0e160003 PR_TRANSPORT_KEY +x0e170003 PR_MSG_STATUS +x0e180003 PR_MESSAGE_DOWNLOAD_TIME +x0e190014 PR_CREATION_VERSION +x0e1a0014 PR_MODIFY_VERSION +x0e1b000b PR_HASATTACH +x0e1c0003 PR_BODY_CRC +x0e1d001f PR_NORMALIZED_SUBJECT +x0e1f000b PR_RTF_IN_SYNC +x0e200003 PR_ATTACH_SIZE +x0e210003 PR_ATTACH_NUM +x0e22000b PR_PREPROCESS +x0e230003 PR_INTERNET_ARTICLE_NUMBER +x0e24001f PR_NEWSGROUP_NAME +x0e250102 PR_ORIGINATING_MTA_CERTIFICATE +x0e260102 PR_PROOF_OF_SUBMISSION +x0e270102 PR_NT_SECURITY_DESCRIPTOR +x0e580102 PR_CREATOR_SID +x0e590102 PR_LAST_MODIFIER_SID +x0e5e0048 PR_MIME_HANDLER_CLASSIDS +x0e610003 PR_URL_COMP_NAME_POSTFIX +x0e62000b PR_URL_COMP_NAME_SET +x0e630003 PR_SUBFOLDER_CT +x0e640003 PR_DELETED_SUBFOLDER_CT +x0e660040 PR_DELETE_TIME +x0e670102 PR_AGE_LIMIT +x0e790003 PR_TRUST_SENDER +x0e960102 PR_ATTACH_VIRUS_SCAN_INFO +x0ff40003 PR_ACCESS +x0ff50003 PR_ROW_TYPE +x0ff60102 PR_INSTANCE_KEY +x0ff70003 PR_ACCESS_LEVEL +x0ff80102 PR_MAPPING_SIGNATURE +x0ff90102 PR_RECORD_KEY +x0ffa0102 PR_STORE_RECORD_KEY +x0ffb0102 PR_STORE_ENTRYID +x0ffc0102 PR_MINI_ICON +x0ffd0102 PR_ICON +x0ffe0003 PR_OBJECT_TYPE +x0fff0102 PR_ENTRYID +x0fff0102 PR_MEMBER_ENTRYID +x1000001f PR_BODY +x1001001f PR_REPORT_TEXT +x10020102 PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY +x10030102 PR_REPORTING_DL_NAME +x10040102 PR_REPORTING_MTA_CERTIFICATE +x10060003 PR_RTF_SYNC_BODY_CRC +x10070003 PR_RTF_SYNC_BODY_COUNT +x1008001f PR_RTF_SYNC_BODY_TAG +x10090102 PR_RTF_COMPRESSED +x10100003 PR_RTF_SYNC_PREFIX_COUNT +x10110003 PR_RTF_SYNC_TRAILING_COUNT +x10120102 PR_ORIGINALLY_INTENDED_RECIP_ENTRYID +x10130102 PR_HTML +x1030001f PR_INTERNET_APPROVED +x1031001f PR_INTERNET_CONTROL +x1032001f PR_INTERNET_DISTRIBUTION +x1033001f PR_INTERNET_FOLLOWUP_TO +x10340003 PR_INTERNET_LINES +x1035001f PR_INTERNET_MESSAGE_ID +x1036001f PR_INTERNET_NEWSGROUPS +x1037001f PR_INTERNET_ORGANIZATION +x1038001f PR_INTERNET_NNTP_PATH +x1039001f PR_INTERNET_REFERENCES +x103a001f PR_SUPERSEDES +x103b0102 PR_POST_FOLDER_ENTRIES +x103c001f PR_POST_FOLDER_NAMES +x103d0102 PR_POST_REPLY_FOLDER_ENTRIES +x103e001f PR_POST_REPLY_FOLDER_NAMES +x103f0102 PR_POST_REPLY_DENIED +x1040001f PR_NNTP_XREF +x1041001f PR_INTERNET_PRECEDENCE +x1042001f PR_IN_REPLY_TO_ID +x1043001f PR_LIST_HELP +x1044001f PR_LIST_SUBSCRIBE +x1045001f PR_LIST_UNSUBSCRIBE +x10800003 PR_ACTION +x10810003 PR_ACTION_FLAG +x10820040 PR_ACTION_DATE +x10900003 PR_FLAG_STATUS +x10910040 PR_FLAG_COMPLETE +x10c00102 PR_SMTP_TEMP_TBL_DATA +x10c10003 PR_SMTP_TEMP_TBL_DATA_2 +x10c20102 PR_SMTP_TEMP_TBL_DATA_3 +x10c30040 PR_CAL_START_TIME +x10c40040 PR_CAL_END_TIME +x10c50040 PR_CAL_RECURRING_ID +x10c6001f PR_DAV_SUBMIT_DATA +x10c70003 PR_CDO_EXPANSION_INDEX +x10c80102 PR_IFS_INTERNAL_DATA +x10ca0040 PR_CAL_REMINDER_NEXT_TIME +x10f1001f PR_OWA_URL +x10f2000b PR_DISABLE_FULL_FIDELITY +x10f3001f PR_URL_COMP_NAME +x10f4000b PR_ATTR_HIDDEN +x10f5000b PR_ATTR_SYSTEM +x10f6000b PR_ATTR_READONLY +x11000102 PR_P1_CONTENT +x11010102 PR_P1_CONTENT_TYPE +x30000003 PR_ROWID +x3001001f PR_DISPLAY_NAME +x3002001f PR_ADDRTYPE +x3003001f PR_EMAIL_ADDRESS +x3004001f PR_COMMENT +x30050003 PR_DEPTH +x3006001f PR_PROVIDER_DISPLAY +x30070040 PR_CREATION_TIME +x30080040 PR_LAST_MODIFICATION_TIME +x30090003 PR_RESOURCE_FLAGS +x300a001f PR_PROVIDER_DLL_NAME +x300b0102 PR_SEARCH_KEY +x300c0102 PR_PROVIDER_UID +x300d0003 PR_PROVIDER_ORDINAL +x3301001f PR_FORM_VERSION +x33020048 PR_FORM_CLSID +x3303001f PR_FORM_CONTACT_NAME +x3304001f PR_FORM_CATEGORY +x3305001f PR_FORM_CATEGORY_SUB +x33061003 PR_FORM_HOST_MAP +x3307000b PR_FORM_HIDDEN +x3308001f PR_FORM_DESIGNER_NAME +x33090048 PR_FORM_DESIGNER_GUID +x330a0003 PR_FORM_MESSAGE_BEHAVIOR +x3400000b PR_DEFAULT_STORE +x340d0003 PR_STORE_SUPPORT_MASK +x340e0003 PR_STORE_STATE +x34100102 PR_IPM_SUBTREE_SEARCH_KEY +x34110102 PR_IPM_OUTBOX_SEARCH_KEY +x34120102 PR_IPM_WASTEBASKET_SEARCH_KEY +x34130102 PR_IPM_SENTMAIL_SEARCH_KEY +x34140102 PR_MDB_PROVIDER +x3415000d PR_RECEIVE_FOLDER_SETTINGS +x35df0003 PR_VALID_FOLDER_MASK +x35e00102 PR_IPM_SUBTREE_ENTRYID +x35e20102 PR_IPM_OUTBOX_ENTRYID +x35e30102 PR_IPM_WASTEBASKET_ENTRYID +x35e40102 PR_IPM_SENTMAIL_ENTRYID +x35e50102 PR_VIEWS_ENTRYID +x35e60102 PR_COMMON_VIEWS_ENTRYID +x35e70102 PR_FINDER_ENTRYID +x36000003 PR_CONTAINER_FLAGS +x36010003 PR_FOLDER_TYPE +x36020003 PR_CONTENT_COUNT +x36030003 PR_CONTENT_UNREAD +x3604000d PR_CREATE_TEMPLATES +x3605000d PR_DETAILS_TABLE +x3607000d PR_SEARCH +x3609000b PR_SELECTABLE +x360a000b PR_SUBFOLDERS +x360b0003 PR_STATUS +x360c001f PR_ANR +x360d1003 PR_CONTENTS_SORT_ORDER +x360e000d PR_CONTAINER_HIERARCHY +x360f000d PR_CONTAINER_CONTENTS +x3610000d PR_FOLDER_ASSOCIATED_CONTENTS +x36110102 PR_DEF_CREATE_DL +x36120102 PR_DEF_CREATE_MAILUSER +x3613001f PR_CONTAINER_CLASS +x36140014 PR_CONTAINER_MODIFY_VERSION +x36150102 PR_AB_PROVIDER_ID +x36160102 PR_DEFAULT_VIEW_ENTRYID +x36170003 PR_ASSOC_CONTENT_COUNT +x361c0102 PR_PACKED_NAME_PROPS +x36d00102 PR_IPM_APPOINTMENT_ENTRYID +x36d10102 PR_IPM_CONTACT_ENTRYID +x36d20102 PR_IPM_JOURNAL_ENTRYID +x36d30102 PR_IPM_NOTE_ENTRYID +x36d40102 PR_IPM_TASK_ENTRYID +x36d50102 PR_REMINDERS_ONLINE_ENTRYID +x36d60102 PR_REMINDERS_OFFLINE_ENTRYID +x36d70102 PR_IPM_DRAFTS_ENTRYID +x36d81102 PR_OUTLOOK_2003_ENTRYIDS +x36df0102 PR_FOLDER_WEBVIEWINFO +x36e00102 PR_FOLDER_XVIEWINFO_E +x36e10003 PR_FOLDER_VIEWS_ONLY +x36e41102 PR_FREEBUSY_ENTRYIDS +x36e5001f PR_DEF_MSG_CLASS +x36e6001f PR_DEF_FORM_NAME +x36e9000b PR_GENERATE_EXCHANGE_VIEWS +x36ec0003 PR_AGING_PERIOD +x36ee0003 PR_AGING_GRANULARITY +x37000102 PR_ATTACHMENT_X400_PARAMETERS +x3701000d PR_ATTACH_DATA_OBJ +x37010102 PR_ATTACH_DATA_BIN +x37020102 PR_ATTACH_ENCODING +x3703001f PR_ATTACH_EXTENSION +x3704001f PR_ATTACH_FILENAME +x37050003 PR_ATTACH_METHOD +x3707001f PR_ATTACH_LONG_FILENAME +x3708001f PR_ATTACH_PATHNAME +x37090102 PR_ATTACH_RENDERING +x370a0102 PR_ATTACH_TAG +x370b0003 PR_RENDERING_POSITION +x370c001f PR_ATTACH_TRANSPORT_NAME +x370d001f PR_ATTACH_LONG_PATHNAME +x370e001f PR_ATTACH_MIME_TAG +x370f0102 PR_ATTACH_ADDITIONAL_INFO +x3712001f PR_ATTACH_CONTENT_ID +x3713001f PR_ATTACH_CONTENT_LOCATION +x37140003 PR_ATTACH_FLAGS +x3716001f PR_ATTACH_CONTENT_DISPOSITION +x38800102 PR_SYNCEVENT_SUPPRESS_GUID +x39000003 PR_DISPLAY_TYPE +x39020102 PR_TEMPLATEID +x39040102 PR_PRIMARY_CAPABILITY +x39fe001f PR_SMTP_ADDRESS +x39ff001f PR_7BIT_DISPLAY_NAME +x39ff001f PR_EMS_AB_DISPLAY_NAME_PRINTABLE +x3a00001f PR_ACCOUNT +x3a010102 PR_ALTERNATE_RECIPIENT +x3a02001f PR_CALLBACK_TELEPHONE_NUMBER +x3a03000b PR_CONVERSION_PROHIBITED +x3a04000b PR_DISCLOSE_RECIPIENTS +x3a05001f PR_GENERATION +x3a06001f PR_GIVEN_NAME +x3a07001f PR_GOVERNMENT_ID_NUMBER +x3a08001f PR_BUSINESS_TELEPHONE_NUMBER +x3a08001f PR_OFFICE_TELEPHONE_NUMBER +x3a09001f PR_HOME_TELEPHONE_NUMBER +x3a0a001f PR_INITIALS +x3a0b001f PR_KEYWORD +x3a0c001f PR_LANGUAGE +x3a0d001f PR_LOCATION +x3a0e000b PR_MAIL_PERMISSION +x3a0f001f PR_MHS_COMMON_NAME +x3a10001f PR_ORGANIZATIONAL_ID_NUMBER +x3a11001f PR_SURNAME +x3a120102 PR_ORIGINAL_ENTRYID +x3a13001f PR_ORIGINAL_DISPLAY_NAME +x3a140102 PR_ORIGINAL_SEARCH_KEY +x3a15001f PR_POSTAL_ADDRESS +x3a16001f PR_COMPANY_NAME +x3a17001f PR_TITLE +x3a18001f PR_DEPARTMENT_NAME +x3a19001f PR_OFFICE_LOCATION +x3a1a001f PR_PRIMARY_TELEPHONE_NUMBER +x3a1b001f PR_BUSINESS2_TELEPHONE_NUMBER +x3a1b001f PR_OFFICE2_TELEPHONE_NUMBER +x3a1c001f PR_CELLULAR_TELEPHONE_NUMBER +x3a1c001f PR_MOBILE_TELEPHONE_NUMBER +x3a1d001f PR_RADIO_TELEPHONE_NUMBER +x3a1e001f PR_CAR_TELEPHONE_NUMBER +x3a1f001f PR_OTHER_TELEPHONE_NUMBER +x3a20001f PR_TRANSMITTABLE_DISPLAY_NAME +x3a21001f PR_BEEPER_TELEPHONE_NUMBER +x3a21001f PR_PAGER_TELEPHONE_NUMBER +x3a220102 PR_USER_CERTIFICATE +x3a23001f PR_PRIMARY_FAX_NUMBER +x3a24001f PR_BUSINESS_FAX_NUMBER +x3a25001f PR_HOME_FAX_NUMBER +x3a26001f PR_BUSINESS_ADDRESS_COUNTRY +x3a26001f PR_COUNTRY +x3a27001f PR_BUSINESS_ADDRESS_CITY +x3a27001f PR_LOCALITY +x3a28001f PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE +x3a28001f PR_STATE_OR_PROVINCE +x3a29001f PR_BUSINESS_ADDRESS_STREET +x3a29001f PR_STREET_ADDRESS +x3a2a001f PR_BUSINESS_ADDRESS_POSTAL_CODE +x3a2a001f PR_POSTAL_CODE +x3a2b001f PR_BUSINESS_ADDRESS_POST_OFFICE_BOX +x3a2b001f PR_POST_OFFICE_BOX +x3a2c001f PR_TELEX_NUMBER +x3a2d001f PR_ISDN_NUMBER +x3a2e001f PR_ASSISTANT_TELEPHONE_NUMBER +x3a2f001f PR_HOME2_TELEPHONE_NUMBER +x3a30001f PR_ASSISTANT +x3a40000b PR_SEND_RICH_INFO +x3a410040 PR_WEDDING_ANNIVERSARY +x3a420040 PR_BIRTHDAY +x3a43001f PR_HOBBIES +x3a44001f PR_MIDDLE_NAME +x3a45001f PR_DISPLAY_NAME_PREFIX +x3a46001f PR_PROFESSION +x3a47001f PR_PREFERRED_BY_NAME +x3a47001f PR_REFERRED_BY_NAME +x3a48001f PR_SPOUSE_NAME +x3a49001f PR_COMPUTER_NETWORK_NAME +x3a4a001f PR_CUSTOMER_ID +x3a4b001f PR_TTYTDD_PHONE_NUMBER +x3a4c001f PR_FTP_SITE +x3a4d0002 PR_GENDER +x3a4e001f PR_MANAGER_NAME +x3a4f001f PR_NICKNAME +x3a50001f PR_PERSONAL_HOME_PAGE +x3a51001f PR_BUSINESS_HOME_PAGE +x3a520048 PR_CONTACT_VERSION +x3a531102 PR_CONTACT_ENTRYIDS +x3a54101f PR_CONTACT_ADDRTYPES +x3a550003 PR_CONTACT_DEFAULT_ADDRESS_INDEX +x3a56101f PR_CONTACT_EMAIL_ADDRESSES +x3a57001f PR_COMPANY_MAIN_PHONE_NUMBER +x3a58101f PR_CHILDRENS_NAMES +x3a59001f PR_HOME_ADDRESS_CITY +x3a5a001f PR_HOME_ADDRESS_COUNTRY +x3a5b001f PR_HOME_ADDRESS_POSTAL_CODE +x3a5c001f PR_HOME_ADDRESS_STATE_OR_PROVINCE +x3a5d001f PR_HOME_ADDRESS_STREET +x3a5e001f PR_HOME_ADDRESS_POST_OFFICE_BOX +x3a5f001f PR_OTHER_ADDRESS_CITY +x3a60001f PR_OTHER_ADDRESS_COUNTRY +x3a61001f PR_OTHER_ADDRESS_POSTAL_CODE +x3a62001f PR_OTHER_ADDRESS_STATE_OR_PROVINCE +x3a63001f PR_OTHER_ADDRESS_STREET +x3a64001f PR_OTHER_ADDRESS_POST_OFFICE_BOX +x3a701102 PR_USER_X509_CERTIFICATE +x3a710003 PR_SEND_INTERNET_ENCODING +x3d000102 PR_STORE_PROVIDERS +x3d010102 PR_AB_PROVIDERS +x3d020102 PR_TRANSPORT_PROVIDERS +x3d04000b PR_DEFAULT_PROFILE +x3d051102 PR_AB_SEARCH_PATH +x3d060102 PR_AB_DEFAULT_DIR +x3d070102 PR_AB_DEFAULT_PAB +x3d080102 PR_FILTERING_HOOKS +x3d09001f PR_SERVICE_NAME +x3d0a001f PR_SERVICE_DLL_NAME +x3d0b001f PR_SERVICE_ENTRY_NAME +x3d0c0102 PR_SERVICE_UID +x3d0d0102 PR_SERVICE_EXTRA_UIDS +x3d0e0102 PR_SERVICES +x3d0f101f PR_SERVICE_SUPPORT_FILES +x3d10101f PR_SERVICE_DELETE_FILES +x3d110102 PR_AB_SEARCH_PATH_UPDATE +x3d12001f PR_PROFILE_NAME +x3d210102 PR_ADMIN_SECURITY_DESCRIPTOR +x3e00001f PR_IDENTITY_DISPLAY +x3e010102 PR_IDENTITY_ENTRYID +x3e020003 PR_RESOURCE_METHODS +x3e030003 PR_RESOURCE_TYPE +x3e040003 PR_STATUS_CODE +x3e050102 PR_IDENTITY_SEARCH_KEY +x3e060102 PR_OWN_STORE_ENTRYID +x3e07001f PR_RESOURCE_PATH +x3e08001f PR_STATUS_STRING +x3e09000b PR_X400_DEFERRED_DELIVERY_CANCEL +x3e0a0102 PR_HEADER_FOLDER_ENTRYID +x3e0b0003 PR_REMOTE_PROGRESS +x3e0c001f PR_REMOTE_PROGRESS_TEXT +x3e0d000b PR_REMOTE_VALIDATE_OK +x3f000003 PR_CONTROL_FLAGS +x3f010102 PR_CONTROL_STRUCTURE +x3f020003 PR_CONTROL_TYPE +x3f030003 PR_DELTAX +x3f040003 PR_DELTAY +x3f050003 PR_XPOS +x3f060003 PR_YPOS +x3f070102 PR_CONTROL_ID +x3f080003 PR_INITIAL_DETAILS_PANE +x3f800014 PR_DID +x3f810014 PR_SEQID +x3f820014 PR_DRAFTID +x3f830040 PR_CHECK_IN_TIME +x3f84001f PR_CHECK_IN_COMMENT +x3f850003 PR_VERSION_OP_CODE +x3f860102 PR_VERSION_OP_DATA +x3f870003 PR_VERSION_SEQUENCE_NUMBER +x3f880014 PR_ATTACH_ID +x3f8d001f PR_PKM_DOC_STATUS +x3f8e101f PR_MV_PKM_OPERATION_REQ +x3f8f001f PR_PKM_DOC_INTERNAL_STATE +x3f900002 PR_VERSIONING_FLAGS +x3f910102 PR_PKM_LAST_UNAPPROVED_VID +x3f92101f PR_MV_PKM_VERSION_LABELS +x3f93101f PR_MV_PKM_VERSION_STATUS +x3f940102 PR_PKM_INTERNAL_DATA +x3fc90102 PR_LAST_CONFLICT +x3fca0102 PR_CONFLICT_MSG_KEY +x3fd00102 PR_REPL_HEADER +x3fd10102 PR_REPL_STATUS +x3fd20102 PR_REPL_CHANGES +x3fd30102 PR_REPL_RGM +x3fd40102 PR_RMI +x3fd50102 PR_INTERNAL_POST_REPLY +x3fd60040 PR_NTSD_MODIFICATION_TIME +x3fd8001f PR_PREVIEW_UNREAD +x3fd9001f PR_PREVIEW +x3fda001f PR_ABSTRACT +x3fdb0003 PR_DL_REPORT_FLAGS +x3fdc0102 PR_BILATERAL_INFO +x3fdd0003 PR_MSG_BODY_ID +x3fde0003 PR_INTERNET_CPID +x3fdf0003 PR_AUTO_RESPONSE_SUPPRESS +x3fe0000d PR_ACL_TABLE +x3fe00102 PR_ACL_DATA +x3fe1000d PR_RULES_TABLE +x3fe10102 PR_RULES_DATA +x3fe20003 PR_FOLDER_DESIGN_FLAGS +x3fe3000b PR_DELEGATED_BY_RULE +x3fe4000b PR_DESIGN_IN_PROGRESS +x3fe5000b PR_SECURE_ORIGINATION +x3fe6000b PR_PUBLISH_IN_ADDRESS_BOOK +x3fe70003 PR_RESOLVE_METHOD +x3fe8001f PR_ADDRESS_BOOK_DISPLAY_NAME +x3fe90003 PR_EFORMS_LOCALE_ID +x3fea000b PR_HAS_DAMS +x3feb0003 PR_DEFERRED_SEND_NUMBER +x3fec0003 PR_DEFERRED_SEND_UNITS +x3fed0003 PR_EXPIRY_NUMBER +x3fee0003 PR_EXPIRY_UNITS +x3fef0040 PR_DEFERRED_SEND_TIME +x3ff00102 PR_CONFLICT_ENTRYID +x3ff10003 PR_MESSAGE_LOCALE_ID +x3ff20102 PR_RULE_TRIGGER_HISTORY +x3ff30102 PR_MOVE_TO_STORE_ENTRYID +x3ff40102 PR_MOVE_TO_FOLDER_ENTRYID +x3ff50003 PR_STORAGE_QUOTA_LIMIT +x3ff60003 PR_EXCESS_STORAGE_USED +x3ff7001f PR_SVR_GENERATING_QUOTA_MSG +x3ff8001f PR_CREATOR_NAME +x3ff90102 PR_CREATOR_ENTRYID +x3ffa001f PR_LAST_MODIFIER_NAME +x3ffb0102 PR_LAST_MODIFIER_ENTRYID +x3ffc001f PR_REPLY_RECIPIENT_SMTP_PROXIES +x3ffd0003 PR_MESSAGE_CODEPAGE +x3ffe0102 PR_EXTENDED_ACL_DATA +x3fff000b PR_FROM_I_HAVE +x40000003 PR_NEW_ATTACH +x40010003 PR_START_EMBED +x40020003 PR_END_EMBED +x40030003 PR_START_RECIP +x40040003 PR_END_RECIP +x40050003 PR_END_CC_RECIP +x40060003 PR_END_BCC_RECIP +x40070003 PR_END_P1_RECIP +x40090003 PR_START_TOP_FLD +x400a0003 PR_START_SUB_FLD +x400b0003 PR_END_FOLDER +x400c0003 PR_START_MESSAGE +x400d0003 PR_END_MESSAGE +x400e0003 PR_END_ATTACH +x400f0003 PR_EC_WARNING +x40100003 PR_START_FAI_MSG +x40110102 PR_NEW_FX_FOLDER +x40120003 PR_INCR_SYNC_CHG +x40130003 PR_INCR_SYNC_DEL +x40140003 PR_INCR_SYNC_END +x40150003 PR_INCR_SYNC_MSG +x40160003 PR_FX_DEL_PROP +x40170003 PR_IDSET_GIVEN +x40190003 PR_SENDER_FLAGS +x401a0003 PR_SENT_REPRESENTING_FLAGS +x401b0003 PR_RCVD_BY_FLAGS +x401c0003 PR_RCVD_REPRESENTING_FLAGS +x401d0003 PR_ORIGINAL_SENDER_FLAGS +x401e0003 PR_ORIGINAL_SENT_REPRESENTING_FLAGS +x401f0003 PR_REPORT_FLAGS +x40200003 PR_READ_RECEIPT_FLAGS +x4021000b PR_SOFT_DELETES +x402c0102 PR_MESSAGE_SUBMISSION_ID_FROM_CLIENT +x4030001f PR_SENDER_SIMPLE_DISP_NAME +x4031001f PR_SENT_REPRESENTING_SIMPLE_DISP_NAME +x4038001f PR_CREATOR_SIMPLE_DISP_NAME +x403d001f PR_ORG_ADDR_TYPE +x403e001f PR_ORG_EMAIL_ADDR +x40590003 PR_CREATOR_FLAGS +x405a0003 PR_MODIFIER_FLAGS +x405b0003 PR_ORIGINATOR_FLAGS +x405c0003 PR_REPORT_DESTINATION_FLAGS +x405d0003 PR_ORIGINAL_AUTHOR_FLAGS +x40610102 PR_ORIGINATOR_SEARCH_KEY +x40640102 PR_REPORT_DESTINATION_SEARCH_KEY +x40650003 PR_ER_FLAG +x40680102 PR_INTERNET_SUBJECT +x40690102 PR_INTERNET_SENT_REPRESENTING_NAME +x59020003 PR_INET_MAIL_OVERRIDE_FORMAT +x59090003 PR_MSG_EDITOR_FORMAT +x60010003 PR_DOTSTUFF_STATE +x65a00014 PR_RULE_SERVER_RULE_ID +x65c20102 PR_REPLY_TEMPLATE_ID +x65e00102 PR_SOURCE_KEY +x65e10102 PR_PARENT_SOURCE_KEY +x65e20102 PR_CHANGE_KEY +x65e30102 PR_PREDECESSOR_CHANGE_LIST +x65e40003 PR_SYNCHRONIZE_FLAGS +x65e5000b PR_AUTO_ADD_NEW_SUBS +x65e6000b PR_NEW_SUBS_GET_AUTO_ADD +x65e7001f PR_MESSAGE_SITE_NAME +x65e8000b PR_MESSAGE_PROCESSED +x65e90003 PR_RULE_MSG_STATE +x65ea0003 PR_RULE_MSG_USER_FLAGS +x65eb001f PR_RULE_MSG_PROVIDER +x65ec001f PR_RULE_MSG_NAME +x65ed0003 PR_RULE_MSG_LEVEL +x65ee0102 PR_RULE_MSG_PROVIDER_DATA +x65ef0102 PR_RULE_MSG_ACTIONS +x65f00102 PR_RULE_MSG_CONDITION +x65f10003 PR_RULE_MSG_CONDITION_LCID +x65f20002 PR_RULE_MSG_VERSION +x65f30003 PR_RULE_MSG_SEQUENCE +x65f4000b PR_PREVENT_MSG_CREATE +x65f50040 PR_IMAP_INTERNAL_DATE +x66000003 PR_PROFILE_VERSION +x66010003 PR_PROFILE_CONFIG_FLAGS +x6602001f PR_PROFILE_HOME_SERVER +x6603001f PR_PROFILE_USER +x66040003 PR_PROFILE_CONNECT_FLAGS +x66050003 PR_PROFILE_TRANSPORT_FLAGS +x66060003 PR_PROFILE_UI_STATE +x6607001f PR_PROFILE_UNRESOLVED_NAME +x6608001f PR_PROFILE_UNRESOLVED_SERVER +x66090003 PR_PROFILE_OPEN_FLAGS +x6609001f PR_PROFILE_BINDING_ORDER +x660a0003 PR_PROFILE_TYPE +x660b001f PR_PROFILE_MAILBOX +x660c001f PR_PROFILE_SERVER +x660d0003 PR_PROFILE_MAX_RESTRICT +x660e001f PR_PROFILE_AB_FILES_PATH +x660f001f PR_PROFILE_FAVFLD_DISPLAY_NAME +x6610001f PR_PROFILE_OFFLINE_STORE_PATH +x66110102 PR_PROFILE_OFFLINE_INFO +x6612001f PR_PROFILE_HOME_SERVER_DN +x6613101f PR_PROFILE_HOME_SERVER_ADDRS +x6614001f PR_PROFILE_SERVER_DN +x6615001f PR_PROFILE_FAVFLD_COMMENT +x6616001f PR_PROFILE_ALLPUB_DISPLAY_NAME +x6617001f PR_PROFILE_ALLPUB_COMMENT +x66180003 PR_DISABLE_WINSOCK +x6618000b PR_IN_TRANSIT +x66190003 PR_PROFILE_AUTH_PACKAGE +x66190102 PR_USER_ENTRYID +x661a001f PR_USER_NAME +x661b0102 PR_MAILBOX_OWNER_ENTRYID +x661c001f PR_MAILBOX_OWNER_NAME +x661d000b PR_OOF_STATE +x661e0102 PR_SCHEDULE_FOLDER_ENTRYID +x661f0102 PR_IPM_DAF_ENTRYID +x66200102 PR_NON_IPM_SUBTREE_ENTRYID +x66210102 PR_EFORMS_REGISTRY_ENTRYID +x66220102 PR_SPLUS_FREE_BUSY_ENTRYID +x6623001f PR_HIERARCHY_SERVER +x66230102 PR_OFFLINE_ADDRBOOK_ENTRYID +x66240102 PR_EFORMS_FOR_LOCALE_ENTRYID +x66250102 PR_FREE_BUSY_FOR_LOCAL_SITE_ENTRYID +x66260102 PR_ADDRBOOK_FOR_LOCAL_SITE_ENTRYID +x66270102 PR_OFFLINE_MESSAGE_ENTRYID +x66280102 PR_GW_MTSIN_ENTRYID +x66290102 PR_GW_MTSOUT_ENTRYID +x662a000b PR_TRANSFER_ENABLED +x662b0102 PR_TEST_LINE_SPEED +x662c000d PR_HIERARCHY_SYNCHRONIZER +x662d000d PR_CONTENTS_SYNCHRONIZER +x662e000d PR_COLLECTOR +x662f000d PR_FAST_TRANSFER +x66300102 PR_IPM_FAVORITES_ENTRYID +x66310102 PR_IPM_PUBLIC_FOLDERS_ENTRYID +x6632000b PR_STORE_OFFLINE +x6634000d PR_CHANGE_ADVISOR +x6635001f PR_FAVORITES_DEFAULT_NAME +x66360102 PR_SYS_CONFIG_FOLDER_ENTRYID +x66370048 PR_CHANGE_NOTIFICATION_GUID +x66380003 PR_FOLDER_CHILD_COUNT +x66390003 PR_RIGHTS +x663a000b PR_HAS_RULES +x663b0102 PR_ADDRESS_BOOK_ENTRYID +x663c0102 PR_PUBLIC_FOLDER_ENTRYID +x663d0003 PR_OFFLINE_FLAGS +x663e0003 PR_HIERARCHY_CHANGE_NUM +x663f000b PR_HAS_MODERATOR_RULES +x66400003 PR_DELETED_MSG_COUNT +x66410003 PR_DELETED_FOLDER_COUNT +x66420040 PR_OLDEST_DELETED_ON +x66430003 PR_DELETED_ASSOC_MSG_COUNT +x6644001f PR_REPLICA_SERVER +x66450102 PR_CLIENT_ACTIONS +x66460102 PR_DAM_ORIGINAL_ENTRYID +x6647000b PR_DAM_BACK_PATCHED +x66480003 PR_RULE_ERROR +x66490003 PR_RULE_ACTION_TYPE +x664a000b PR_HAS_NAMED_PROPERTIES +x664b0014 PR_REPLICA_VERSION +x66500003 PR_RULE_ACTION_NUMBER +x66510102 PR_RULE_FOLDER_ENTRYID +x66520102 PR_ACTIVE_USER_ENTRYID +x66530003 PR_X400_ENVELOPE_TYPE +x66540040 PR_MSG_FOLD_TIME +x66550102 PR_ICS_CHANGE_KEY +x66580003 PR_GW_ADMIN_OPERATIONS +x66590102 PR_INTERNET_CONTENT +x665a000b PR_HAS_ATTACH_FROM_IMAIL +x665b001f PR_ORIGINATOR_NAME +x665c001f PR_ORIGINATOR_ADDR +x665d001f PR_ORIGINATOR_ADDRTYPE +x665e0102 PR_ORIGINATOR_ENTRYID +x665f0040 PR_ARRIVAL_TIME +x66600102 PR_TRACE_INFO +x66610102 PR_SUBJECT_TRACE_INFO +x66620003 PR_RECIPIENT_NUMBER +x66630102 PR_MTS_SUBJECT_ID +x6664001f PR_REPORT_DESTINATION_NAME +x66650102 PR_REPORT_DESTINATION_ENTRYID +x66660102 PR_CONTENT_SEARCH_KEY +x66670102 PR_FOREIGN_ID +x66680102 PR_FOREIGN_REPORT_ID +x66690102 PR_FOREIGN_SUBJECT_ID +x666a0102 PR_INTERNAL_TRACE_INFO +x666a0102 PR_PROMOTE_PROP_ID_LIST +x666c000b PR_IN_CONFLICT +x66700102 PR_LONGTERM_ENTRYID_FROM_TABLE +x66710014 PR_MEMBER_ID +x6672001f PR_MEMBER_NAME +x66730003 PR_MEMBER_RIGHTS +x66740014 PR_RULE_ID +x66750102 PR_RULE_IDS +x66760003 PR_RULE_SEQUENCE +x66770003 PR_RULE_STATE +x66780003 PR_RULE_USER_FLAGS +x667900fd PR_RULE_CONDITION +x667b001f PR_PROFILE_MOAB +x667c001f PR_PROFILE_MOAB_GUID +x667d0003 PR_PROFILE_MOAB_SEQ +x667f1102 PR_IMPLIED_RESTRICTIONS +x668000fe PR_RULE_ACTIONS +x6681001f PR_RULE_PROVIDER +x6682001f PR_RULE_NAME +x66830003 PR_RULE_LEVEL +x66840102 PR_RULE_PROVIDER_DATA +x66850040 PR_LAST_FULL_BACKUP +x66870102 PR_PROFILE_ADDR_INFO +x66890102 PR_PROFILE_OPTIONS_DATA +x668a0102 PR_EVENTS_ROOT_FOLDER_ENTRYID +x668a0102 PR_NNTP_ARTICLE_FOLDER_ENTRYID +x668b0102 PR_NNTP_CONTROL_FOLDER_ENTRYID +x668c0102 PR_NEWSGROUP_ROOT_FOLDER_ENTRYID +x668d001f PR_INBOUND_NEWSFEED_DN +x668e001f PR_OUTBOUND_NEWSFEED_DN +x668f0040 PR_DELETED_ON +x66900003 PR_REPLICATION_STYLE +x66910102 PR_REPLICATION_SCHEDULE +x66920003 PR_REPLICATION_MESSAGE_PRIORITY +x66930003 PR_OVERALL_MSG_AGE_LIMIT +x66940003 PR_REPLICATION_ALWAYS_INTERVAL +x66950003 PR_REPLICATION_MSG_SIZE +x6696000b PR_IS_NEWSGROUP_ANCHOR +x6697000b PR_IS_NEWSGROUP +x66980102 PR_REPLICA_LIST +x66990003 PR_OVERALL_AGE_LIMIT +x669a001f PR_INTERNET_CHARSET +x669b0014 PR_DELETED_MESSAGE_SIZE_EXTENDED +x669c0014 PR_DELETED_NORMAL_MESSAGE_SIZE_EXTENDED +x669d0014 PR_DELETED_ASSOC_MESSAGE_SIZE_EXTENDED +x669e000b PR_SECURE_IN_SITE +x66a0001f PR_NT_USER_NAME +x66a10003 PR_LOCALE_ID +x66a20040 PR_LAST_LOGON_TIME +x66a30040 PR_LAST_LOGOFF_TIME +x66a40003 PR_STORAGE_LIMIT_INFORMATION +x66a5001f PR_NEWSGROUP_COMPONENT +x66a60102 PR_NEWSFEED_INFO +x66a7001f PR_INTERNET_NEWSGROUP_NAME +x66a80003 PR_FOLDER_FLAGS +x66a90040 PR_LAST_ACCESS_TIME +x66aa0003 PR_RESTRICTION_COUNT +x66ab0003 PR_CATEG_COUNT +x66ac0003 PR_CACHED_COLUMN_COUNT +x66ad0003 PR_NORMAL_MSG_W_ATTACH_COUNT +x66ae0003 PR_ASSOC_MSG_W_ATTACH_COUNT +x66af0003 PR_RECIPIENT_ON_NORMAL_MSG_COUNT +x66b00003 PR_RECIPIENT_ON_ASSOC_MSG_COUNT +x66b10003 PR_ATTACH_ON_NORMAL_MSG_COUNT +x66b20003 PR_ATTACH_ON_ASSOC_MSG_COUNT +x66b30003 PR_NORMAL_MESSAGE_SIZE +x66b30014 PR_NORMAL_MESSAGE_SIZE_EXTENDED +x66b40003 PR_ASSOC_MESSAGE_SIZE +x66b40014 PR_ASSOC_MESSAGE_SIZE_EXTENDED +x66b5001f PR_FOLDER_PATHNAME +x66b60003 PR_OWNER_COUNT +x66b70003 PR_CONTACT_COUNT +x66c30003 PR_CODE_PAGE_ID +x66c40003 PR_RETENTION_AGE_LIMIT +x66c5000b PR_DISABLE_PERUSER_READ +x66c60102 PR_INTERNET_PARSE_STATE +x66c70102 PR_INTERNET_MESSAGE_INFO +x6700001f PR_PST_PATH +x6701000b PR_PST_REMEMBER_PW +x67020003 PR_OST_ENCRYPTION +x67020003 PR_PST_ENCRYPTION +x6703001f PR_PST_PW_SZ_OLD +x6704001f PR_PST_PW_SZ_NEW +x67050003 PR_SORT_LOCALE_ID +x6707001f PR_URL_NAME +x67090040 PR_LOCAL_COMMIT_TIME +x670a0040 PR_LOCAL_COMMIT_TIME_MAX +x670b0003 PR_DELETED_COUNT_TOTAL +x670c0048 PR_AUTO_RESET +x67100003 PR_URL_COMP_NAME_HASH +x67110003 PR_MSG_FOLDER_TEMPLATE_RES_2 +x67120003 PR_RANK +x6713000b PR_MSG_FOLDER_TEMPLATE_RES_4 +x6714000b PR_MSG_FOLDER_TEMPLATE_RES_5 +x6715000b PR_MSG_FOLDER_TEMPLATE_RES_6 +x67160102 PR_MSG_FOLDER_TEMPLATE_RES_7 +x67170102 PR_MSG_FOLDER_TEMPLATE_RES_8 +x67180102 PR_MSG_FOLDER_TEMPLATE_RES_9 +x6719001f PR_MSG_FOLDER_TEMPLATE_RES_10 +x671a001f PR_MSG_FOLDER_TEMPLATE_RES_11 +x671b001f PR_MSG_FOLDER_TEMPLATE_RES_12 +x671e000b PR_PF_PLATINUM_HOME_MDB +x671f000b PR_PF_PROXY_REQUIRED +x67200102 PR_INTERNET_FREE_DOC_INFO +x67210003 PR_PF_OVER_HARD_QUOTA_LIMIT +x67220003 PR_PF_MSG_SIZE_LIMIT +x67430003 PR_CONNECTION_MODULUS +x6744001f PR_DELIVER_TO_DN +x67460003 PR_MIME_SIZE +x67470014 PR_FILE_SIZE_EXTENDED +x67480014 PR_FID +x67490014 PR_PARENT_FID +x674a0014 PR_MID +x674b0014 PR_CATEG_ID +x674c0014 PR_PARENT_CATEG_ID +x674d0014 PR_INST_ID +x674e0003 PR_INSTANCE_NUM +x674f0014 PR_ADDRBOOK_MID +x67500003 PR_ICS_NOTIF +x67510003 PR_ARTICLE_NUM_NEXT +x67520003 PR_IMAP_LAST_ARTICLE_ID +x6753000b PR_NOT_822_RENDERABLE +x67580102 PR_LTID +x67590102 PR_CN_EXPORT +x675a0102 PR_PCL_EXPORT +x675b1102 PR_CN_MV_EXPORT +x67790003 PR_PF_QUOTA_STYLE +x677b0003 PR_PF_STORAGE_QUOTA +x67830003 PR_SEARCH_FLAGS +x67aa000b PR_ASSOCIATED +x67f00102 PR_PROFILE_SECURE_MAILBOX +x6800001f PR_MAILBEAT_BOUNCE_SERVER +x68010040 PR_MAILBEAT_REQUEST_SENT +x6802001f PR_USENET_SITE_NAME +x68030040 PR_MAILBEAT_REQUEST_RECEIVED +x68040040 PR_MAILBEAT_REQUEST_PROCESSED +x68060040 PR_MAILBEAT_REPLY_SENT +x68070040 PR_MAILBEAT_REPLY_SUBMIT +x68080040 PR_MAILBEAT_REPLY_RECEIVED +x68090040 PR_MAILBEAT_REPLY_PROCESSED +x6844101f PR_DELEGATES_DISPLAY_NAMES +x68451102 PR_DELEGATES_ENTRYIDS +x68470003 PR_FREEBUSY_START_RANGE +x68480003 PR_FREEBUSY_END_RANGE +x6849001f PR_FREEBUSY_EMAIL_ADDRESS +x684f1003 PR_FREEBUSY_ALL_MONTHS +x68501102 PR_FREEBUSY_ALL_EVENTS +x68511003 PR_FREEBUSY_TENTATIVE_MONTHS +x68521102 PR_FREEBUSY_TENTATIVE_EVENTS +x68531003 PR_FREEBUSY_BUSY_MONTHS +x68541102 PR_FREEBUSY_BUSY_EVENTS +x68551003 PR_FREEBUSY_OOF_MONTHS +x68561102 PR_FREEBUSY_OOF_EVENTS +x68680040 PR_FREEBUSY_LAST_MODIFIED +x68690003 PR_FREEBUSY_NUM_MONTHS +x686b1003 PR_DELEGATES_SEE_PRIVATE +x686c0102 PR_PERSONAL_FREEBUSY +x686d000b PR_PROCESS_MEETING_REQUESTS +x686e000b PR_DECLINE_RECURRING_MEETING_REQUESTS +x686f000b PR_DECLINE_CONFLICTING_MEETING_REQUESTS +x70010102 PR_VD_BINARY +x7002001f PR_VD_STRINGS +x70030003 PR_VD_FLAGS +x70040102 PR_VD_LINK_TO +x70050102 PR_VD_VIEW_FOLDER +x7006001f PR_VD_NAME +x70070003 PR_VD_VERSION +x7c00001f PR_FAV_DISPLAY_NAME +x7c00001f PR_FAV_DISPLAY_ALIAS +x7c020102 PR_FAV_PUBLIC_SOURCE_KEY +x7c040102 PR_OST_OSTID +x7c0a000b PR_STORE_SLOWLINK +x7d010003 PR_FAV_AUTOSUBFOLDERS +x7d020102 PR_FAV_PARENT_SOURCE_KEY +x7d030003 PR_FAV_LEVEL_MASK +x7d070003 PR_FAV_INHERIT_AUTO +x7d080102 PR_FAV_DEL_SUBS +x7ffa0003 PR_ATTACHMENT_LINKID +x7ffb0040 PR_EXCEPTION_STARTTIME +x7ffc0040 PR_EXCEPTION_ENDTIME +x7ffd0003 PR_ATTACHMENT_FLAGS +x7ffe000b PR_ATTACHMENT_HIDDEN +x8001000b PR_EMS_AB_DISPLAY_NAME_OVERRIDE +x80031102 PR_EMS_AB_CA_CERTIFICATE +x8004001f PR_EMS_AB_FOLDER_PATHNAME +x8005000d PR_EMS_AB_MANAGER +x8005001f PR_EMS_AB_MANAGER_T +x8006000d PR_EMS_AB_HOME_MDB_O +x8006001f PR_EMS_AB_HOME_MDB +x8007000d PR_EMS_AB_HOME_MTA_O +x8007001f PR_EMS_AB_HOME_MTA +x8008000d PR_EMS_AB_IS_MEMBER_OF_DL +x8008001f PR_EMS_AB_IS_MEMBER_OF_DL_T +x8009000d PR_EMS_AB_MEMBER +x8009001f PR_EMS_AB_MEMBER_T +x800a001f PR_EMS_AB_AUTOREPLY_MESSAGE +x800b000b PR_EMS_AB_AUTOREPLY +x800c000d PR_EMS_AB_OWNER_O +x800c001f PR_EMS_AB_OWNER +x800d000d PR_EMS_AB_KM_SERVER_O +x800d001f PR_EMS_AB_KM_SERVER +x800e000d PR_EMS_AB_REPORTS +x800e000d PR_EMS_AB_REPORTS_T +x800f101f PR_EMS_AB_PROXY_ADDRESSES +x80100102 PR_EMS_AB_HELP_DATA32 +x8011001f PR_EMS_AB_TARGET_ADDRESS +x8012101f PR_EMS_AB_TELEPHONE_NUMBER +x80130102 PR_EMS_AB_NT_SECURITY_DESCRIPTOR +x8014000d PR_EMS_AB_HOME_MDB_BL_O +x8014101f PR_EMS_AB_HOME_MDB_BL +x8015000d PR_EMS_AB_PUBLIC_DELEGATES +x8015001f PR_EMS_AB_PUBLIC_DELEGATES_T +x80160102 PR_EMS_AB_CERTIFICATE_REVOCATION_LIST +x80170102 PR_EMS_AB_ADDRESS_ENTRY_DISPLAY_TABLE +x80180102 PR_EMS_AB_ADDRESS_SYNTAX +x80230102 PR_EMS_AB_BUSINESS_ROLES +x8024000d PR_EMS_AB_OWNER_BL_O +x8024101f PR_EMS_AB_OWNER_BL +x80251102 PR_EMS_AB_CROSS_CERTIFICATE_PAIR +x80261102 PR_EMS_AB_AUTHORITY_REVOCATION_LIST +x80270102 PR_EMS_AB_ASSOC_NT_ACCOUNT +x80280040 PR_EMS_AB_EXPIRATION_TIME +x80290003 PR_EMS_AB_USN_CHANGED +x802d001f PR_EMS_AB_EXTENSION_ATTRIBUTE_1 +x802e001f PR_EMS_AB_EXTENSION_ATTRIBUTE_2 +x802f001f PR_EMS_AB_EXTENSION_ATTRIBUTE_3 +x8030001f PR_EMS_AB_EXTENSION_ATTRIBUTE_4 +x8031001f PR_EMS_AB_EXTENSION_ATTRIBUTE_5 +x8032001f PR_EMS_AB_EXTENSION_ATTRIBUTE_6 +x8033001f PR_EMS_AB_EXTENSION_ATTRIBUTE_7 +x8034001f PR_EMS_AB_EXTENSION_ATTRIBUTE_8 +x8035001f PR_EMS_AB_EXTENSION_ATTRIBUTE_9 +x8036001f PR_EMS_AB_EXTENSION_ATTRIBUTE_10 +x80371102 PR_EMS_AB_SECURITY_PROTOCOL +x8038000d PR_EMS_AB_PF_CONTACTS_O +x8038101f PR_EMS_AB_PF_CONTACTS +x803a0102 PR_EMS_AB_HELP_DATA16 +x803b001f PR_EMS_AB_HELP_FILE_NAME +x803c000d PR_EMS_AB_OBJ_DIST_NAME_O +x803c001f PR_EMS_AB_OBJ_DIST_NAME +x803d001f PR_EMS_AB_ENCRYPT_ALG_SELECTED_OTHER +x803e001f PR_EMS_AB_AUTOREPLY_SUBJECT +x803f000d PR_EMS_AB_HOME_PUBLIC_SERVER_O +x803f001f PR_EMS_AB_HOME_PUBLIC_SERVER +x8040101f PR_EMS_AB_ENCRYPT_ALG_LIST_NA +x8041101f PR_EMS_AB_ENCRYPT_ALG_LIST_OTHER +x8042001f PR_EMS_AB_IMPORTED_FROM +x8043001f PR_EMS_AB_ENCRYPT_ALG_SELECTED_NA +x80440003 PR_EMS_AB_ACCESS_CATEGORY +x80450102 PR_EMS_AB_ACTIVATION_SCHEDULE +x80460003 PR_EMS_AB_ACTIVATION_STYLE +x80470102 PR_EMS_AB_ADDRESS_ENTRY_DISPLAY_TABLE_MSDOS +x8048001f PR_EMS_AB_ADDRESS_TYPE +x8049001f PR_EMS_AB_ADMD +x804a001f PR_EMS_AB_ADMIN_DESCRIPTION +x804b001f PR_EMS_AB_ADMIN_DISPLAY_NAME +x804c001f PR_EMS_AB_ADMIN_EXTENSION_DLL +x804d000d PR_EMS_AB_ALIASED_OBJECT_NAME_O +x804d001f PR_EMS_AB_ALIASED_OBJECT_NAME +x804e000d PR_EMS_AB_ALT_RECIPIENT_O +x804e001f PR_EMS_AB_ALT_RECIPIENT +x804f000d PR_EMS_AB_ALT_RECIPIENT_BL_O +x804f101f PR_EMS_AB_ALT_RECIPIENT_BL +x80500102 PR_EMS_AB_ANCESTOR_ID +x8051000d PR_EMS_AB_ASSOC_REMOTE_DXA_O +x8051101f PR_EMS_AB_ASSOC_REMOTE_DXA +x80520003 PR_EMS_AB_ASSOCIATION_LIFETIME +x8053000d PR_EMS_AB_AUTH_ORIG_BL_O +x8053101f PR_EMS_AB_AUTH_ORIG_BL +x8054001f PR_EMS_AB_AUTHORIZED_DOMAIN +x80550102 PR_EMS_AB_AUTHORIZED_PASSWORD +x8056001f PR_EMS_AB_AUTHORIZED_USER +x8057101f PR_EMS_AB_BUSINESS_CATEGORY +x8058000d PR_EMS_AB_CAN_CREATE_PF_O +x8058101f PR_EMS_AB_CAN_CREATE_PF +x8059000d PR_EMS_AB_CAN_CREATE_PF_BL_O +x8059101f PR_EMS_AB_CAN_CREATE_PF_BL +x805a000d PR_EMS_AB_CAN_CREATE_PF_DL_O +x805a101f PR_EMS_AB_CAN_CREATE_PF_DL +x805b000d PR_EMS_AB_CAN_CREATE_PF_DL_BL_O +x805b101f PR_EMS_AB_CAN_CREATE_PF_DL_BL +x805c000d PR_EMS_AB_CAN_NOT_CREATE_PF_O +x805c101f PR_EMS_AB_CAN_NOT_CREATE_PF +x805d000d PR_EMS_AB_CAN_NOT_CREATE_PF_BL_O +x805d101f PR_EMS_AB_CAN_NOT_CREATE_PF_BL +x805e000d PR_EMS_AB_CAN_NOT_CREATE_PF_DL_O +x805e101f PR_EMS_AB_CAN_NOT_CREATE_PF_DL +x805f000d PR_EMS_AB_CAN_NOT_CREATE_PF_DL_BL_O +x805f101f PR_EMS_AB_CAN_NOT_CREATE_PF_DL_BL +x8060000b PR_EMS_AB_CAN_PRESERVE_DNS +x80610003 PR_EMS_AB_CLOCK_ALERT_OFFSET +x8062000b PR_EMS_AB_CLOCK_ALERT_REPAIR +x80630003 PR_EMS_AB_CLOCK_WARNING_OFFSET +x8064000b PR_EMS_AB_CLOCK_WARNING_REPAIR +x8065001f PR_EMS_AB_COMPUTER_NAME +x8066101f PR_EMS_AB_CONNECTED_DOMAINS +x80670003 PR_EMS_AB_CONTAINER_INFO +x80680003 PR_EMS_AB_COST +x8069001f PR_EMS_AB_COUNTRY_NAME +x806a0003 PR_EMS_AB_DELIV_CONT_LENGTH +x806b1102 PR_EMS_AB_DELIV_EITS +x806c1102 PR_EMS_AB_DELIV_EXT_CONT_TYPES +x806d000b PR_EMS_AB_DELIVER_AND_REDIRECT +x806e0003 PR_EMS_AB_DELIVERY_MECHANISM +x806f101f PR_EMS_AB_DESCRIPTION +x8070101f PR_EMS_AB_DESTINATION_INDICATOR +x8071001f PR_EMS_AB_DIAGNOSTIC_REG_KEY +x8072000d PR_EMS_AB_DL_MEM_REJECT_PERMS_BL_O +x8072101f PR_EMS_AB_DL_MEM_REJECT_PERMS_BL +x8073000d PR_EMS_AB_DL_MEM_SUBMIT_PERMS_BL_O +x8073101f PR_EMS_AB_DL_MEM_SUBMIT_PERMS_BL +x80741102 PR_EMS_AB_DL_MEMBER_RULE +x8075000d PR_EMS_AB_DOMAIN_DEF_ALT_RECIP_O +x8075001f PR_EMS_AB_DOMAIN_DEF_ALT_RECIP +x8076001f PR_EMS_AB_DOMAIN_NAME +x80770102 PR_EMS_AB_DSA_SIGNATURE +x8078000b PR_EMS_AB_DXA_ADMIN_COPY +x8079000b PR_EMS_AB_DXA_ADMIN_FORWARD +x807a0003 PR_EMS_AB_DXA_ADMIN_UPDATE +x807b000b PR_EMS_AB_DXA_APPEND_REQCN +x807c000d PR_EMS_AB_DXA_CONF_CONTAINER_LIST_O +x807c101f PR_EMS_AB_DXA_CONF_CONTAINER_LIST +x807d0040 PR_EMS_AB_DXA_CONF_REQ_TIME +x807e001f PR_EMS_AB_DXA_CONF_SEQ +x807f0003 PR_EMS_AB_DXA_CONF_SEQ_USN +x80800003 PR_EMS_AB_DXA_EXCHANGE_OPTIONS +x8081000b PR_EMS_AB_DXA_EXPORT_NOW +x80820003 PR_EMS_AB_DXA_FLAGS +x8083001f PR_EMS_AB_DXA_IMP_SEQ +x80840040 PR_EMS_AB_DXA_IMP_SEQ_TIME +x80850003 PR_EMS_AB_DXA_IMP_SEQ_USN +x8086000b PR_EMS_AB_DXA_IMPORT_NOW +x8087101f PR_EMS_AB_DXA_IN_TEMPLATE_MAP +x8088000d PR_EMS_AB_DXA_LOCAL_ADMIN_O +x8088001f PR_EMS_AB_DXA_LOCAL_ADMIN +x80890003 PR_EMS_AB_DXA_LOGGING_LEVEL +x808a001f PR_EMS_AB_DXA_NATIVE_ADDRESS_TYPE +x808b101f PR_EMS_AB_DXA_OUT_TEMPLATE_MAP +x808c001f PR_EMS_AB_DXA_PASSWORD +x808d0003 PR_EMS_AB_DXA_PREV_EXCHANGE_OPTIONS +x808e000b PR_EMS_AB_DXA_PREV_EXPORT_NATIVE_ONLY +x808f0003 PR_EMS_AB_DXA_PREV_IN_EXCHANGE_SENSITIVITY +x8090000d PR_EMS_AB_DXA_PREV_REMOTE_ENTRIES_O +x8090001f PR_EMS_AB_DXA_PREV_REMOTE_ENTRIES +x80910003 PR_EMS_AB_DXA_PREV_REPLICATION_SENSITIVITY +x80920003 PR_EMS_AB_DXA_PREV_TEMPLATE_OPTIONS +x80930003 PR_EMS_AB_DXA_PREV_TYPES +x8094001f PR_EMS_AB_DXA_RECIPIENT_CP +x8095000d PR_EMS_AB_DXA_REMOTE_CLIENT_O +x8095001f PR_EMS_AB_DXA_REMOTE_CLIENT +x8096001f PR_EMS_AB_DXA_REQ_SEQ +x80970040 PR_EMS_AB_DXA_REQ_SEQ_TIME +x80980003 PR_EMS_AB_DXA_REQ_SEQ_USN +x8099001f PR_EMS_AB_DXA_REQNAME +x809a001f PR_EMS_AB_DXA_SVR_SEQ +x809b0040 PR_EMS_AB_DXA_SVR_SEQ_TIME +x809c0003 PR_EMS_AB_DXA_SVR_SEQ_USN +x809d0003 PR_EMS_AB_DXA_TASK +x809e0003 PR_EMS_AB_DXA_TEMPLATE_OPTIONS +x809f0040 PR_EMS_AB_DXA_TEMPLATE_TIMESTAMP +x80a00003 PR_EMS_AB_DXA_TYPES +x80a1000d PR_EMS_AB_DXA_UNCONF_CONTAINER_LIST_O +x80a1101f PR_EMS_AB_DXA_UNCONF_CONTAINER_LIST +x80a20003 PR_EMS_AB_ENCAPSULATION_METHOD +x80a3000b PR_EMS_AB_ENCRYPT +x80a4000b PR_EMS_AB_EXPAND_DLS_LOCALLY +x80a5000d PR_EMS_AB_EXPORT_CONTAINERS_O +x80a5101f PR_EMS_AB_EXPORT_CONTAINERS +x80a6000b PR_EMS_AB_EXPORT_CUSTOM_RECIPIENTS +x80a7000b PR_EMS_AB_EXTENDED_CHARS_ALLOWED +x80a81102 PR_EMS_AB_EXTENSION_DATA +x80a9101f PR_EMS_AB_EXTENSION_NAME +x80aa101f PR_EMS_AB_EXTENSION_NAME_INHERITED +x80ab1102 PR_EMS_AB_FACSIMILE_TELEPHONE_NUMBER +x80ac0102 PR_EMS_AB_FILE_VERSION +x80ad000b PR_EMS_AB_FILTER_LOCAL_ADDRESSES +x80ae000d PR_EMS_AB_FOLDERS_CONTAINER_O +x80ae001f PR_EMS_AB_FOLDERS_CONTAINER +x80af0003 PR_EMS_AB_GARBAGE_COLL_PERIOD +x80b0001f PR_EMS_AB_GATEWAY_LOCAL_CRED +x80b1001f PR_EMS_AB_GATEWAY_LOCAL_DESIG +x80b2101f PR_EMS_AB_GATEWAY_PROXY +x80b30102 PR_EMS_AB_GATEWAY_ROUTING_TREE +x80b40040 PR_EMS_AB_GWART_LAST_MODIFIED +x80b5000d PR_EMS_AB_HAS_FULL_REPLICA_NCS_O +x80b5101f PR_EMS_AB_HAS_FULL_REPLICA_NCS +x80b6000d PR_EMS_AB_HAS_MASTER_NCS_O +x80b6101f PR_EMS_AB_HAS_MASTER_NCS +x80b70003 PR_EMS_AB_HEURISTICS +x80b8000b PR_EMS_AB_HIDE_DL_MEMBERSHIP +x80b9000b PR_EMS_AB_HIDE_FROM_ADDRESS_BOOK +x80ba000d PR_EMS_AB_IMPORT_CONTAINER_O +x80ba001f PR_EMS_AB_IMPORT_CONTAINER +x80bb0003 PR_EMS_AB_IMPORT_SENSITIVITY +x80bc000d PR_EMS_AB_INBOUND_SITES_O +x80bc101f PR_EMS_AB_INBOUND_SITES +x80bd0003 PR_EMS_AB_INSTANCE_TYPE +x80be101f PR_EMS_AB_INTERNATIONAL_ISDN_NUMBER +x80bf0102 PR_EMS_AB_INVOCATION_ID +x80c0000b PR_EMS_AB_IS_DELETED +x80c1000b PR_EMS_AB_IS_SINGLE_VALUED +x80c21102 PR_EMS_AB_KCC_STATUS +x80c3101f PR_EMS_AB_KNOWLEDGE_INFORMATION +x80c40003 PR_EMS_AB_LINE_WRAP +x80c50003 PR_EMS_AB_LINK_ID +x80c6001f PR_EMS_AB_LOCAL_BRIDGE_HEAD +x80c7001f PR_EMS_AB_LOCAL_BRIDGE_HEAD_ADDRESS +x80c8000b PR_EMS_AB_LOCAL_INITIAL_TURN +x80c9000d PR_EMS_AB_LOCAL_SCOPE_O +x80c9101f PR_EMS_AB_LOCAL_SCOPE +x80ca001f PR_EMS_AB_LOG_FILENAME +x80cb0003 PR_EMS_AB_LOG_ROLLOVER_INTERVAL +x80cc000b PR_EMS_AB_MAINTAIN_AUTOREPLY_HISTORY +x80cd0003 PR_EMS_AB_MAPI_DISPLAY_TYPE +x80ce0003 PR_EMS_AB_MAPI_ID +x80cf0003 PR_EMS_AB_MDB_BACKOFF_INTERVAL +x80d00003 PR_EMS_AB_MDB_MSG_TIME_OUT_PERIOD +x80d10003 PR_EMS_AB_MDB_OVER_QUOTA_LIMIT +x80d20003 PR_EMS_AB_MDB_STORAGE_QUOTA +x80d30003 PR_EMS_AB_MDB_UNREAD_LIMIT +x80d4000b PR_EMS_AB_MDB_USE_DEFAULTS +x80d5000b PR_EMS_AB_MESSAGE_TRACKING_ENABLED +x80d6000b PR_EMS_AB_MONITOR_CLOCK +x80d7000b PR_EMS_AB_MONITOR_SERVERS +x80d8000b PR_EMS_AB_MONITOR_SERVICES +x80d9000d PR_EMS_AB_MONITORED_CONFIGURATIONS_O +x80d9101f PR_EMS_AB_MONITORED_CONFIGURATIONS +x80da000d PR_EMS_AB_MONITORED_SERVERS_O +x80da101f PR_EMS_AB_MONITORED_SERVERS +x80db101f PR_EMS_AB_MONITORED_SERVICES +x80dc0003 PR_EMS_AB_MONITORING_ALERT_DELAY +x80dd0003 PR_EMS_AB_MONITORING_ALERT_UNITS +x80de0003 PR_EMS_AB_MONITORING_AVAILABILITY_STYLE +x80df0102 PR_EMS_AB_MONITORING_AVAILABILITY_WINDOW +x80e0000d PR_EMS_AB_MONITORING_CACHED_VIA_MAIL_O +x80e0101f PR_EMS_AB_MONITORING_CACHED_VIA_MAIL +x80e1000d PR_EMS_AB_MONITORING_CACHED_VIA_RPC_O +x80e1101f PR_EMS_AB_MONITORING_CACHED_VIA_RPC +x80e21102 PR_EMS_AB_MONITORING_ESCALATION_PROCEDURE +x80e30003 PR_EMS_AB_MONITORING_HOTSITE_POLL_INTERVAL +x80e40003 PR_EMS_AB_MONITORING_HOTSITE_POLL_UNITS +x80e50003 PR_EMS_AB_MONITORING_MAIL_UPDATE_INTERVAL +x80e60003 PR_EMS_AB_MONITORING_MAIL_UPDATE_UNITS +x80e70003 PR_EMS_AB_MONITORING_NORMAL_POLL_INTERVAL +x80e80003 PR_EMS_AB_MONITORING_NORMAL_POLL_UNITS +x80e9000d PR_EMS_AB_MONITORING_RECIPIENTS_O +x80e9101f PR_EMS_AB_MONITORING_RECIPIENTS +x80ea000d PR_EMS_AB_MONITORING_RECIPIENTS_NDR_O +x80ea101f PR_EMS_AB_MONITORING_RECIPIENTS_NDR +x80eb0003 PR_EMS_AB_MONITORING_RPC_UPDATE_INTERVAL +x80ec0003 PR_EMS_AB_MONITORING_RPC_UPDATE_UNITS +x80ed0003 PR_EMS_AB_MONITORING_WARNING_DELAY +x80ee0003 PR_EMS_AB_MONITORING_WARNING_UNITS +x80ef001f PR_EMS_AB_MTA_LOCAL_CRED +x80f0001f PR_EMS_AB_MTA_LOCAL_DESIG +x80f10102 PR_EMS_AB_N_ADDRESS +x80f20003 PR_EMS_AB_N_ADDRESS_TYPE +x80f3001f PR_EMS_AB_NT_MACHINE_NAME +x80f40003 PR_EMS_AB_NUM_OF_OPEN_RETRIES +x80f50003 PR_EMS_AB_NUM_OF_TRANSFER_RETRIES +x80f60003 PR_EMS_AB_OBJECT_CLASS_CATEGORY +x80f70003 PR_EMS_AB_OBJECT_VERSION +x80f8000d PR_EMS_AB_OFF_LINE_AB_CONTAINERS_O +x80f8101f PR_EMS_AB_OFF_LINE_AB_CONTAINERS +x80f90102 PR_EMS_AB_OFF_LINE_AB_SCHEDULE +x80fa000d PR_EMS_AB_OFF_LINE_AB_SERVER_O +x80fa001f PR_EMS_AB_OFF_LINE_AB_SERVER +x80fb0003 PR_EMS_AB_OFF_LINE_AB_STYLE +x80fc0003 PR_EMS_AB_OID_TYPE +x80fd0102 PR_EMS_AB_OM_OBJECT_CLASS +x80fe0003 PR_EMS_AB_OM_SYNTAX +x80ff000b PR_EMS_AB_OOF_REPLY_TO_ORIGINATOR +x81000003 PR_EMS_AB_OPEN_RETRY_INTERVAL +x8101101f PR_EMS_AB_ORGANIZATION_NAME +x8102101f PR_EMS_AB_ORGANIZATIONAL_UNIT_NAME +x81030102 PR_EMS_AB_ORIGINAL_DISPLAY_TABLE +x81040102 PR_EMS_AB_ORIGINAL_DISPLAY_TABLE_MSDOS +x8105000d PR_EMS_AB_OUTBOUND_SITES_O +x8105101f PR_EMS_AB_OUTBOUND_SITES +x81060102 PR_EMS_AB_P_SELECTOR +x81070102 PR_EMS_AB_P_SELECTOR_INBOUND +x81080102 PR_EMS_AB_PER_MSG_DIALOG_DISPLAY_TABLE +x81090102 PR_EMS_AB_PER_RECIP_DIALOG_DISPLAY_TABLE +x810a0102 PR_EMS_AB_PERIOD_REP_SYNC_TIMES +x810b0003 PR_EMS_AB_PERIOD_REPL_STAGGER +x810c1102 PR_EMS_AB_POSTAL_ADDRESS +x810d1003 PR_EMS_AB_PREFERRED_DELIVERY_METHOD +x810e001f PR_EMS_AB_PRMD +x810f001f PR_EMS_AB_PROXY_GENERATOR_DLL +x8110000d PR_EMS_AB_PUBLIC_DELEGATES_BL_O +x8110101f PR_EMS_AB_PUBLIC_DELEGATES_BL +x81110102 PR_EMS_AB_QUOTA_NOTIFICATION_SCHEDULE +x81120003 PR_EMS_AB_QUOTA_NOTIFICATION_STYLE +x81130003 PR_EMS_AB_RANGE_LOWER +x81140003 PR_EMS_AB_RANGE_UPPER +x8115001f PR_EMS_AB_RAS_CALLBACK_NUMBER +x8116001f PR_EMS_AB_RAS_PHONE_NUMBER +x8117001f PR_EMS_AB_RAS_PHONEBOOK_ENTRY_NAME +x8118001f PR_EMS_AB_RAS_REMOTE_SRVR_NAME +x81191102 PR_EMS_AB_REGISTERED_ADDRESS +x811a001f PR_EMS_AB_REMOTE_BRIDGE_HEAD +x811b001f PR_EMS_AB_REMOTE_BRIDGE_HEAD_ADDRESS +x811c000d PR_EMS_AB_REMOTE_OUT_BH_SERVER_O +x811c001f PR_EMS_AB_REMOTE_OUT_BH_SERVER +x811d000d PR_EMS_AB_REMOTE_SITE_O +x811d001f PR_EMS_AB_REMOTE_SITE +x811e0003 PR_EMS_AB_REPLICATION_SENSITIVITY +x811f0003 PR_EMS_AB_REPLICATION_STAGGER +x8120000b PR_EMS_AB_REPORT_TO_ORIGINATOR +x8121000b PR_EMS_AB_REPORT_TO_OWNER +x81220003 PR_EMS_AB_REQ_SEQ +x8123000d PR_EMS_AB_RESPONSIBLE_LOCAL_DXA_O +x8123001f PR_EMS_AB_RESPONSIBLE_LOCAL_DXA +x8124000d PR_EMS_AB_RID_SERVER_O +x8124001f PR_EMS_AB_RID_SERVER +x8125000d PR_EMS_AB_ROLE_OCCUPANT_O +x8125101f PR_EMS_AB_ROLE_OCCUPANT +x8126101f PR_EMS_AB_ROUTING_LIST +x81270003 PR_EMS_AB_RTS_CHECKPOINT_SIZE +x81280003 PR_EMS_AB_RTS_RECOVERY_TIMEOUT +x81290003 PR_EMS_AB_RTS_WINDOW_SIZE +x812a000d PR_EMS_AB_RUNS_ON_O +x812a101f PR_EMS_AB_RUNS_ON +x812b0102 PR_EMS_AB_S_SELECTOR +x812c0102 PR_EMS_AB_S_SELECTOR_INBOUND +x812d0003 PR_EMS_AB_SEARCH_FLAGS +x812e1102 PR_EMS_AB_SEARCH_GUIDE +x812f000d PR_EMS_AB_SEE_ALSO_O +x812f101f PR_EMS_AB_SEE_ALSO +x8130101f PR_EMS_AB_SERIAL_NUMBER +x81310003 PR_EMS_AB_SERVICE_ACTION_FIRST +x81320003 PR_EMS_AB_SERVICE_ACTION_OTHER +x81330003 PR_EMS_AB_SERVICE_ACTION_SECOND +x81340003 PR_EMS_AB_SERVICE_RESTART_DELAY +x8135001f PR_EMS_AB_SERVICE_RESTART_MESSAGE +x81360003 PR_EMS_AB_SESSION_DISCONNECT_TIMER +x8137101f PR_EMS_AB_SITE_AFFINITY +x8138101f PR_EMS_AB_SITE_PROXY_SPACE +x81390040 PR_EMS_AB_SPACE_LAST_COMPUTED +x813a001f PR_EMS_AB_STREET_ADDRESS +x813b000d PR_EMS_AB_SUB_REFS_O +x813b101f PR_EMS_AB_SUB_REFS +x813c0003 PR_EMS_AB_SUBMISSION_CONT_LENGTH +x813d1102 PR_EMS_AB_SUPPORTED_APPLICATION_CONTEXT +x813e000d PR_EMS_AB_SUPPORTING_STACK_O +x813e101f PR_EMS_AB_SUPPORTING_STACK +x813f000d PR_EMS_AB_SUPPORTING_STACK_BL_O +x813f101f PR_EMS_AB_SUPPORTING_STACK_BL +x81400102 PR_EMS_AB_T_SELECTOR +x81410102 PR_EMS_AB_T_SELECTOR_INBOUND +x8142101f PR_EMS_AB_TARGET_MTAS +x81431102 PR_EMS_AB_TELETEX_TERMINAL_IDENTIFIER +x81440003 PR_EMS_AB_TEMP_ASSOC_THRESHOLD +x81450003 PR_EMS_AB_TOMBSTONE_LIFETIME +x8146001f PR_EMS_AB_TRACKING_LOG_PATH_NAME +x81470003 PR_EMS_AB_TRANS_RETRY_MINS +x81480003 PR_EMS_AB_TRANS_TIMEOUT_MINS +x81490003 PR_EMS_AB_TRANSFER_RETRY_INTERVAL +x814a0003 PR_EMS_AB_TRANSFER_TIMEOUT_NON_URGENT +x814b0003 PR_EMS_AB_TRANSFER_TIMEOUT_NORMAL +x814c0003 PR_EMS_AB_TRANSFER_TIMEOUT_URGENT +x814d0003 PR_EMS_AB_TRANSLATION_TABLE_USED +x814e000b PR_EMS_AB_TRANSPORT_EXPEDITED_DATA +x814f0003 PR_EMS_AB_TRUST_LEVEL +x81500003 PR_EMS_AB_TURN_REQUEST_THRESHOLD +x8151000b PR_EMS_AB_TWO_WAY_ALTERNATE_FACILITY +x8152000d PR_EMS_AB_UNAUTH_ORIG_BL_O +x8152101f PR_EMS_AB_UNAUTH_ORIG_BL +x81531102 PR_EMS_AB_USER_PASSWORD +x81540003 PR_EMS_AB_USN_CREATED +x81550003 PR_EMS_AB_USN_DSA_LAST_OBJ_REMOVED +x81560003 PR_EMS_AB_USN_LAST_OBJ_REM +x81570003 PR_EMS_AB_USN_SOURCE +x8158101f PR_EMS_AB_X121_ADDRESS +x81590102 PR_EMS_AB_X25_CALL_USER_DATA_INCOMING +x815a0102 PR_EMS_AB_X25_CALL_USER_DATA_OUTGOING +x815b0102 PR_EMS_AB_X25_FACILITIES_DATA_INCOMING +x815c0102 PR_EMS_AB_X25_FACILITIES_DATA_OUTGOING +x815d0102 PR_EMS_AB_X25_LEASED_LINE_PORT +x815e000b PR_EMS_AB_X25_LEASED_OR_SWITCHED +x815f001f PR_EMS_AB_X25_REMOTE_MTA_PHONE +x81600102 PR_EMS_AB_X400_ATTACHMENT_TYPE +x81610003 PR_EMS_AB_X400_SELECTOR_SYNTAX +x81620102 PR_EMS_AB_X500_ACCESS_CONTROL_LIST +x81630003 PR_EMS_AB_XMIT_TIMEOUT_NON_URGENT +x81640003 PR_EMS_AB_XMIT_TIMEOUT_NORMAL +x81650003 PR_EMS_AB_XMIT_TIMEOUT_URGENT +x81660102 PR_EMS_AB_SITE_FOLDER_GUID +x8167000d PR_EMS_AB_SITE_FOLDER_SERVER_O +x8167001f PR_EMS_AB_SITE_FOLDER_SERVER +x81680003 PR_EMS_AB_REPLICATION_MAIL_MSG_SIZE +x81690102 PR_EMS_AB_MAXIMUM_OBJECT_ID +x8170101f PR_EMS_AB_NETWORK_ADDRESS +x8171101f PR_EMS_AB_LDAP_DISPLAY_NAME +x81730003 PR_EMS_AB_SCHEMA_FLAGS +x8174000d PR_EMS_AB_BRIDGEHEAD_SERVERS_O +x8174101f PR_EMS_AB_BRIDGEHEAD_SERVERS +x8175001f PR_EMS_AB_WWW_HOME_PAGE +x8176001f PR_EMS_AB_NNTP_CONTENT_FORMAT +x8177001f PR_EMS_AB_POP_CONTENT_FORMAT +x81780003 PR_EMS_AB_LANGUAGE +x8179001f PR_EMS_AB_POP_CHARACTER_SET +x817a0003 PR_EMS_AB_USN_INTERSITE +x817b001f PR_EMS_AB_SUB_SITE +x817c1003 PR_EMS_AB_SCHEMA_VERSION +x817d001f PR_EMS_AB_NNTP_CHARACTER_SET +x817e000b PR_EMS_AB_USE_SERVER_VALUES +x817f0003 PR_EMS_AB_ENABLED_PROTOCOLS +x81800102 PR_EMS_AB_CONNECTION_LIST_FILTER +x8181101f PR_EMS_AB_AVAILABLE_AUTHORIZATION_PACKAGES +x8182101f PR_EMS_AB_CHARACTER_SET_LIST +x8183000b PR_EMS_AB_USE_SITE_VALUES +x8184101f PR_EMS_AB_ENABLED_AUTHORIZATION_PACKAGES +x8185001f PR_EMS_AB_CHARACTER_SET +x81860003 PR_EMS_AB_CONTENT_TYPE +x8187000b PR_EMS_AB_ANONYMOUS_ACCESS +x81880102 PR_EMS_AB_CONTROL_MSG_FOLDER_ID +x8189001f PR_EMS_AB_USENET_SITE_NAME +x818a0102 PR_EMS_AB_CONTROL_MSG_RULES +x818b001f PR_EMS_AB_AVAILABLE_DISTRIBUTIONS +x818d0102 PR_EMS_AB_OUTBOUND_HOST +x818e101f PR_EMS_AB_INBOUND_HOST +x818f0003 PR_EMS_AB_OUTGOING_MSG_SIZE_LIMIT +x81900003 PR_EMS_AB_INCOMING_MSG_SIZE_LIMIT +x8191000b PR_EMS_AB_SEND_TNEF +x81920102 PR_EMS_AB_AUTHORIZED_PASSWORD_CONFIRM +x8193001f PR_EMS_AB_INBOUND_NEWSFEED +x81940003 PR_EMS_AB_NEWSFEED_TYPE +x8195001f PR_EMS_AB_OUTBOUND_NEWSFEED +x81960102 PR_EMS_AB_NEWSGROUP_LIST +x8197101f PR_EMS_AB_NNTP_DISTRIBUTIONS +x8198001f PR_EMS_AB_NEWSGROUP +x8199001f PR_EMS_AB_MODERATOR +x819a001f PR_EMS_AB_AUTHENTICATION_TO_USE +x819b000b PR_EMS_AB_HTTP_PUB_GAL +x819c0003 PR_EMS_AB_HTTP_PUB_GAL_LIMIT +x819e1102 PR_EMS_AB_HTTP_PUB_PF +x81a1001f PR_EMS_AB_X500_RDN +x81a2001f PR_EMS_AB_X500_NC +x81a3101f PR_EMS_AB_REFERRAL_LIST +x81a4000b PR_EMS_AB_NNTP_DISTRIBUTIONS_FLAG +x81a5000d PR_EMS_AB_ASSOC_PROTOCOL_CFG_NNTP_O +x81a5001f PR_EMS_AB_ASSOC_PROTOCOL_CFG_NNTP +x81a6000d PR_EMS_AB_NNTP_NEWSFEEDS_O +x81a6101f PR_EMS_AB_NNTP_NEWSFEEDS +x81a8000b PR_EMS_AB_ENABLED_PROTOCOL_CFG +x81a9101f PR_EMS_AB_HTTP_PUB_AB_ATTRIBUTES +x81ab101f PR_EMS_AB_HTTP_SERVERS +x81ac000b PR_EMS_AB_MODERATED +x81ad001f PR_EMS_AB_RAS_ACCOUNT +x81ae0102 PR_EMS_AB_RAS_PASSWORD +x81af0102 PR_EMS_AB_INCOMING_PASSWORD +x81b0000b PR_EMS_AB_OUTBOUND_HOST_TYPE +x81b1000b PR_EMS_AB_PROXY_GENERATION_ENABLED +x81b20102 PR_EMS_AB_ROOT_NEWSGROUPS_FOLDER_ID +x81b3000b PR_EMS_AB_CONNECTION_TYPE +x81b40003 PR_EMS_AB_CONNECTION_LIST_FILTER_TYPE +x81b50003 PR_EMS_AB_PORT_NUMBER +x81b6101f PR_EMS_AB_PROTOCOL_SETTINGS +x81b7001f PR_EMS_AB_GROUP_BY_ATTR_1 +x81b8001f PR_EMS_AB_GROUP_BY_ATTR_2 +x81b9001f PR_EMS_AB_GROUP_BY_ATTR_3 +x81ba001f PR_EMS_AB_GROUP_BY_ATTR_4 +x81be001f PR_EMS_AB_VIEW_SITE +x81bf001f PR_EMS_AB_VIEW_CONTAINER_1 +x81c0001f PR_EMS_AB_VIEW_CONTAINER_2 +x81c1001f PR_EMS_AB_VIEW_CONTAINER_3 +x81c20040 PR_EMS_AB_PROMO_EXPIRATION +x81c3101f PR_EMS_AB_DISABLED_GATEWAY_PROXY +x81c40102 PR_EMS_AB_COMPROMISED_KEY_LIST +x81c5000d PR_EMS_AB_INSADMIN_O +x81c5001f PR_EMS_AB_INSADMIN +x81c6000b PR_EMS_AB_OVERRIDE_NNTP_CONTENT_FORMAT +x81c7000d PR_EMS_AB_OBJ_VIEW_CONTAINERS_O +x81c7101f PR_EMS_AB_OBJ_VIEW_CONTAINERS +x8c180003 PR_EMS_AB_VIEW_FLAGS +x8c19001f PR_EMS_AB_GROUP_BY_ATTR_VALUE_STR +x8c1a000d PR_EMS_AB_GROUP_BY_ATTR_VALUE_DN_O +x8c1a001f PR_EMS_AB_GROUP_BY_ATTR_VALUE_DN +x8c1b1102 PR_EMS_AB_VIEW_DEFINITION +x8c1c0102 PR_EMS_AB_MIME_TYPES +x8c1d0003 PR_EMS_AB_LDAP_SEARCH_CFG +x8c1e000d PR_EMS_AB_INBOUND_DN_O +x8c1e001f PR_EMS_AB_INBOUND_DN +x8c1f000b PR_EMS_AB_INBOUND_NEWSFEED_TYPE +x8c20000b PR_EMS_AB_INBOUND_ACCEPT_ALL +x8c21000b PR_EMS_AB_ENABLED +x8c22000b PR_EMS_AB_PRESERVE_INTERNET_CONTENT +x8c23000b PR_EMS_AB_DISABLE_DEFERRED_COMMIT +x8c24000b PR_EMS_AB_CLIENT_ACCESS_ENABLED +x8c25000b PR_EMS_AB_REQUIRE_SSL +x8c26001f PR_EMS_AB_ANONYMOUS_ACCOUNT +x8c270102 PR_EMS_AB_CERTIFICATE_CHAIN_V3 +x8c280102 PR_EMS_AB_CERTIFICATE_REVOCATION_LIST_V3 +x8c290102 PR_EMS_AB_CERTIFICATE_REVOCATION_LIST_V1 +x8c301102 PR_EMS_AB_CROSS_CERTIFICATE_CRL +x8c31000b PR_EMS_AB_SEND_EMAIL_MESSAGE +x8c32000b PR_EMS_AB_ENABLE_COMPATIBILITY +x8c33101f PR_EMS_AB_SMIME_ALG_LIST_NA +x8c34101f PR_EMS_AB_SMIME_ALG_LIST_OTHER +x8c35001f PR_EMS_AB_SMIME_ALG_SELECTED_NA +x8c36001f PR_EMS_AB_SMIME_ALG_SELECTED_OTHER +x8c37000b PR_EMS_AB_DEFAULT_MESSAGE_FORMAT +x8c38001f PR_EMS_AB_TYPE +x8c3a0003 PR_EMS_AB_DO_OAB_VERSION +x8c3b0102 PR_EMS_AB_VOICE_MAIL_SYSTEM_GUID +x8c3c001f PR_EMS_AB_VOICE_MAIL_USER_ID +x8c3d001f PR_EMS_AB_VOICE_MAIL_PASSWORD +x8c3e0102 PR_EMS_AB_VOICE_MAIL_RECORDED_NAME +x8c3f101f PR_EMS_AB_VOICE_MAIL_GREETINGS +x8c401102 PR_EMS_AB_VOICE_MAIL_FLAGS +x8c410003 PR_EMS_AB_VOICE_MAIL_VOLUME +x8c420003 PR_EMS_AB_VOICE_MAIL_SPEED +x8c431003 PR_EMS_AB_VOICE_MAIL_RECORDING_LENGTH +x8c44001f PR_EMS_AB_DISPLAY_NAME_SUFFIX +x8c451102 PR_EMS_AB_ATTRIBUTE_CERTIFICATE +x8c461102 PR_EMS_AB_DELTA_REVOCATION_LIST +x8c471102 PR_EMS_AB_SECURITY_POLICY +x8c48000b PR_EMS_AB_SUPPORT_SMIME_SIGNATURES +x8c49000b PR_EMS_AB_DELEGATE_USER +x8c50000b PR_EMS_AB_LIST_PUBLIC_FOLDERS +x8c51001f PR_EMS_AB_LABELEDURI +x8c52000b PR_EMS_AB_RETURN_EXACT_MSG_SIZE +x8c53001f PR_EMS_AB_GENERATION_QUALIFIER +x8c54001f PR_EMS_AB_HOUSE_IDENTIFIER +x8c550102 PR_EMS_AB_SUPPORTED_ALGORITHMS +x8c56001f PR_EMS_AB_DMD_NAME +x8c57001f PR_EMS_AB_EXTENSION_ATTRIBUTE_11 +x8c58001f PR_EMS_AB_EXTENSION_ATTRIBUTE_12 +x8c59001f PR_EMS_AB_EXTENSION_ATTRIBUTE_13 +x8c60001f PR_EMS_AB_EXTENSION_ATTRIBUTE_14 +x8c61001f PR_EMS_AB_EXTENSION_ATTRIBUTE_15 +x8c620003 PR_EMS_AB_REPLICATED_OBJECT_VERSION +x8c63001f PR_EMS_AB_MAIL_DROP +x8c64001f PR_EMS_AB_FORWARDING_ADDRESS +x8c650102 PR_EMS_AB_FORM_DATA +x8c66001f PR_EMS_AB_OWA_SERVER +x8c67001f PR_EMS_AB_EMPLOYEE_NUMBER +x8c68001f PR_EMS_AB_TELEPHONE_PERSONAL_PAGER +x8c69001f PR_EMS_AB_EMPLOYEE_TYPE +x8c6a1102 PR_EMS_AB_TAGGED_X509_CERT +x8c6b001f PR_EMS_AB_PERSONAL_TITLE +x8c6c001f PR_EMS_AB_LANGUAGE_ISO639 +xf000000d PR_EMS_AB_OTHER_RECIPS +xfff8101f PR_EMS_AB_CHILD_RDNS +xfff9001f PR_EMS_AB_HIERARCHY_PATH +xfffa0102 PR_EMS_AB_OBJECT_OID +xfffb000b PR_EMS_AB_IS_MASTER +xfffc0102 PR_EMS_AB_PARENT_ENTRYID +xfffd0003 PR_EMS_AB_CONTAINERID +xfffd0003 PR_EMS_AB_DOS_ENTRYID +xfffe001f PR_EMS_AB_SERVER diff --git a/servers/exchange/lib/mapi.h b/servers/exchange/lib/mapi.h new file mode 100644 index 0000000..d5aaefc --- /dev/null +++ b/servers/exchange/lib/mapi.h @@ -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 index 0000000..ae83547 --- /dev/null +++ b/servers/exchange/storage/Makefile.am @@ -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 index 0000000..da5a173 --- /dev/null +++ b/servers/exchange/storage/e-folder-exchange.c @@ -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 +#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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 index 0000000..56149a9 --- /dev/null +++ b/servers/exchange/storage/e-folder-exchange.h @@ -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 index 0000000..cf0df4c --- /dev/null +++ b/servers/exchange/storage/e-folder-tree.c @@ -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 +#endif + +#include "e-folder-tree.h" + +#include +#include + +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 index 0000000..02df249 --- /dev/null +++ b/servers/exchange/storage/e-folder-tree.h @@ -0,0 +1,58 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-folder-tree.h + * + * Copyright (C) 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 + +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 index 0000000..e735588 --- /dev/null +++ b/servers/exchange/storage/e-folder-type-registry.c @@ -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 +#endif + +#include "e-folder-type-registry.h" + +// SURF : #include + +// SURF : #include + +#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 index 0000000..66a61e4 --- /dev/null +++ b/servers/exchange/storage/e-folder-type-registry.h @@ -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 +#include + +#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 index 0000000..12719a7 --- /dev/null +++ b/servers/exchange/storage/e-folder.c @@ -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 +#endif + +#include "e-folder.h" +#include "e-shell-marshal.h" + +#include +#include +#include + +#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 index 0000000..e9c4bb9 --- /dev/null +++ b/servers/exchange/storage/e-folder.h @@ -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 +#include + +#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 index 0000000..cd8fa12 --- /dev/null +++ b/servers/exchange/storage/e-storage.c @@ -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 +#endif + +#include "e-storage.h" + +#include "e-folder-tree.h" +#include "e-shell-marshal.h" + +#include +#include + +#include + +#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 index 0000000..64f68de --- /dev/null +++ b/servers/exchange/storage/e-storage.h @@ -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 +#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 index 0000000..0558332 --- /dev/null +++ b/servers/exchange/storage/exchange-account.c @@ -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 +#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 +#include + +//#include +#include + +#include +#include +#include + +#include +#include +#include + +#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 (tspriv->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 index 0000000..fe47c01 --- /dev/null +++ b/servers/exchange/storage/exchange-account.h @@ -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 +#include +#include "e2k-autoconfig.h" +#include "e2k-context.h" +#include "e2k-global-catalog.h" +#include "e2k-security-descriptor.h" +#include "e-folder.h" +#include + +#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 index 0000000..1492636 --- /dev/null +++ b/servers/exchange/storage/exchange-constants.h @@ -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 index 0000000..1f82ca5 --- /dev/null +++ b/servers/exchange/storage/exchange-folder-size.c @@ -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 +#endif + +#include + +// SURF : #include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..13b1e6b --- /dev/null +++ b/servers/exchange/storage/exchange-folder-size.h @@ -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 + +#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 index 0000000..21eb16f --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy-favorites.c @@ -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 +#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 + +#include +#include +#include + +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 index 0000000..933f17e --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy-favorites.h @@ -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 index 0000000..9ee7693 --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy-gal.c @@ -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 +#endif + +#include "exchange-hierarchy-gal.h" +#include "exchange-account.h" +#include "e-folder-exchange.h" +//#include "exchange-config-listener.h" + +#include + +#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 index 0000000..1cc9717 --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy-gal.h @@ -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 index 0000000..2ff1a39 --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy-somedav.c @@ -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 +#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 + +#include +#include +#include + +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 index 0000000..1ac6626 --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy-somedav.h @@ -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 index 0000000..1cc085f --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy-webdav.c @@ -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 +#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 +#include "e2k-path.h" +#include + +#include +#include +#include + +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 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 index 0000000..078e2c0 --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy-webdav.h @@ -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 index 0000000..e5a5299 --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy.c @@ -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 +#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 index 0000000..5de5acb --- /dev/null +++ b/servers/exchange/storage/exchange-hierarchy.h @@ -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 index 0000000..2612973 --- /dev/null +++ b/servers/exchange/storage/exchange-types.h @@ -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 + +#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 index 0000000..e1368eb --- /dev/null +++ b/servers/exchange/storage/libexchange-storage.pc.in @@ -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 index 0000000..6b07afc --- /dev/null +++ b/servers/exchange/xntlm/ChangeLog @@ -0,0 +1,61 @@ +2005-04-21 Sarfraaz Ahmed + + * Moved the code to e-d-s from exchange + +2005-02-08 David Malcolm + + * 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 + + ** 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 + + * xntlm.c (xntlm_negotiate): Redo the doc comment to make gtk-doc + happy + +2003-11-13 Dan Winship + + * xntlm-des.c: ANSIfy prototypes for gtk-doc + +2003-10-07 Dan Winship + + * xntlm.c (ntlm_lanmanager_hash): fix a gcc 3.3 warning + +2003-04-08 Dan Winship + + * xntlm.c (setup_schedule): Fix to not read uninitialized memory + to make valgrind/purify happy + +2003-04-04 Dan Winship + + * 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 + + * Makefile.am: Make this a static library + +2003-02-26 Dan Winship + + * xntlm.c (strip_dup): Fix a bug + +2003-02-21 Dan Winship + + * xntlm.c, xntlm-des.c, xntlm-md4.c: #include config.h + +2002-08-05 Dan Winship + + * 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 index 0000000..ef8df16 --- /dev/null +++ b/servers/exchange/xntlm/Makefile.am @@ -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 index 0000000..2c93a96 --- /dev/null +++ b/servers/exchange/xntlm/xntlm-des.c @@ -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 +#endif + +#include "xntlm-des.h" +#include + +/* 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 index 0000000..f95e8f7 --- /dev/null +++ b/servers/exchange/xntlm/xntlm-des.h @@ -0,0 +1,20 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Copyright (C) 2001-2004 Novell, Inc. */ + +#ifndef _XNTLM_DES_H +#define _XNTLM_DES_H + +#include + +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 index 0000000..eadca39 --- /dev/null +++ b/servers/exchange/xntlm/xntlm-md4.c @@ -0,0 +1,175 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* Copyright (C) 2001-2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU 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 +#endif + +#include +#include "xntlm-md4.h" + +#include + +/* 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 index 0000000..70c91b0 --- /dev/null +++ b/servers/exchange/xntlm/xntlm-md4.h @@ -0,0 +1,10 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Copyright (C) 2001-2004 Novell, Inc. */ + +#ifndef _XNTLM_MD4_H +#define _XNTLM_MD4_H + +void xntlm_md4sum (const 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 index 0000000..69abd21 --- /dev/null +++ b/servers/exchange/xntlm/xntlm.c @@ -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 +#endif + +#include "xntlm.h" +#include "xntlm-des.h" +#include "xntlm-md4.h" + +#include +#include + + +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 index 0000000..3b2e278 --- /dev/null +++ b/servers/exchange/xntlm/xntlm.h @@ -0,0 +1,16 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Copyright (C) 2001-2004 Novell, Inc. */ + +#ifndef _XNTLM_H +#define _XNTLM_H + +#include + +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 */