/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* Copyright (C) 2002-2004 Novell, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* ExchangeAccount: Handles a single configured Connector account. This * is strictly a model object. ExchangeStorage handles the view. */ #ifdef HAVE_CONFIG_H #include #endif #include "exchange-account.h" #include "exchange-hierarchy-webdav.h" #include "exchange-hierarchy-favorites.h" #include "exchange-hierarchy-gal.h" #include "exchange-folder-size.h" #include "e-folder-exchange.h" #include "e2k-autoconfig.h" #include "e2k-encoding-utils.h" #include "e2k-kerberos.h" #include "e2k-propnames.h" #include "e2k-uri.h" #include "e2k-utils.h" #include "exchange-hierarchy-foreign.h" /* This is an ugly hack to avoid API break */ /* Added for get_authtype */ #include "exchange-esource.h" #include #include #include #include #include #include #include #define d(x) #define ADS_UF_DONT_EXPIRE_PASSWORD 0x10000 #define ONE_HUNDRED_NANOSECOND 0.000000100 #define SECONDS_IN_DAY 86400 struct _ExchangeAccountPrivate { E2kContext *ctx; E2kGlobalCatalog *gc; GHashTable *standard_uris; ExchangeFolderSize *fsize; GMutex *connect_lock; gboolean connecting, connected; int account_online; GPtrArray *hierarchies; GHashTable *hierarchies_by_folder, *foreign_hierarchies; ExchangeHierarchy *favorites_hierarchy; GHashTable *folders, *fresh_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, quota_limit; EAccountList *account_list; EAccount *account; GMutex *discover_data_lock; GList *discover_datas; }; enum { CONNECTED, NEW_FOLDER, REMOVED_FOLDER, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0 }; #define PARENT_TYPE G_TYPE_OBJECT static GObjectClass *parent_class = NULL; static void dispose (GObject *); static void finalize (GObject *); static void remove_hierarchy (ExchangeAccount *account, ExchangeHierarchy *hier); static void class_init (GObjectClass *object_class) { parent_class = g_type_class_ref (PARENT_TYPE); /* virtual method override */ object_class->dispose = dispose; object_class->finalize = finalize; /* signals */ signals[CONNECTED] = g_signal_new ("connected", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ExchangeAccountClass, connected), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, E2K_TYPE_CONTEXT); signals[NEW_FOLDER] = g_signal_new ("new_folder", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ExchangeAccountClass, new_folder), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[REMOVED_FOLDER] = g_signal_new ("removed_folder", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ExchangeAccountClass, removed_folder), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); } static void init (GObject *object) { ExchangeAccount *account = EXCHANGE_ACCOUNT (object); account->priv = g_new0 (ExchangeAccountPrivate, 1); account->priv->connect_lock = g_mutex_new (); account->priv->hierarchies = g_ptr_array_new (); account->priv->hierarchies_by_folder = g_hash_table_new (NULL, NULL); account->priv->foreign_hierarchies = g_hash_table_new (g_str_hash, g_str_equal); account->priv->folders = g_hash_table_new (g_str_hash, g_str_equal); account->priv->fresh_folders = NULL; account->priv->discover_data_lock = g_mutex_new (); account->priv->account_online = UNSUPPORTED_MODE; account->priv->nt_domain = NULL; account->priv->fsize = exchange_folder_size_new (); } static void free_name (gpointer name, gpointer value, gpointer data) { g_free (name); } static void free_folder (gpointer key, gpointer folder, gpointer data) { g_object_unref (folder); } static void dispose (GObject *object) { ExchangeAccount *account = EXCHANGE_ACCOUNT (object); 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; } if (account->priv->fresh_folders) { g_hash_table_foreach (account->priv->fresh_folders, free_folder, NULL); g_hash_table_destroy (account->priv->fresh_folders); account->priv->fresh_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; EFolder *toplevel; g_return_if_fail (EXCHANGE_IS_ACCOUNT (account)); if (account->priv->fresh_folders) { g_hash_table_foreach (account->priv->fresh_folders, free_folder, NULL); g_hash_table_destroy (account->priv->fresh_folders); account->priv->fresh_folders = NULL; } account->priv->fresh_folders = g_hash_table_new (g_str_hash, g_str_equal); for (i = 0; i < account->priv->hierarchies->len; i++) { /* First include the toplevel folder of the hierarchy as well */ toplevel = EXCHANGE_HIERARCHY (account->priv->hierarchies->pdata[i])->toplevel; g_object_ref (toplevel); g_hash_table_insert (account->priv->fresh_folders, (char *)e_folder_exchange_get_path (toplevel), toplevel); exchange_hierarchy_scan_subtree (account->priv->hierarchies->pdata[i], toplevel, account->priv->account_online); 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); char *key; /* This makes the cleanup easier. We just unref it each time * we find it in account->priv->folders. */ key = (char *) e_folder_exchange_get_path (folder); if (!g_hash_table_lookup (account->priv->folders, key)) { /* Avoid dupilcations since the user could add a folder as favorite even though it is already marked as favorite */ g_object_ref (folder); g_hash_table_insert (account->priv->folders, key, folder); table_updated = 1; } if (account->priv->fresh_folders) { g_object_ref (folder); g_hash_table_insert (account->priv->fresh_folders, key, folder); } key = (char *) e_folder_get_physical_uri (folder); if (!g_hash_table_lookup (account->priv->folders, key)) { /* Avoid dupilcations since the user could add a folder as favorite even though it is already marked as favorite */ g_object_ref (folder); g_hash_table_insert (account->priv->folders, key, folder); table_updated = 1; } key = (char *) e_folder_exchange_get_internal_uri (folder); if (!g_hash_table_lookup (account->priv->folders, key)) { /* The internal_uri for public folders and favorites folder is same !!! Without this check the folder value could overwrite the previously added folder. */ g_object_ref (folder); g_hash_table_insert (account->priv->folders, key, folder); table_updated = 1; } if (permanent_uri && (!g_hash_table_lookup (account->priv->folders, permanent_uri))) { g_object_ref (folder); g_hash_table_insert (account->priv->folders, (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); } static gboolean check_if_sf (gpointer key, gpointer value, gpointer user_data) { char *sf_href = (char *)value; char *int_uri = (char *)user_data; if (!strcmp (sf_href, int_uri)) return TRUE; /* Quit calling the callback */ return FALSE; /* Continue calling the callback till end of table */ } ExchangeAccountFolderResult exchange_account_remove_folder (ExchangeAccount *account, const char *path) { ExchangeHierarchy *hier; EFolder *folder; const char *int_uri; g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR); d(g_print ("exchange_account_remove_folder: path=[%s]\n", path)); if (!get_folder (account, path, &folder, &hier)) return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST; int_uri = e_folder_exchange_get_internal_uri (folder); if (g_hash_table_find (account->priv->standard_uris, check_if_sf, (char *)int_uri)) { 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); hier = exchange_hierarchy_foreign_new (account, hierarchy_name, physical_uri_prefix, internal_uri_prefix, entry->display_name, entry->email, source); g_free (hierarchy_name); g_free (physical_uri_prefix); g_free (internal_uri_prefix); g_free (source); setup_hierarchy_foreign (account, hier); return hier; } ExchangeAccountFolderResult exchange_account_discover_shared_folder (ExchangeAccount *account, const 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_GC_NOTREACHABLE; email = strchr (user, '<'); if (email) email = g_strndup (email + 1, strcspn (email + 1, ">")); else email = g_strdup (user); hier = g_hash_table_lookup (account->priv->foreign_hierarchies, email); if (hier) { g_free (email); return exchange_hierarchy_foreign_add_folder (hier, folder_name, folder); } dd.user = user; dd.folder_name = folder_name; e2k_operation_init (&dd.op); g_mutex_lock (account->priv->discover_data_lock); account->priv->discover_datas = g_list_prepend (account->priv->discover_datas, &dd); g_mutex_unlock (account->priv->discover_data_lock); status = e2k_global_catalog_lookup (account->priv->gc, &dd.op, E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL, email, E2K_GLOBAL_CATALOG_LOOKUP_EMAIL | E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX, &entry); g_free (email); e2k_operation_free (&dd.op); g_mutex_lock (account->priv->discover_data_lock); account->priv->discover_datas = g_list_remove (account->priv->discover_datas, &dd); g_mutex_unlock (account->priv->discover_data_lock); if (status != E2K_GLOBAL_CATALOG_OK) { if (status == E2K_GLOBAL_CATALOG_ERROR) return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR; if (status == E2K_GLOBAL_CATALOG_NO_SUCH_USER) return EXCHANGE_ACCOUNT_FOLDER_NO_SUCH_USER; else return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST; } hier = get_hierarchy_for (account, entry); return exchange_hierarchy_foreign_add_folder (hier, folder_name, folder); } void exchange_account_cancel_discover_shared_folder (ExchangeAccount *account, const 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 (!EXCHANGE_IS_HIERARCHY_FOREIGN (hier)) return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION; return exchange_hierarchy_remove_folder (hier, folder); } ExchangeAccountFolderResult exchange_account_open_folder (ExchangeAccount *account, const char *path) { ExchangeHierarchy *hier; EFolder *folder; int mode; g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR); d(g_print ("exchange_account_remove_folder: path=[%s]\n", path)); if (!get_folder (account, path, &folder, &hier)) return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST; exchange_account_is_offline (account, &mode); if (mode == ONLINE_MODE && !account->priv->connected && hier == (ExchangeHierarchy *)account->priv->hierarchies->pdata[0] && folder == hier->toplevel) { /* The shell is asking us to open the personal folders * hierarchy, but we're already planning to do that * anyway. So just ignore the request for now. */ return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST; } return exchange_hierarchy_scan_subtree (hier, folder, mode); } ExchangeAccountFolderResult exchange_account_add_favorite (ExchangeAccount *account, EFolder *folder) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR); g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR); return exchange_hierarchy_favorites_add_folder ( account->priv->favorites_hierarchy, folder); } ExchangeAccountFolderResult exchange_account_remove_favorite (ExchangeAccount *account, EFolder *folder) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR); g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR); return exchange_hierarchy_remove_folder ( EXCHANGE_HIERARCHY (account->priv->favorites_hierarchy), folder); } gboolean exchange_account_is_favorite_folder (ExchangeAccount *account, EFolder *folder) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR); g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR); return exchange_hierarchy_favorites_is_added ( EXCHANGE_HIERARCHY (account->priv->favorites_hierarchy), folder); } static void context_redirect (E2kContext *ctx, E2kHTTPStatus status, const 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; } #if 0 static gboolean get_password (ExchangeAccount *account, E2kAutoconfig *ac, ExchangeAccountResult error) { char *password; if (error != EXCHANGE_ACCOUNT_CONNECT_SUCCESS) e_passwords_forget_password ("Exchange", account->priv->password_key); password = e_passwords_get_password ("Exchange", account->priv->password_key); #if 0 // SURF : if (exchange_component_is_interactive (global_exchange_component)) { gboolean remember, oldremember; if (!password) { char *prompt; prompt = g_strdup_printf (_("Enter password for %s"), account->account_name); oldremember = remember = account->priv->account->source->save_passwd; password = e_passwords_ask_password ( _("Enter password"), "Exchange", account->priv->password_key, prompt, E_PASSWORDS_REMEMBER_FOREVER|E_PASSWORDS_SECRET, &remember, NULL); if (remember != oldremember) { account->priv->account->source->save_passwd = remember; } g_free (prompt); } else if (!account->priv->account->source->save_passwd) { /* get_password returns the password cached but user has not * selected remember password option, forget this password * whis is stored temporarily by e2k_validate_user() */ e_passwords_forget_password ("Exchange", account->priv->password_key); } } #endif if (!password) { } else if (!account->priv->account->source->save_passwd) { /* get_password returns the password cached but user has not * selected remember password option, forget this password * whis is stored temporarily by e2k_validate_user() */ e_passwords_forget_password ("Exchange", account->priv->password_key); } if (password) { e2k_autoconfig_set_password (ac, password); memset (password, 0, strlen (password)); g_free (password); return TRUE; } else return FALSE; } #endif /* This uses the kerberos calls to check if the authentication failure * was due to the password getting expired. If the password has expired * this returns TRUE, else it returns FALSE. */ #ifdef HAVE_KRB5 static gboolean is_password_expired (ExchangeAccount *account, E2kAutoconfig *ac) { 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); } #endif static int 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 -1; /* Check for password expiry period */ /* This needs to be invoked after is_password_expired(), i.e., only if password is not expired */ /* Check for account control value for a user */ e2k_operation_init (&gcop); gcstatus = e2k_global_catalog_lookup (account->priv->gc, &gcop, E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL, account->priv->identity_email, E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL, &entry); e2k_operation_free (&gcop); if (gcstatus != E2K_GLOBAL_CATALOG_OK) return -1; if (entry->user_account_control & ADS_UF_DONT_EXPIRE_PASSWORD) { return -1; /* Password is not set to expire */ } /* Here we don't check not setting the password and expired password */ /* Check for the maximum password age set */ e2k_operation_init (&gcop); max_pwd_age = lookup_passwd_max_age (account->priv->gc, &gcop); e2k_operation_free (&gcop); if (max_pwd_age > 0) { /* Calculate password expiry period */ max_pwd_age_days = ( max_pwd_age * ONE_HUNDRED_NANOSECOND ) / SECONDS_IN_DAY; if (max_pwd_age_days <= account->priv->passwd_exp_warn_period) { return max_pwd_age_days; } } return -1; } 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); } ExchangeAccountResult exchange_account_set_password (ExchangeAccount *account, char *old_pass, char *new_pass) { #ifdef HAVE_KRB5 E2kKerberosResult result; char *domain; g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED); g_return_val_if_fail (old_pass != NULL, EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED); g_return_val_if_fail (new_pass != NULL, EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED); domain = account->priv->gc ? account->priv->gc->domain : NULL; if (!domain) { domain = strchr (account->priv->identity_email, '@'); if (domain) domain++; } if (!domain) { /* email id is not proper, we return instead of trying nt_domain */ return EXCHANGE_ACCOUNT_CONFIG_ERROR; } result = e2k_kerberos_change_password (account->priv->username, domain, old_pass, new_pass); if (result != E2K_KERBEROS_OK && result != E2K_KERBEROS_PASSWORD_TOO_WEAK) { /* try with nt_domain */ domain = account->priv->nt_domain; if (domain) result = e2k_kerberos_change_password (account->priv->username, domain, old_pass, new_pass); } switch (result) { case E2K_KERBEROS_OK: e_passwords_forget_password ("Exchange", account->priv->password_key); e_passwords_add_password (account->priv->password_key, new_pass); if (account->priv->account->source->save_passwd) e_passwords_remember_password ("Exchange", account->priv->password_key); break; case E2K_KERBEROS_PASSWORD_TOO_WEAK: return EXCHANGE_ACCOUNT_PASSWORD_WEAK_ERROR; case E2K_KERBEROS_FAILED: default: return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED; } return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_SUCCESS; #else g_warning ("exchange_account_set_password: Not implemented (no KRB5)"); return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED; #endif } void exchange_account_set_save_password (ExchangeAccount *account, gboolean save_password) { account->priv->account->source->save_passwd = save_password; } gboolean exchange_account_is_save_password (ExchangeAccount *account) { return account->priv->account->source->save_passwd; } /** * exchange_account_set_offline: * @account: an #ExchangeAccount * * This nullifies the connection and sets the account as offline. * The caller should take care that the required data is fetched * before calling this method. * * Return value: Returns TRUE is successfully sets the account to * offline or FALSE if failed **/ gboolean exchange_account_set_offline (ExchangeAccount *account) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), FALSE); g_mutex_lock (account->priv->connect_lock); if (account->priv->ctx) { g_object_unref (account->priv->ctx); account->priv->ctx = NULL; } account->priv->account_online = OFFLINE_MODE; g_mutex_unlock (account->priv->connect_lock); return TRUE; } /** * exchange_account_set_online: * @account: an #ExchangeAccount * * This nullifies the connection and sets the account as offline. * The caller should take care that the required data is fetched * before calling this method. * * Return value: Returns TRUE is successfully sets the account to * offline or FALSE if failed **/ gboolean exchange_account_set_online (ExchangeAccount *account) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), FALSE); g_mutex_lock (account->priv->connect_lock); account->priv->account_online = ONLINE_MODE; g_mutex_unlock (account->priv->connect_lock); return TRUE; } /** * exchange_account_is_offline: * @account: an #ExchangeAccount * * Return value: Returns TRUE if account is offline **/ void exchange_account_is_offline (ExchangeAccount *account, int *state) { g_return_if_fail (EXCHANGE_IS_ACCOUNT (account)); *state = account->priv->account_online; } static gboolean setup_account_hierarchies (ExchangeAccount *account) { ExchangeHierarchy *hier, *personal_hier; ExchangeAccountFolderResult fresult; char *phys_uri_prefix, *dir; GDir *d; const char *dent; int mode; exchange_account_is_offline (account, &mode); if (mode == UNSUPPORTED_MODE) return FALSE; /* Check if folder hierarchies are already setup. */ if (account->priv->hierarchies->len > 0) goto hierarchies_created; /* Set up Personal Folders hierarchy */ phys_uri_prefix = g_strdup_printf ("exchange://%s/;personal", account->priv->uri_authority); hier = exchange_hierarchy_webdav_new (account, EXCHANGE_HIERARCHY_PERSONAL, _("Personal Folders"), phys_uri_prefix, account->home_uri, account->priv->identity_name, account->priv->identity_email, account->priv->source_uri, TRUE); setup_hierarchy (account, hier); g_free (phys_uri_prefix); /* Favorite Public Folders */ phys_uri_prefix = g_strdup_printf ("exchange://%s/;favorites", account->priv->uri_authority); hier = exchange_hierarchy_favorites_new (account, _("Favorite Public Folders"), phys_uri_prefix, account->home_uri, account->public_uri, account->priv->identity_name, account->priv->identity_email, account->priv->source_uri); setup_hierarchy (account, hier); g_free (phys_uri_prefix); account->priv->favorites_hierarchy = hier; /* Public Folders */ phys_uri_prefix = g_strdup_printf ("exchange://%s/;public", account->priv->uri_authority); hier = exchange_hierarchy_webdav_new (account, EXCHANGE_HIERARCHY_PUBLIC, /* i18n: Outlookism */ _("All Public Folders"), phys_uri_prefix, account->public_uri, account->priv->identity_name, account->priv->identity_email, account->priv->source_uri, FALSE); setup_hierarchy (account, hier); g_free (phys_uri_prefix); /* Global Address List */ phys_uri_prefix = g_strdup_printf ("gal://%s/gal", account->priv->uri_authority); /* i18n: Outlookism */ hier = exchange_hierarchy_gal_new (account, _("Global Address List"), phys_uri_prefix); setup_hierarchy (account, hier); g_free (phys_uri_prefix); /* Other users' folders */ d = g_dir_open (account->storage_dir, 0, NULL); if (d) { while ((dent = g_dir_read_name (d))) { if (!strchr (dent, '@')) continue; dir = g_strdup_printf ("%s/%s", account->storage_dir, dent); hier = exchange_hierarchy_foreign_new_from_dir (account, dir); g_free (dir); if (!hier) continue; setup_hierarchy_foreign (account, hier); } g_dir_close (d); } hierarchies_created: /* Scan the personal and favorite folders so we can resolve references * to the Calendar, Contacts, etc even if the tree isn't * opened. */ /* Assuming the first element being personal hierarchy. */ personal_hier = account->priv->hierarchies->pdata[0]; fresult = exchange_hierarchy_scan_subtree (personal_hier, personal_hier->toplevel, mode); if (fresult != EXCHANGE_ACCOUNT_FOLDER_OK) { account->priv->connecting = FALSE; return FALSE; } account->mbox_size = exchange_hierarchy_webdav_get_total_folder_size ( EXCHANGE_HIERARCHY_WEBDAV (personal_hier)); fresult = exchange_hierarchy_scan_subtree ( account->priv->favorites_hierarchy, account->priv->favorites_hierarchy->toplevel, mode); if (fresult != EXCHANGE_ACCOUNT_FOLDER_OK && fresult != EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST) { account->priv->connecting = FALSE; return FALSE; } return TRUE; } /** * exchange_account_connect: * @account: an #ExchangeAccount * * This attempts to connect to @account. If the shell has enabled user * interaction, then it will prompt for a password if needed, and put * up an error message if the connection attempt failed. * * Return value: an #E2kContext, or %NULL if the connection attempt * failed. **/ E2kContext * exchange_account_connect (ExchangeAccount *account, const char *pword, ExchangeAccountResult *info_result) { E2kAutoconfig *ac; E2kAutoconfigResult result; E2kHTTPStatus status; gboolean redirected = FALSE; E2kResult *results; int nresults = 0, mode; GByteArray *entryid; const char *tz; E2kGlobalCatalogStatus gcstatus; E2kGlobalCatalogEntry *entry; E2kOperation gcop; char *user_name = NULL; *info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR; g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL); *info_result = EXCHANGE_ACCOUNT_CONNECT_SUCCESS; exchange_account_is_offline (account, &mode); g_mutex_lock (account->priv->connect_lock); if (mode == UNSUPPORTED_MODE) { *info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR; account->priv->connecting = FALSE; g_mutex_unlock (account->priv->connect_lock); return NULL; } if (account->priv->connecting || mode == OFFLINE_MODE) { g_mutex_unlock (account->priv->connect_lock); if (mode == OFFLINE_MODE) { setup_account_hierarchies (account); *info_result = EXCHANGE_ACCOUNT_OFFLINE; } else { *info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR; } return NULL; } else if (account->priv->ctx) { g_mutex_unlock (account->priv->connect_lock); return account->priv->ctx; } account->priv->connecting = TRUE; if (account->priv->windows_domain) user_name = g_strdup_printf ("%s\\%s", account->priv->windows_domain, account->priv->username); else user_name = g_strdup (account->priv->username); ac = e2k_autoconfig_new (account->home_uri, user_name, NULL, account->priv->auth_pref); g_free (user_name); e2k_autoconfig_set_gc_server (ac, account->priv->ad_server, account->priv->ad_limit); if (!pword) { account->priv->connecting = FALSE; g_mutex_unlock (account->priv->connect_lock); *info_result = EXCHANGE_ACCOUNT_PASSWORD_INCORRECT; e2k_autoconfig_free (ac); return NULL; } e2k_autoconfig_set_password (ac, pword); try_connect_again: account->priv->ctx = e2k_autoconfig_get_context (ac, NULL, &result); if (!account->priv->nt_domain && ac->nt_domain) account->priv->nt_domain = g_strdup (ac->nt_domain); else account->priv->nt_domain = NULL; if (result != E2K_AUTOCONFIG_OK) { #ifdef HAVE_KRB5 if ( is_password_expired (account, ac)) { *info_result = EXCHANGE_ACCOUNT_PASSWORD_EXPIRED; account->priv->connecting = FALSE; g_mutex_unlock (account->priv->connect_lock); e2k_autoconfig_free (ac); return NULL; } #endif switch (result) { case E2K_AUTOCONFIG_AUTH_ERROR: *info_result = EXCHANGE_ACCOUNT_PASSWORD_INCORRECT; e2k_autoconfig_free (ac); account->priv->connecting = FALSE; g_mutex_unlock (account->priv->connect_lock); return NULL; case E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN: *info_result = EXCHANGE_ACCOUNT_DOMAIN_ERROR; e2k_autoconfig_free (ac); account->priv->connecting = FALSE; g_mutex_unlock (account->priv->connect_lock); return NULL; case E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM: ac->use_ntlm = 1; goto try_connect_again; case E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC: ac->use_ntlm = 0; goto try_connect_again; case E2K_AUTOCONFIG_REDIRECT: if (!redirected && account_moved (account, ac)) goto try_connect_again; break; case E2K_AUTOCONFIG_TRY_SSL: if (account_moved (account, ac)) goto try_connect_again; break; default: break; } e2k_autoconfig_free (ac); account->priv->connecting = FALSE; account->priv->account_online = OFFLINE_MODE; /* correct? */ switch (result) { case E2K_AUTOCONFIG_REDIRECT: case E2K_AUTOCONFIG_TRY_SSL: *info_result = EXCHANGE_ACCOUNT_MAILBOX_NA; break; case E2K_AUTOCONFIG_EXCHANGE_5_5: *info_result = EXCHANGE_ACCOUNT_VERSION_ERROR; break; case E2K_AUTOCONFIG_NOT_EXCHANGE: case E2K_AUTOCONFIG_NO_OWA: *info_result = EXCHANGE_ACCOUNT_WSS_ERROR; break; case E2K_AUTOCONFIG_NO_MAILBOX: *info_result = EXCHANGE_ACCOUNT_NO_MAILBOX; break; case E2K_AUTOCONFIG_CANT_RESOLVE: *info_result = EXCHANGE_ACCOUNT_RESOLVE_ERROR; break; case E2K_AUTOCONFIG_CANT_CONNECT: *info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR; break; case E2K_AUTOCONFIG_CANCELLED: break; default: *info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR; break; } g_mutex_unlock (account->priv->connect_lock); return NULL; } account->priv->gc = e2k_autoconfig_get_global_catalog (ac, NULL); e2k_autoconfig_free (ac); status = e2k_context_propfind (account->priv->ctx, NULL, account->home_uri, mailbox_info_props, n_mailbox_info_props, &results, &nresults); if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) { account->priv->connecting = FALSE; *info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR; g_mutex_unlock (account->priv->connect_lock); return NULL; /* FIXME: what error has happened? */ } if (nresults) { account->priv->standard_uris = g_hash_table_new (e2k_ascii_strcase_hash, e2k_ascii_strcase_equal); e2k_properties_foreach (results[0].props, set_sf_prop, account); /* FIXME: we should get these from the autoconfig */ entryid = e2k_properties_get_prop (results[0].props, PR_STORE_ENTRYID); if (entryid) account->legacy_exchange_dn = g_strdup (e2k_entryid_to_dn (entryid)); tz = e2k_properties_get_prop (results[0].props, E2K_PR_EXCHANGE_TIMEZONE); if (tz) account->default_timezone = g_strdup (tz); } if (!setup_account_hierarchies (account)) { *info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR; g_mutex_unlock (account->priv->connect_lock); if (nresults) e2k_results_free (results, nresults); return NULL; /* FIXME: what error has happened? */ } account->priv->account_online = ONLINE_MODE; account->priv->connecting = FALSE; account->priv->connected = TRUE; if (!account->priv->gc) goto skip_quota; /* Check for quota usage */ e2k_operation_init (&gcop); gcstatus = e2k_global_catalog_lookup (account->priv->gc, &gcop, E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL, account->priv->identity_email, E2K_GLOBAL_CATALOG_LOOKUP_QUOTA, &entry); e2k_operation_free (&gcop); /* FIXME: warning message should have quota limit value */ if (gcstatus == E2K_GLOBAL_CATALOG_OK) { if (entry->quota_norecv && account->mbox_size >= entry->quota_norecv) { *info_result = EXCHANGE_ACCOUNT_QUOTA_RECIEVE_ERROR; account->priv->quota_limit = entry->quota_norecv; } else if (entry->quota_nosend && account->mbox_size >= entry->quota_nosend) { *info_result = EXCHANGE_ACCOUNT_QUOTA_SEND_ERROR; account->priv->quota_limit = entry->quota_nosend; } else if (entry->quota_warn && account->mbox_size >= entry->quota_warn) { *info_result = EXCHANGE_ACCOUNT_QUOTA_WARN; account->priv->quota_limit = entry->quota_warn; } } skip_quota: g_signal_connect (account->priv->ctx, "redirect", G_CALLBACK (context_redirect), account); g_signal_emit (account, signals[CONNECTED], 0, account->priv->ctx); g_mutex_unlock (account->priv->connect_lock); if (nresults) e2k_results_free (results, nresults); return account->priv->ctx; } /** * exchange_account_is_offline_sync_set: * @account: an #ExchangeAccount * * Return value: TRUE if offline_sync is set for @account and FALSE if not. */ void exchange_account_is_offline_sync_set (ExchangeAccount *account, 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_fetch: * @acct: an #ExchangeAccount * * Return value: @account's #EAccount, if it is connected and * online, or %NULL if not. **/ EAccount * exchange_account_fetch (ExchangeAccount *acct) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (acct), NULL); return acct->priv->account; } /** * exchange_account_get_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; } /* Scans the subscribed users folders. */ /*FIXME This function is not really required if the syncronization problem between exchange and evolution is fixed. Exchange does not get to know if an user's folder is subscribed from evolution */ void exchange_account_scan_foreign_hierarchy (ExchangeAccount *account, const char *user_email) { char *dir; ExchangeHierarchy *hier; int mode; hier = g_hash_table_lookup (account->priv->foreign_hierarchies, user_email); if (hier) { exchange_hierarchy_rescan (hier); return; } dir = g_strdup_printf ("%s/%s", account->storage_dir, user_email); if (g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { hier = exchange_hierarchy_foreign_new_from_dir (account, dir); g_free (dir); if (hier) { exchange_account_is_offline (account, &mode); setup_hierarchy_foreign (account, hier); exchange_hierarchy_scan_subtree (hier, hier->toplevel, mode); } } } /** * exchange_account_get_hierarchy_by_email: * @account: an #ExchangeAccount * @email: email id of the foreign user * * If the hierarchy is present just return it back. Else try to get it * from the filesystem and return it. * * Return value: Returns the ExchangeHierarchy of the foreign user's folder. **/ ExchangeHierarchy * exchange_account_get_hierarchy_by_email (ExchangeAccount *account, const char *email) { char *dir; ExchangeHierarchy *hier = NULL; int mode; g_return_val_if_fail (email != NULL, NULL); hier = g_hash_table_lookup (account->priv->foreign_hierarchies, email); if (!hier) { dir = g_strdup_printf ("%s/%s", account->storage_dir, email); if (g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { hier = exchange_hierarchy_foreign_new_from_dir (account, dir); g_free (dir); if (hier) { exchange_account_is_offline (account, &mode); setup_hierarchy_foreign (account, hier); } } } return hier; } /** * exchange_account_get_folder: * @account: an #ExchangeAccount * @path_or_uri: the shell path or URI referring to the folder * * Return value: an #EFolder corresponding to the indicated * folder. **/ EFolder * exchange_account_get_folder (ExchangeAccount *account, const char *path_or_uri) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL); if (!path_or_uri) return NULL; 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)); } struct _folders_tree { char *path; GPtrArray *folders; }; static void add_folder (gpointer key, gpointer value, gpointer folders) { EFolder *folder = value; d(g_print ("%s(%d):%s: key=[%s]\t folder-path=[%s]\n", __FILE__, __LINE__, G_GNUC_PRETTY_FUNCTION, key, e_folder_exchange_get_path (folder))); /* Each folder appears under three different keys, but * we only want to add it to the results array once. So * we only add when we see the "path" key. */ if (!strcmp (key, e_folder_exchange_get_path (folder))) g_ptr_array_add (folders, folder); } static void add_folder_tree (gpointer key, gpointer value, gpointer folders) { EFolder *folder = value; struct _folders_tree *fld_tree = (struct _folders_tree *) folders; if (!fld_tree || !fld_tree->path) return; if (g_str_has_prefix (key, fld_tree->path)) add_folder (key, folder, fld_tree->folders); } /** * exchange_account_get_folders: * @account: an #ExchangeAccount * * Return an array of folders (sorted such that parents will occur * before children). If the caller wants to keep up to date with the * list of folders, he should also listen to %new_folder and * %removed_folder signals. * * Return value: an array of folders. The array should be freed with * g_ptr_array_free(). **/ GPtrArray * exchange_account_get_folders (ExchangeAccount *account) { GPtrArray *folders; g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL); folders = g_ptr_array_new (); /* if (account->priv->fresh_folders) g_hash_table_foreach (account->priv->fresh_folders, add_folder, folders); else */ g_hash_table_foreach (account->priv->folders, add_folder, folders); qsort (folders->pdata, folders->len, sizeof (EFolder *), folder_comparator); return folders; } /** * exchange_account_get_folder_tree: * @account: an #ExchangeAccount * * Return an array of folders (sorted such that parents will occur * before children). If the caller wants to keep up to date with the * list of folders, he should also listen to %new_folder and * %removed_folder signals. * * Return value: an array of folders. The array should be freed with * g_ptr_array_free(). **/ GPtrArray * exchange_account_get_folder_tree (ExchangeAccount *account, char* path) { GPtrArray *folders = NULL; EFolder *folder = NULL; ExchangeHierarchy *hier = NULL; struct _folders_tree *fld_tree = NULL; g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL); if (!get_folder (account, path, &folder, &hier)) return folders; exchange_hierarchy_scan_subtree (hier, folder, account->priv->account_online); folders = g_ptr_array_new (); fld_tree = g_new0 (struct _folders_tree, 1); fld_tree->path = path; fld_tree->folders = folders; /* if (account->priv->fresh_folders) g_hash_table_foreach (account->priv->fresh_folders, add_folder, folders); else */ g_hash_table_foreach (account->priv->folders, add_folder_tree, fld_tree); qsort (folders->pdata, folders->len, sizeof (EFolder *), folder_comparator); g_free (fld_tree); return folders; } /** * exchange_account_get_quota_limit: * @account: an #ExchangeAccount * * Return the value of the quota limit reached. * * Return value: an int **/ int exchange_account_get_quota_limit (ExchangeAccount *account) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 0); return account->priv->quota_limit; } int exchange_account_check_password_expiry (ExchangeAccount *account) { E2kGlobalCatalogEntry *entry; /* This is never set before it's used! */ int max_pwd_age_days = -1; g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 0); max_pwd_age_days = find_passwd_exp_period (account, entry); return max_pwd_age_days; } char * exchange_account_get_username (ExchangeAccount *account) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL); return account->priv->username; } /** * exchange_account_get_email_id : * @account : #ExchangeAccount * * Retunrs user's e-mail id. * * Return value : e-mail id string. **/ char * exchange_account_get_email_id (ExchangeAccount *account) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL); return account->priv->identity_email; } /** * exchange_account_folder_size_add : * @account : #ExchangeAccount * @folder_name : * @size : Size of @folder_name * * Updates the #ExchangeFolderSize object with the @size of @folder_name * * Return value : void **/ void exchange_account_folder_size_add (ExchangeAccount *account, const char *folder_name, gdouble size) { g_return_if_fail (EXCHANGE_IS_ACCOUNT (account)); exchange_folder_size_update (account->priv->fsize, folder_name, size); } /** * exchange_account_folder_size_remove : * @account : #ExchangeAccount * @folder_name : * * Removes the entry for @folder_name in #ExchangeFolderSize object * * Return value : void **/ void exchange_account_folder_size_remove (ExchangeAccount *account, const char *folder_name) { g_return_if_fail (EXCHANGE_IS_ACCOUNT (account)); exchange_folder_size_remove (account->priv->fsize, folder_name); } /** * exchange_account_folder_size_rename : * @account : #ExchangeAccount * @old_name : Old name of the folder * @new_name : New name of the folder * * Removes the entry for @old_name in #ExchangeFolderSize object and adds * a new entry for @new_name with the same folder size * * Return value : void **/ void exchange_account_folder_size_rename (ExchangeAccount *account, const char *old_name, const char *new_name) { gdouble cached_size; g_return_if_fail (EXCHANGE_IS_ACCOUNT (account)); cached_size = exchange_folder_size_get (account->priv->fsize, old_name); if (cached_size >= 0) { exchange_folder_size_remove (account->priv->fsize, old_name); exchange_folder_size_update (account->priv->fsize, new_name, cached_size); } } /** * exchange_account_folder_size_get_model : * @account : #ExchangeAccount * * Returns the model store of #ExchangeFolderSize object * * Return value : The model store. A GtkListStore **/ GtkListStore * exchange_account_folder_size_get_model (ExchangeAccount *account) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL); return exchange_folder_size_get_model (account->priv->fsize); } char * exchange_account_get_authtype (ExchangeAccount *account) { g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL); if (account->priv->auth_pref == E2K_AUTOCONFIG_USE_BASIC) return g_strdup ("Basic"); else if (account->priv->auth_pref == E2K_AUTOCONFIG_USE_NTLM) return g_strdup ("NTLM"); return NULL; } /** * exchange_account_new: * @adata: an #EAccount * * An #ExchangeAccount is essentially an #E2kContext with * associated configuration. * * Return value: a new #ExchangeAccount corresponding to @adata **/ ExchangeAccount * exchange_account_new (EAccountList *account_list, EAccount *adata) { ExchangeAccount *account; 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); if (!account) return NULL; account->priv->account_list = account_list; g_object_ref (account_list); account->priv->account = adata; g_object_ref (adata); account->account_name = g_strdup (adata->name); account->storage_dir = g_strdup_printf ("%s/.evolution/exchange/%s@%s", g_get_home_dir (), uri->user, uri->host); /*account->account_filename = strrchr (account->storage_dir, '/') + 1; e_filename_make_safe (account->account_filename); */ /* Identity info */ account->priv->identity_name = g_strdup (adata->id->name); account->priv->identity_email = g_strdup (adata->id->address); /* URI, etc, info */ enc_user = e2k_uri_encode (uri->user, FALSE, "@/;:"); if (uri->authmech) account->priv->uri_authority = g_strdup_printf ("%s;auth=%s@%s", enc_user, uri->authmech, uri->host); else account->priv->uri_authority = g_strdup_printf ("%s@%s", enc_user, uri->host); g_free (enc_user); account->account_filename = account->priv->uri_authority; account->priv->source_uri = g_strdup_printf ("exchange://%s/", account->priv->uri_authority); /* Backword compatibility; FIXME, we should just migrate the * password from this to source_uri. * old_uri_authority = g_strdup_printf ("%s@%s", enc_user, * uri->host); * old_uri_authority needs to be used in the key for migrating * passwords remembered. */ account->priv->password_key = g_strdup_printf ("exchange://%s/", account->priv->uri_authority); account->priv->username = g_strdup (uri->user); if (uri->domain) account->priv->windows_domain = g_strdup (uri->domain); else account->priv->windows_domain = NULL; account->exchange_server = g_strdup (uri->host); if (uri->authmech && !strcmp (uri->authmech, "Basic")) account->priv->auth_pref = E2K_AUTOCONFIG_USE_BASIC; else account->priv->auth_pref = E2K_AUTOCONFIG_USE_NTLM; param = e2k_uri_get_param (uri, "ad_server"); if (param && *param) { account->priv->ad_server = g_strdup (param); param = e2k_uri_get_param (uri, "ad_limit"); if (param) account->priv->ad_limit = atoi (param); } passwd_exp_warn_period = e2k_uri_get_param (uri, "passwd_exp_warn_period"); if (!passwd_exp_warn_period || !*passwd_exp_warn_period) account->priv->passwd_exp_warn_period = -1; else account->priv->passwd_exp_warn_period = atoi (passwd_exp_warn_period); offline_sync = e2k_uri_get_param (uri, "offline_sync"); if (!offline_sync) account->priv->offline_sync = FALSE; else account->priv->offline_sync = TRUE; owa_path = e2k_uri_get_param (uri, "owa_path"); if (!owa_path || !*owa_path) owa_path = "exchange"; else if (*owa_path == '/') owa_path++; pf_server = e2k_uri_get_param (uri, "pf_server"); if (!pf_server || !*pf_server) pf_server = uri->host; /* We set protocol reading owa_url, instead of having use_ssl parameter * because we don't have SSL section anymore in the account creation * druid and account editor */ /* proto = e2k_uri_get_param (uri, "use_ssl") ? "https" : "http"; */ owa_url = e2k_uri_get_param (uri, "owa_url"); if (owa_url) { account->priv->owa_url = g_strdup (owa_url); if (!strncmp (owa_url, "https:", 6)) proto = "https"; } if (uri->port != 0) { account->priv->http_uri_schema = g_strdup_printf ("%s://%%s:%d/%s/%%s/", proto, uri->port, owa_path); account->public_uri = g_strdup_printf ("%s://%s:%d/public", proto, pf_server, uri->port); } else { account->priv->http_uri_schema = g_strdup_printf ("%s://%%s/%s/%%s/", proto, owa_path); account->public_uri = g_strdup_printf ("%s://%s/public", proto, pf_server); } param = e2k_uri_get_param (uri, "mailbox"); if (!param || !*param) param = uri->user; else if (!g_ascii_strncasecmp (param, account->priv->identity_email, strlen (param))) account->priv->uris_use_email = TRUE; mailbox = e2k_uri_encode (param, TRUE, "/"); account->home_uri = g_strdup_printf (account->priv->http_uri_schema, uri->host, mailbox); g_free (mailbox); param = e2k_uri_get_param (uri, "filter"); if (param) account->filter_inbox = TRUE; param = e2k_uri_get_param (uri, "filter_junk"); if (param) account->filter_junk = TRUE; param = e2k_uri_get_param (uri, "filter_junk_inbox"); if (param) account->filter_junk_inbox_only = TRUE; e2k_uri_free (uri); return account; } /** * exchange_account_get_hierarchy_by_type: * @account: an #ExchangeAccount * @type: Hierarchy type * * Returns the non-foreign hierarchy pointer for the requested type * * Return value: Returns the hierarchy pointer for the requested type **/ ExchangeHierarchy* exchange_account_get_hierarchy_by_type (ExchangeAccount* acct, ExchangeHierarchyType type) { int i; g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (acct), NULL); g_return_val_if_fail (type != EXCHANGE_HIERARCHY_FOREIGN, NULL); for (i = 0; i < acct->priv->hierarchies->len; i++) { if (EXCHANGE_HIERARCHY (acct->priv->hierarchies->pdata[i])->type == type) return EXCHANGE_HIERARCHY (acct->priv->hierarchies->pdata[i]); } return NULL; }