Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / storage / exchange-account.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2002-2004 Novell, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /* ExchangeAccount: Handles a single configured Connector account. This
21  * is strictly a model object. ExchangeStorage handles the view.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include "exchange-account.h"
29 #include "exchange-hierarchy-webdav.h"
30 #include "exchange-hierarchy-favorites.h"
31 #include "exchange-hierarchy-gal.h"
32 #include "exchange-folder-size.h"
33 #include "e-folder-exchange.h"
34 #include "e2k-autoconfig.h"
35 #include "e2k-encoding-utils.h"
36 #include "e2k-kerberos.h"
37 #include "e2k-propnames.h"
38 #include "e2k-uri.h"
39 #include "e2k-utils.h"
40 #include "exchange-hierarchy-foreign.h"
41
42 /* This is an ugly hack to avoid API break */
43 /* Added for get_authtype */
44 #include "exchange-esource.h"
45 #include <libedataserverui/e-passwords.h>
46
47 #include <libgnome/gnome-util.h>
48
49 #include <glade/glade-xml.h>
50 #include <gtk/gtkdialog.h>
51 #include <gtk/gtklabel.h>
52
53 #include <stdlib.h>
54 #include <string.h>
55
56 #define d(x) 
57 #define ADS_UF_DONT_EXPIRE_PASSWORD 0x10000
58 #define ONE_HUNDRED_NANOSECOND 0.000000100
59 #define SECONDS_IN_DAY 86400
60
61 struct _ExchangeAccountPrivate {
62         E2kContext *ctx;
63         E2kGlobalCatalog *gc;
64         GHashTable *standard_uris;
65         ExchangeFolderSize *fsize;
66
67         GMutex *connect_lock;
68         gboolean connecting, connected; 
69         int account_online;
70
71         GPtrArray *hierarchies;
72         GHashTable *hierarchies_by_folder, *foreign_hierarchies;
73         ExchangeHierarchy *favorites_hierarchy;
74         GHashTable *folders, *fresh_folders;
75         char *uri_authority, *http_uri_schema;
76         gboolean uris_use_email, offline_sync;
77
78         char *identity_name, *identity_email, *source_uri, *password_key;
79         char *username, *password, *windows_domain, *nt_domain, *ad_server;
80         char *owa_url;
81         E2kAutoconfigAuthPref auth_pref;
82         int ad_limit, passwd_exp_warn_period, quota_limit;
83
84         EAccountList *account_list;
85         EAccount *account;
86
87         GMutex *discover_data_lock;
88         GList *discover_datas;
89 };
90
91 enum {
92         CONNECTED,
93         NEW_FOLDER,
94         REMOVED_FOLDER,
95         LAST_SIGNAL
96 };
97
98 static guint signals [LAST_SIGNAL] = { 0 };
99
100 #define PARENT_TYPE G_TYPE_OBJECT
101 static GObjectClass *parent_class = NULL;
102
103 static void dispose (GObject *);
104 static void finalize (GObject *);
105 static void remove_hierarchy (ExchangeAccount *account, ExchangeHierarchy *hier);
106
107 static void
108 class_init (GObjectClass *object_class)
109 {
110         parent_class = g_type_class_ref (PARENT_TYPE);
111
112         /* virtual method override */
113         object_class->dispose = dispose;
114         object_class->finalize = finalize;
115
116         /* signals */
117         signals[CONNECTED] =
118                 g_signal_new ("connected",
119                               G_OBJECT_CLASS_TYPE (object_class),
120                               G_SIGNAL_RUN_LAST,
121                               G_STRUCT_OFFSET (ExchangeAccountClass, connected),
122                               NULL, NULL,
123                               g_cclosure_marshal_VOID__OBJECT,
124                               G_TYPE_NONE, 1,
125                               E2K_TYPE_CONTEXT);
126         signals[NEW_FOLDER] =
127                 g_signal_new ("new_folder",
128                               G_OBJECT_CLASS_TYPE (object_class),
129                               G_SIGNAL_RUN_LAST,
130                               G_STRUCT_OFFSET (ExchangeAccountClass, new_folder),
131                               NULL, NULL,
132                               g_cclosure_marshal_VOID__POINTER,
133                               G_TYPE_NONE, 1,
134                               G_TYPE_POINTER);
135         signals[REMOVED_FOLDER] =
136                 g_signal_new ("removed_folder",
137                               G_OBJECT_CLASS_TYPE (object_class),
138                               G_SIGNAL_RUN_LAST,
139                               G_STRUCT_OFFSET (ExchangeAccountClass, removed_folder),
140                               NULL, NULL,
141                               g_cclosure_marshal_VOID__POINTER,
142                               G_TYPE_NONE, 1,
143                               G_TYPE_POINTER);
144 }
145
146 static void
147 init (GObject *object)
148 {
149         ExchangeAccount *account = EXCHANGE_ACCOUNT (object);
150
151         account->priv = g_new0 (ExchangeAccountPrivate, 1);
152         account->priv->connect_lock = g_mutex_new ();
153         account->priv->hierarchies = g_ptr_array_new ();
154         account->priv->hierarchies_by_folder = g_hash_table_new (NULL, NULL);
155         account->priv->foreign_hierarchies = g_hash_table_new (g_str_hash, g_str_equal);
156         account->priv->folders = g_hash_table_new (g_str_hash, g_str_equal);
157         account->priv->fresh_folders = NULL;
158         account->priv->discover_data_lock = g_mutex_new ();
159         account->priv->account_online = UNSUPPORTED_MODE;
160         account->priv->nt_domain = NULL;
161         account->priv->fsize = exchange_folder_size_new ();
162 }
163
164 static void
165 free_name (gpointer name, gpointer value, gpointer data)
166 {
167         g_free (name);
168 }
169
170 static void
171 free_folder (gpointer key, gpointer folder, gpointer data)
172 {
173         g_object_unref (folder);
174 }
175
176 static void
177 dispose (GObject *object)
178 {
179         ExchangeAccount *account = EXCHANGE_ACCOUNT (object);
180         int i;
181
182         if (account->priv->account) {
183                 g_object_unref (account->priv->account);
184                 account->priv->account = NULL;
185         }
186
187         if (account->priv->account_list) {
188                 g_object_unref (account->priv->account_list);
189                 account->priv->account_list = NULL;
190         }
191
192         if (account->priv->ctx) {
193                 g_object_unref (account->priv->ctx);
194                 account->priv->ctx = NULL;
195         }
196
197         if (account->priv->gc) {
198                 g_object_unref (account->priv->gc);
199                 account->priv->gc = NULL;
200         }
201
202         if (account->priv->hierarchies) {
203                 for (i = 0; i < account->priv->hierarchies->len; i++)
204                         g_object_unref (account->priv->hierarchies->pdata[i]);
205                 g_ptr_array_free (account->priv->hierarchies, TRUE);
206                 account->priv->hierarchies = NULL;
207         }
208
209         if (account->priv->hierarchies_by_folder) {
210                 g_hash_table_destroy (account->priv->hierarchies_by_folder);
211                 account->priv->hierarchies_by_folder = NULL;
212         }
213
214         if (account->priv->foreign_hierarchies) {
215                 g_hash_table_foreach (account->priv->foreign_hierarchies, free_name, NULL);
216                 g_hash_table_destroy (account->priv->foreign_hierarchies);
217                 account->priv->foreign_hierarchies = NULL;
218         }
219
220         if (account->priv->folders) {
221                 g_hash_table_foreach (account->priv->folders, free_folder, NULL);
222                 g_hash_table_destroy (account->priv->folders);
223                 account->priv->folders = NULL;
224         }
225
226         if (account->priv->fresh_folders) {
227                 g_hash_table_foreach (account->priv->fresh_folders, free_folder, NULL);
228                 g_hash_table_destroy (account->priv->fresh_folders);
229                 account->priv->fresh_folders = NULL;
230         }
231
232         G_OBJECT_CLASS (parent_class)->dispose (object);
233 }
234
235 static void
236 free_uri (gpointer name, gpointer uri, gpointer data)
237 {
238         g_free (name);
239         g_free (uri);
240 }
241
242 static void
243 finalize (GObject *object)
244 {
245         ExchangeAccount *account = EXCHANGE_ACCOUNT (object);
246
247         if (account->account_name)
248                 g_free (account->account_name);
249         if (account->storage_dir)
250                 g_free (account->storage_dir);
251         if (account->exchange_server)
252                 g_free (account->exchange_server);
253         if (account->home_uri)
254                 g_free (account->home_uri);
255         if (account->public_uri)
256                 g_free (account->public_uri);
257         if (account->legacy_exchange_dn)
258                 g_free (account->legacy_exchange_dn);
259         if (account->default_timezone)
260                 g_free (account->default_timezone);
261
262         if (account->priv->standard_uris) {
263                 g_hash_table_foreach (account->priv->standard_uris,
264                                       free_uri, NULL);
265                 g_hash_table_destroy (account->priv->standard_uris);
266         }
267
268         if (account->priv->uri_authority)
269                 g_free (account->priv->uri_authority);
270         if (account->priv->http_uri_schema)
271                 g_free (account->priv->http_uri_schema);
272
273         if (account->priv->identity_name)
274                 g_free (account->priv->identity_name);
275         if (account->priv->identity_email)
276                 g_free (account->priv->identity_email);
277         if (account->priv->source_uri)
278                 g_free (account->priv->source_uri);
279         if (account->priv->password_key)
280                 g_free (account->priv->password_key);
281
282         if (account->priv->username)
283                 g_free (account->priv->username);
284         if (account->priv->password) {
285                 memset (account->priv->password, 0,
286                         strlen (account->priv->password));
287                 g_free (account->priv->password);
288         }
289         if (account->priv->windows_domain)
290                 g_free (account->priv->windows_domain);
291
292         if (account->priv->nt_domain)
293                 g_free (account->priv->nt_domain);
294
295         if (account->priv->ad_server)
296                 g_free (account->priv->ad_server);
297
298         if (account->priv->owa_url)
299                 g_free (account->priv->owa_url);
300
301         if (account->priv->connect_lock)
302                 g_mutex_free (account->priv->connect_lock);
303
304         if (account->priv->discover_data_lock)
305                 g_mutex_free (account->priv->discover_data_lock);
306
307         g_free (account->priv);
308
309         G_OBJECT_CLASS (parent_class)->finalize (object);
310 }
311
312
313 E2K_MAKE_TYPE (exchange_account, ExchangeAccount, class_init, init, PARENT_TYPE)
314
315
316 void
317 exchange_account_rescan_tree (ExchangeAccount *account)
318 {
319         int i;
320         EFolder *toplevel;
321
322         g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
323
324         if (account->priv->fresh_folders) {
325                 g_hash_table_foreach (account->priv->fresh_folders, free_folder, NULL);
326                 g_hash_table_destroy (account->priv->fresh_folders);
327                 account->priv->fresh_folders = NULL;
328         }
329         account->priv->fresh_folders = g_hash_table_new (g_str_hash, g_str_equal);
330
331         for (i = 0; i < account->priv->hierarchies->len; i++) {
332                 /* First include the toplevel folder of the hierarchy as well */
333                 toplevel = EXCHANGE_HIERARCHY (account->priv->hierarchies->pdata[i])->toplevel;
334                 g_object_ref (toplevel);
335                 g_hash_table_insert (account->priv->fresh_folders,
336                                      (char *)e_folder_exchange_get_path (toplevel),
337                                      toplevel);
338
339                 exchange_hierarchy_scan_subtree (account->priv->hierarchies->pdata[i],
340                                                 toplevel, account->priv->account_online);
341                 exchange_hierarchy_rescan (account->priv->hierarchies->pdata[i]);
342         }
343 }
344
345 /*
346  * ExchangeHierarchy folder creation/deletion/xfer notifications
347  */
348
349 static void
350 hierarchy_new_folder (ExchangeHierarchy *hier, EFolder *folder,
351                       ExchangeAccount *account)
352 {
353         int table_updated = 0;
354         const char *permanent_uri =
355                 e_folder_exchange_get_permanent_uri (folder);
356         char *key;
357
358         /* This makes the cleanup easier. We just unref it each time
359          * we find it in account->priv->folders.
360          */
361         key = (char *) e_folder_exchange_get_path (folder);
362         if (!g_hash_table_lookup (account->priv->folders, key)) {
363                 /* Avoid dupilcations since the user could add a folder as
364                   favorite even though it is already marked as favorite */
365                 g_object_ref (folder);
366                 g_hash_table_insert (account->priv->folders,
367                                      key,
368                                      folder);
369                 table_updated = 1;
370         }
371
372         if (account->priv->fresh_folders) {
373                 g_object_ref (folder);
374                 g_hash_table_insert (account->priv->fresh_folders,
375                                      key,
376                                      folder);
377         }       
378         
379         key = (char *) e_folder_get_physical_uri (folder);
380         if (!g_hash_table_lookup (account->priv->folders, key)) {
381                 /* Avoid dupilcations since the user could add a folder as
382                   favorite even though it is already marked as favorite */
383                 g_object_ref (folder);
384                 g_hash_table_insert (account->priv->folders,
385                                      key,
386                                      folder);
387                 table_updated = 1;
388         }
389
390         key = (char *) e_folder_exchange_get_internal_uri (folder);
391         if (!g_hash_table_lookup (account->priv->folders, key)) {
392                 /* The internal_uri for public folders and favorites folder 
393                    is same !!! Without this check the folder value could 
394                    overwrite the previously added folder. */
395                 g_object_ref (folder);
396                 g_hash_table_insert (account->priv->folders,
397                                      key,
398                                      folder);
399                 table_updated = 1;
400         }
401
402         if (permanent_uri && (!g_hash_table_lookup (account->priv->folders, 
403                                         permanent_uri))) {
404                 g_object_ref (folder);
405                 g_hash_table_insert (account->priv->folders,
406                                      (char *)permanent_uri,
407                                      folder);
408                 table_updated = 1;
409         }
410         
411         if (table_updated)
412         {
413                 g_hash_table_insert (account->priv->hierarchies_by_folder, 
414                                         folder, hier);
415
416                 g_signal_emit (account, signals[NEW_FOLDER], 0, folder);
417         }
418 }
419
420 static void
421 hierarchy_removed_folder (ExchangeHierarchy *hier, EFolder *folder,
422                           ExchangeAccount *account)
423 {
424         if (!g_hash_table_lookup (account->priv->folders, 
425                                         e_folder_exchange_get_path (folder)))
426                 return;
427
428         g_hash_table_remove (account->priv->folders, 
429                                         e_folder_exchange_get_path (folder));
430         g_hash_table_remove (account->priv->folders, 
431                                         e_folder_get_physical_uri (folder));
432         /* Dont remove this for favorites, as the internal_uri is shared 
433                 by the public folder as well */
434         if (hier->type != EXCHANGE_HIERARCHY_FAVORITES) {
435                 g_hash_table_remove (account->priv->folders, 
436                                         e_folder_exchange_get_internal_uri (folder));
437         }
438         g_hash_table_remove (account->priv->hierarchies_by_folder, folder);
439         g_signal_emit (account, signals[REMOVED_FOLDER], 0, folder);
440
441         if (folder == hier->toplevel)
442                 remove_hierarchy (account, hier);
443
444         g_object_unref (folder);
445         g_object_unref (folder);
446         if (hier->type != EXCHANGE_HIERARCHY_FAVORITES) {
447                 g_object_unref (folder);
448         }
449 }
450
451 static gboolean
452 get_folder (ExchangeAccount *account, const char *path,
453             EFolder **folder, ExchangeHierarchy **hier)
454 {
455         *folder = g_hash_table_lookup (account->priv->folders, path);
456         if (!*folder)
457                 return FALSE;
458         *hier = g_hash_table_lookup (account->priv->hierarchies_by_folder,
459                                      *folder);
460         if (!*hier)
461                 return FALSE;
462         return TRUE;
463 }
464
465 static gboolean
466 get_parent_and_name (ExchangeAccount *account, const char **path,
467                      EFolder **parent, ExchangeHierarchy **hier)
468 {
469         char *name, *parent_path;
470
471         name = strrchr (*path + 1, '/');
472         if (!name)
473                 return FALSE;
474
475         parent_path = g_strndup (*path, name - *path);
476         *parent = g_hash_table_lookup (account->priv->folders, parent_path);
477         g_free (parent_path);
478
479         if (!*parent)
480                 return FALSE;
481
482         *hier = g_hash_table_lookup (account->priv->hierarchies_by_folder,
483                                      *parent);
484         if (!*hier)
485                 return FALSE;
486
487         *path = name + 1;
488         return TRUE;
489 }
490
491 ExchangeAccountFolderResult
492 exchange_account_create_folder (ExchangeAccount *account,
493                                 const char *path, const char *type)
494 {
495         ExchangeHierarchy *hier;
496         EFolder *parent;
497
498         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 
499                                 EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
500
501         if (!get_parent_and_name (account, &path, &parent, &hier))
502                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
503
504         return exchange_hierarchy_create_folder (hier, parent, path, type);
505 }
506
507 static gboolean
508 check_if_sf (gpointer key, gpointer value, gpointer user_data)
509 {
510         char *sf_href = (char *)value;
511         char *int_uri = (char *)user_data;
512
513         if (!strcmp (sf_href, int_uri))
514                 return TRUE; /* Quit calling the callback */
515
516         return FALSE; /* Continue calling the callback till end of table */
517 }
518
519 ExchangeAccountFolderResult
520 exchange_account_remove_folder (ExchangeAccount *account, const char *path)
521 {
522         ExchangeHierarchy *hier;
523         EFolder *folder;
524         const char *int_uri;
525
526         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 
527                                 EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
528
529         d(g_print ("exchange_account_remove_folder: path=[%s]\n", path));
530
531         if (!get_folder (account, path, &folder, &hier))
532                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
533
534         int_uri = e_folder_exchange_get_internal_uri (folder);
535
536         if (g_hash_table_find (account->priv->standard_uris, 
537                                         check_if_sf, (char *)int_uri)) {
538                 return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION;
539         }
540
541         return exchange_hierarchy_remove_folder (hier, folder);
542 }
543
544 ExchangeAccountFolderResult
545 exchange_account_xfer_folder (ExchangeAccount *account,
546                               const char *source_path,
547                               const char *dest_path,
548                               gboolean remove_source)
549 {
550         EFolder *source, *dest_parent;
551         ExchangeHierarchy *source_hier, *dest_hier;
552         const char *name;
553
554         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 
555                                 EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
556
557         if (!get_folder (account, source_path, &source, &source_hier) ||
558             !get_parent_and_name (account, &dest_path, &dest_parent, &dest_hier)) {
559                 /* Source or dest seems to not exist */
560                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
561         }
562         if (source_hier != dest_hier) {
563                 /* Can't move something between hierarchies */
564                 return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
565         }
566         if (remove_source) {
567                 name = e_folder_get_name (source);
568                 if (exchange_account_get_standard_uri (account, name))
569                         return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION;
570
571         }
572
573         return exchange_hierarchy_xfer_folder (source_hier, source,
574                                                dest_parent, dest_path,
575                                                remove_source);
576 }
577
578 static void
579 remove_hierarchy (ExchangeAccount *account, ExchangeHierarchy *hier)
580 {
581         int i;
582
583         for (i = 0; i < account->priv->hierarchies->len; i++) {
584                 if (account->priv->hierarchies->pdata[i] == hier) {
585                         g_ptr_array_remove_index_fast (account->priv->hierarchies, i);
586                         break;
587                 }
588         }
589         g_hash_table_remove (account->priv->foreign_hierarchies,
590                              hier->owner_email);
591         g_signal_handlers_disconnect_by_func (hier, hierarchy_new_folder, account);
592         g_signal_handlers_disconnect_by_func (hier, hierarchy_removed_folder, account);
593         g_object_unref (hier);
594 }
595
596 static void
597 setup_hierarchy (ExchangeAccount *account, ExchangeHierarchy *hier)
598 {
599         g_ptr_array_add (account->priv->hierarchies, hier);
600
601         g_signal_connect (hier, "new_folder",
602                           G_CALLBACK (hierarchy_new_folder), account);
603         g_signal_connect (hier, "removed_folder",
604                           G_CALLBACK (hierarchy_removed_folder), account);
605
606         exchange_hierarchy_add_to_storage (hier);
607 }
608
609 static void
610 setup_hierarchy_foreign (ExchangeAccount *account, ExchangeHierarchy *hier)
611 {
612         g_hash_table_insert (account->priv->foreign_hierarchies,
613                              (char *)hier->owner_email, hier);
614         setup_hierarchy (account, hier);
615 }
616
617 struct discover_data {
618         const char *user, *folder_name;
619         E2kOperation op;
620 };
621
622 static ExchangeHierarchy *
623 get_hierarchy_for (ExchangeAccount *account, E2kGlobalCatalogEntry *entry)
624 {
625         ExchangeHierarchy *hier;
626         char *hierarchy_name, *source;
627         char *physical_uri_prefix, *internal_uri_prefix;
628
629         hier = g_hash_table_lookup (account->priv->foreign_hierarchies,
630                                     entry->email);
631         if (hier)
632                 return hier;
633
634         /* i18n: This is the title of an "other user's folders"
635            hierarchy. Eg, "John Doe's Folders". */
636         hierarchy_name = g_strdup_printf (_("%s's Folders"),
637                                           entry->display_name);
638         source = g_strdup_printf ("exchange://%s@%s/", entry->mailbox,
639                                   account->exchange_server);
640         physical_uri_prefix = g_strdup_printf ("exchange://%s/;%s",
641                                                account->priv->uri_authority,
642                                                entry->email);
643         internal_uri_prefix = exchange_account_get_foreign_uri (account, entry,
644                                                                 NULL);
645
646         hier = exchange_hierarchy_foreign_new (account, hierarchy_name,
647                                                physical_uri_prefix,
648                                                internal_uri_prefix,
649                                                entry->display_name,
650                                                entry->email, source);
651         g_free (hierarchy_name);
652         g_free (physical_uri_prefix);
653         g_free (internal_uri_prefix);
654         g_free (source);
655
656         setup_hierarchy_foreign (account, hier);
657         return hier;
658 }
659
660 ExchangeAccountFolderResult
661 exchange_account_discover_shared_folder (ExchangeAccount *account,
662                                          const char *user,
663                                          const char *folder_name,
664                                          EFolder **folder)
665 {
666         struct discover_data dd;
667         ExchangeHierarchy *hier;
668         char *email;
669         E2kGlobalCatalogStatus status;
670         E2kGlobalCatalogEntry *entry;
671
672         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 
673                                 EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
674
675         if (!account->priv->gc)
676                 return EXCHANGE_ACCOUNT_FOLDER_GC_NOTREACHABLE;
677
678         email = strchr (user, '<');
679         if (email)
680                 email = g_strndup (email + 1, strcspn (email + 1, ">"));
681         else
682                 email = g_strdup (user);
683         hier = g_hash_table_lookup (account->priv->foreign_hierarchies, email);
684         if (hier) {
685                 g_free (email);
686                 return exchange_hierarchy_foreign_add_folder (hier, folder_name, folder);
687         }
688
689         dd.user = user;
690         dd.folder_name = folder_name;
691         e2k_operation_init (&dd.op);
692
693         g_mutex_lock (account->priv->discover_data_lock);
694         account->priv->discover_datas =
695                 g_list_prepend (account->priv->discover_datas, &dd);
696         g_mutex_unlock (account->priv->discover_data_lock);
697
698         status = e2k_global_catalog_lookup (account->priv->gc, &dd.op,
699                                             E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL,
700                                             email,
701                                             E2K_GLOBAL_CATALOG_LOOKUP_EMAIL |
702                                             E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX,
703                                             &entry);
704         g_free (email);
705         e2k_operation_free (&dd.op);
706
707         g_mutex_lock (account->priv->discover_data_lock);
708         account->priv->discover_datas =
709                 g_list_remove (account->priv->discover_datas, &dd);
710         g_mutex_unlock (account->priv->discover_data_lock);
711
712         if (status != E2K_GLOBAL_CATALOG_OK) {
713                 if (status == E2K_GLOBAL_CATALOG_ERROR)
714                         return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
715                 if (status == E2K_GLOBAL_CATALOG_NO_SUCH_USER)
716                         return EXCHANGE_ACCOUNT_FOLDER_NO_SUCH_USER;
717                 else
718                         return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
719         }
720
721         hier = get_hierarchy_for (account, entry);
722         return exchange_hierarchy_foreign_add_folder (hier, folder_name, folder);
723 }
724
725 void
726 exchange_account_cancel_discover_shared_folder (ExchangeAccount *account,
727                                                 const char *user,
728                                                 const char *folder_name)
729 {
730         struct discover_data *dd;
731         GList *dds;
732
733         g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
734
735         g_mutex_lock (account->priv->discover_data_lock);
736         for (dds = account->priv->discover_datas; dds; dds = dds->next) {
737                 dd = dds->data;
738                 if (!strcmp (dd->user, user) &&
739                     !strcmp (dd->folder_name, folder_name))
740                         break;
741         }
742         if (!dds) {
743                 g_mutex_unlock (account->priv->discover_data_lock);
744                 return;
745         }
746
747         e2k_operation_cancel (&dd->op);
748         g_mutex_unlock (account->priv->discover_data_lock);
749
750 #ifdef FIXME
751         /* We can't actually cancel the hierarchy's attempt to get
752          * the folder, but we can remove the hierarchy if appropriate.
753          */
754         if (dd->hier && exchange_hierarchy_is_empty (dd->hier))
755                 hierarchy_removed_folder (dd->hier, dd->hier->toplevel, account);
756 #endif
757 }
758
759 ExchangeAccountFolderResult
760 exchange_account_remove_shared_folder (ExchangeAccount *account,
761                                        const char *path)
762 {
763         ExchangeHierarchy *hier;
764         EFolder *folder;
765
766         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 
767                                 EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
768
769         if (!get_folder (account, path, &folder, &hier))
770                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
771         if (!EXCHANGE_IS_HIERARCHY_FOREIGN (hier))
772                 return EXCHANGE_ACCOUNT_FOLDER_UNSUPPORTED_OPERATION;
773
774         return exchange_hierarchy_remove_folder (hier, folder);
775 }
776
777 ExchangeAccountFolderResult
778 exchange_account_open_folder (ExchangeAccount *account, const char *path)
779 {
780         ExchangeHierarchy *hier;
781         EFolder *folder;
782         int mode;
783
784         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 
785                                 EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
786
787         d(g_print ("exchange_account_remove_folder: path=[%s]\n", path));
788
789         if (!get_folder (account, path, &folder, &hier))
790                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
791
792         exchange_account_is_offline (account, &mode);
793         if (mode == ONLINE_MODE && !account->priv->connected &&
794             hier == (ExchangeHierarchy *)account->priv->hierarchies->pdata[0] &&
795             folder == hier->toplevel) {
796                 /* The shell is asking us to open the personal folders
797                  * hierarchy, but we're already planning to do that
798                  * anyway. So just ignore the request for now.
799                  */
800                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
801         }               
802         
803         return exchange_hierarchy_scan_subtree (hier, folder, mode);
804 }
805
806 ExchangeAccountFolderResult
807 exchange_account_add_favorite (ExchangeAccount *account,
808                                EFolder         *folder)
809 {
810         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
811         g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
812
813         return exchange_hierarchy_favorites_add_folder (
814                 account->priv->favorites_hierarchy,
815                 folder);
816 }
817
818 ExchangeAccountFolderResult
819 exchange_account_remove_favorite (ExchangeAccount *account,
820                                   EFolder         *folder)
821 {
822         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
823         g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
824
825         return exchange_hierarchy_remove_folder (
826                 EXCHANGE_HIERARCHY (account->priv->favorites_hierarchy),
827                 folder);
828 }
829
830 gboolean
831 exchange_account_is_favorite_folder (ExchangeAccount *account,
832                                         EFolder         *folder)
833 {
834         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
835         g_return_val_if_fail (E_IS_FOLDER (folder), EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR);
836
837         return exchange_hierarchy_favorites_is_added (
838                 EXCHANGE_HIERARCHY (account->priv->favorites_hierarchy),
839                 folder);
840 }
841
842 static void
843 context_redirect (E2kContext *ctx, E2kHTTPStatus status,
844                   const char *old_uri, const char *new_uri,
845                   ExchangeAccount *account)
846 {
847         EFolder *folder;
848
849         folder = g_hash_table_lookup (account->priv->folders, old_uri);
850         if (!folder)
851                 return;
852
853         g_hash_table_remove (account->priv->folders, old_uri);
854         e_folder_exchange_set_internal_uri (folder, new_uri);
855         g_hash_table_insert (account->priv->folders,
856                              (char *)e_folder_exchange_get_internal_uri (folder),
857                              folder);
858 }
859
860 static void
861 set_sf_prop (const char *propname, E2kPropType type,
862              gpointer href, gpointer user_data)
863 {
864         ExchangeAccount *account = user_data;
865
866         propname = strrchr (propname, ':');
867         if (!propname++)
868                 return;
869
870         g_hash_table_insert (account->priv->standard_uris,
871                              g_strdup (propname),
872                              e2k_strdup_with_trailing_slash (href));
873 }
874
875 static const char *mailbox_info_props[] = {
876         E2K_PR_STD_FOLDER_CALENDAR,
877         E2K_PR_STD_FOLDER_CONTACTS,
878         E2K_PR_STD_FOLDER_DELETED_ITEMS,
879         E2K_PR_STD_FOLDER_DRAFTS,
880         E2K_PR_STD_FOLDER_INBOX,
881         E2K_PR_STD_FOLDER_JOURNAL,
882         E2K_PR_STD_FOLDER_NOTES,
883         E2K_PR_STD_FOLDER_OUTBOX,
884         E2K_PR_STD_FOLDER_SENT_ITEMS,
885         E2K_PR_STD_FOLDER_TASKS,
886         E2K_PR_STD_FOLDER_ROOT,
887         E2K_PR_STD_FOLDER_SENDMSG,
888
889         PR_STORE_ENTRYID,
890         E2K_PR_EXCHANGE_TIMEZONE
891 };
892 static const int n_mailbox_info_props = G_N_ELEMENTS (mailbox_info_props);
893
894
895
896 static gboolean
897 account_moved (ExchangeAccount *account, E2kAutoconfig *ac)
898 {
899         E2kAutoconfigResult result;
900         EAccount *eaccount;
901
902         result = e2k_autoconfig_check_exchange (ac, NULL);
903         if (result != E2K_AUTOCONFIG_OK)
904                 return FALSE;
905         result = e2k_autoconfig_check_global_catalog (ac, NULL);
906         if (result != E2K_AUTOCONFIG_OK)
907                 return FALSE;
908
909         eaccount = account->priv->account;
910
911         if (eaccount->source->url && eaccount->transport->url &&
912             !strcmp (eaccount->source->url, eaccount->transport->url)) {
913                 g_free (eaccount->transport->url);
914                 eaccount->transport->url = g_strdup (ac->account_uri);
915         }
916         g_free (eaccount->source->url);
917         eaccount->source->url = g_strdup (ac->account_uri);
918
919         e_account_list_change (account->priv->account_list, eaccount);
920         e_account_list_save (account->priv->account_list);
921         return TRUE;
922 }
923
924 #if 0
925 static gboolean
926 get_password (ExchangeAccount *account, E2kAutoconfig *ac, ExchangeAccountResult error)
927 {
928         char *password;
929
930         if (error != EXCHANGE_ACCOUNT_CONNECT_SUCCESS)
931                 e_passwords_forget_password ("Exchange", account->priv->password_key);
932
933         password = e_passwords_get_password ("Exchange", account->priv->password_key);
934 #if 0
935         // SURF : if (exchange_component_is_interactive (global_exchange_component)) {
936                 gboolean remember, oldremember;
937                 if (!password) {
938                         char *prompt;
939
940                         prompt = g_strdup_printf (_("Enter password for %s"),
941                                                   account->account_name);
942                         oldremember = remember = 
943                                         account->priv->account->source->save_passwd;
944                         password = e_passwords_ask_password (
945                                         _("Enter password"),
946                                         "Exchange", 
947                                         account->priv->password_key,
948                                         prompt, 
949                                         E_PASSWORDS_REMEMBER_FOREVER|E_PASSWORDS_SECRET,
950                                         &remember, 
951                                         NULL);
952                         if (remember != oldremember) {
953                                 account->priv->account->source->save_passwd = remember;
954                         }
955                         g_free (prompt);
956                 } 
957                 else if (!account->priv->account->source->save_passwd) {
958                         /* get_password returns the password cached but user has not 
959                          * selected remember password option, forget this password 
960                          * whis is stored temporarily by e2k_validate_user() 
961                          */
962                         e_passwords_forget_password ("Exchange", account->priv->password_key);
963                 }
964         }
965 #endif
966         if (!password) {
967         }
968         else if (!account->priv->account->source->save_passwd) {
969                 /* get_password returns the password cached but user has not 
970                  * selected remember password option, forget this password 
971                  * whis is stored temporarily by e2k_validate_user() 
972                  */
973                 e_passwords_forget_password ("Exchange", account->priv->password_key);
974         }
975
976         if (password) {
977                 e2k_autoconfig_set_password (ac, password);
978                 memset (password, 0, strlen (password));
979                 g_free (password);
980                 return TRUE;
981         } else
982                 return FALSE;
983 }
984 #endif
985
986 /* This uses the kerberos calls to check if the authentication failure
987  * was due to the password getting expired. If the password has expired
988  * this returns TRUE, else it returns FALSE.
989  */
990 #ifdef HAVE_KRB5
991 static gboolean
992 is_password_expired (ExchangeAccount *account, E2kAutoconfig *ac)
993 {
994         char *domain;
995         E2kKerberosResult result;
996
997         if (!ac->password)
998                 return FALSE;
999
1000         domain = ac->w2k_domain;
1001         if (!domain) {
1002                 domain = strchr (account->priv->identity_email, '@');
1003                 if (domain)
1004                         domain++;
1005         }
1006         if (!domain)
1007                 return FALSE;
1008
1009         result = e2k_kerberos_check_password (ac->username, domain,
1010                                               ac->password);
1011         if (result != E2K_KERBEROS_OK && 
1012             result != E2K_KERBEROS_PASSWORD_EXPIRED) {
1013                 /* try again with nt domain */
1014                 domain = ac->nt_domain;
1015                 if (domain)
1016                         result = e2k_kerberos_check_password (ac->username, 
1017                                                               domain,
1018                                                               ac->password);
1019         } 
1020
1021         return (result == E2K_KERBEROS_PASSWORD_EXPIRED);
1022 }
1023 #endif
1024
1025 static int
1026 find_passwd_exp_period (ExchangeAccount *account, E2kGlobalCatalogEntry *entry)
1027 {
1028         double max_pwd_age = 0;
1029         int max_pwd_age_days;
1030         E2kOperation gcop;
1031         E2kGlobalCatalogStatus gcstatus;
1032
1033         /* If user has not selected password expiry warning option, return */
1034         if (account->priv->passwd_exp_warn_period == -1)
1035                 return -1;
1036
1037         /* Check for password expiry period */ 
1038         /* This needs to be invoked after is_password_expired(), i.e., 
1039            only if password is not expired */
1040
1041         /* Check for account control value for a user */
1042
1043         e2k_operation_init (&gcop);
1044         gcstatus = e2k_global_catalog_lookup (account->priv->gc, 
1045                                               &gcop, 
1046                                               E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL, 
1047                                               account->priv->identity_email, 
1048                                               E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL, 
1049                                               &entry); 
1050         e2k_operation_free (&gcop);
1051         if (gcstatus != E2K_GLOBAL_CATALOG_OK) 
1052                 return -1;
1053        
1054         if (entry->user_account_control & ADS_UF_DONT_EXPIRE_PASSWORD) {
1055                 return -1;         /* Password is not set to expire */
1056         }
1057
1058         /* Here we don't check not setting the password and expired password */ 
1059         /* Check for the maximum password age set */
1060
1061         e2k_operation_init (&gcop); 
1062         max_pwd_age = lookup_passwd_max_age (account->priv->gc, &gcop); 
1063         e2k_operation_free (&gcop);
1064
1065         if (max_pwd_age > 0) {
1066                 /* Calculate password expiry period */
1067                 max_pwd_age_days = 
1068                 ( max_pwd_age * ONE_HUNDRED_NANOSECOND ) / SECONDS_IN_DAY;
1069
1070                 if (max_pwd_age_days <= account->priv->passwd_exp_warn_period) {
1071                         return max_pwd_age_days;
1072                 }
1073         } 
1074         return -1;
1075 }
1076
1077 char *
1078 exchange_account_get_password (ExchangeAccount *account)
1079 {
1080         return e_passwords_get_password ("Exchange", account->priv->password_key);
1081 }
1082
1083 void
1084 exchange_account_forget_password (ExchangeAccount *account)
1085 {
1086         e_passwords_forget_password ("Exchange", account->priv->password_key);
1087 }
1088
1089 ExchangeAccountResult
1090 exchange_account_set_password (ExchangeAccount *account, char *old_pass, char *new_pass)
1091 {
1092 #ifdef HAVE_KRB5
1093         E2kKerberosResult result;
1094         char *domain;
1095
1096         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED);
1097         g_return_val_if_fail (old_pass != NULL, EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED);
1098         g_return_val_if_fail (new_pass != NULL, EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED);
1099
1100         domain = account->priv->gc ? account->priv->gc->domain : NULL;
1101         if (!domain) {
1102                 domain = strchr (account->priv->identity_email, '@');
1103                 if (domain)
1104                         domain++;
1105         }
1106         if (!domain) {
1107                 /* email id is not proper, we return instead of trying nt_domain */
1108                 return EXCHANGE_ACCOUNT_CONFIG_ERROR;
1109         }
1110
1111         result = e2k_kerberos_change_password (account->priv->username, domain,
1112                                                old_pass, new_pass);
1113         if (result != E2K_KERBEROS_OK && result != E2K_KERBEROS_PASSWORD_TOO_WEAK) {
1114                 /* try with nt_domain */
1115                 domain = account->priv->nt_domain;
1116                 if (domain)
1117                         result = e2k_kerberos_change_password (account->priv->username, 
1118                                                                domain, old_pass,
1119                                                                new_pass);
1120         }
1121         switch (result) {
1122         case E2K_KERBEROS_OK:
1123                 e_passwords_forget_password ("Exchange", account->priv->password_key);
1124                 e_passwords_add_password (account->priv->password_key, new_pass);
1125                 if (account->priv->account->source->save_passwd)
1126                         e_passwords_remember_password ("Exchange", account->priv->password_key);
1127                 break;
1128
1129         case E2K_KERBEROS_PASSWORD_TOO_WEAK:
1130                 return EXCHANGE_ACCOUNT_PASSWORD_WEAK_ERROR;
1131
1132         case E2K_KERBEROS_FAILED:
1133         default:
1134                 return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED;
1135         }
1136         
1137         return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_SUCCESS;
1138 #else
1139         g_warning ("exchange_account_set_password: Not implemented (no KRB5)");
1140         return EXCHANGE_ACCOUNT_PASSWORD_CHANGE_FAILED;
1141 #endif
1142 }
1143
1144 void 
1145 exchange_account_set_save_password (ExchangeAccount *account, gboolean save_password)
1146 {
1147         account->priv->account->source->save_passwd = save_password;
1148 }
1149
1150 gboolean 
1151 exchange_account_is_save_password (ExchangeAccount *account)
1152 {
1153         return account->priv->account->source->save_passwd;
1154 }
1155
1156
1157 /**
1158  * exchange_account_set_offline:
1159  * @account: an #ExchangeAccount
1160  *
1161  * This nullifies the connection and sets the account as offline.
1162  * The caller should take care that the required data is fetched
1163  * before calling this method.
1164  *
1165  * Return value: Returns TRUE is successfully sets the account to
1166  * offline or FALSE if failed
1167  **/
1168 gboolean
1169 exchange_account_set_offline (ExchangeAccount *account)
1170 {
1171                 
1172         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), FALSE);
1173
1174         g_mutex_lock (account->priv->connect_lock);
1175         if (account->priv->ctx) {
1176                 g_object_unref (account->priv->ctx);
1177                 account->priv->ctx = NULL;
1178         }
1179
1180         account->priv->account_online = OFFLINE_MODE;
1181         g_mutex_unlock (account->priv->connect_lock);
1182         return TRUE;
1183 }
1184
1185 /**
1186  * exchange_account_set_online:
1187  * @account: an #ExchangeAccount
1188  *
1189  * This nullifies the connection and sets the account as offline.
1190  * The caller should take care that the required data is fetched
1191  * before calling this method.
1192  *
1193  * Return value: Returns TRUE is successfully sets the account to
1194  * offline or FALSE if failed
1195  **/
1196 gboolean
1197 exchange_account_set_online (ExchangeAccount *account)
1198 {
1199         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), FALSE);
1200
1201         g_mutex_lock (account->priv->connect_lock);
1202         account->priv->account_online = ONLINE_MODE;
1203         g_mutex_unlock (account->priv->connect_lock);
1204         
1205         return TRUE;
1206 }
1207
1208 /**
1209  * exchange_account_is_offline:
1210  * @account: an #ExchangeAccount
1211  *
1212  * Return value: Returns TRUE if account is offline
1213  **/
1214 void
1215 exchange_account_is_offline (ExchangeAccount *account, int *state)
1216 {
1217         g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
1218         
1219         *state = account->priv->account_online;
1220 }
1221
1222 static gboolean
1223 setup_account_hierarchies (ExchangeAccount *account)
1224 {
1225         ExchangeHierarchy *hier, *personal_hier;
1226         ExchangeAccountFolderResult fresult;
1227         char *phys_uri_prefix, *dir;
1228         GDir *d;
1229         const char *dent;
1230         int mode;
1231
1232         exchange_account_is_offline (account, &mode);
1233
1234         if (mode == UNSUPPORTED_MODE)
1235                 return FALSE;
1236
1237         /* Check if folder hierarchies are already setup. */
1238         if (account->priv->hierarchies->len > 0)
1239                 goto hierarchies_created;
1240
1241         /* Set up Personal Folders hierarchy */
1242         phys_uri_prefix = g_strdup_printf ("exchange://%s/;personal",
1243                                            account->priv->uri_authority);
1244         hier = exchange_hierarchy_webdav_new (account,
1245                                               EXCHANGE_HIERARCHY_PERSONAL,
1246                                               _("Personal Folders"),
1247                                               phys_uri_prefix,
1248                                               account->home_uri,
1249                                               account->priv->identity_name,
1250                                               account->priv->identity_email,
1251                                               account->priv->source_uri,
1252                                               TRUE);
1253
1254         setup_hierarchy (account, hier);
1255         g_free (phys_uri_prefix);
1256
1257         /* Favorite Public Folders */
1258         phys_uri_prefix = g_strdup_printf ("exchange://%s/;favorites",
1259                                            account->priv->uri_authority);
1260         hier = exchange_hierarchy_favorites_new (account,
1261                                                  _("Favorite Public Folders"),
1262                                                  phys_uri_prefix,
1263                                                  account->home_uri,
1264                                                  account->public_uri,
1265                                                  account->priv->identity_name,
1266                                                  account->priv->identity_email,
1267                                                  account->priv->source_uri);
1268         setup_hierarchy (account, hier);
1269         g_free (phys_uri_prefix);
1270         account->priv->favorites_hierarchy = hier;
1271
1272         /* Public Folders */
1273         phys_uri_prefix = g_strdup_printf ("exchange://%s/;public",
1274                                            account->priv->uri_authority);
1275         hier = exchange_hierarchy_webdav_new (account,
1276                                               EXCHANGE_HIERARCHY_PUBLIC,
1277                                               /* i18n: Outlookism */
1278                                               _("All Public Folders"),
1279                                               phys_uri_prefix,
1280                                               account->public_uri,
1281                                               account->priv->identity_name,
1282                                               account->priv->identity_email,
1283                                               account->priv->source_uri,
1284                                               FALSE);
1285         setup_hierarchy (account, hier);
1286         g_free (phys_uri_prefix);
1287
1288         /* Global Address List */
1289         phys_uri_prefix = g_strdup_printf ("gal://%s/gal",
1290                                            account->priv->uri_authority);
1291                                                      /* i18n: Outlookism */
1292         hier = exchange_hierarchy_gal_new (account, _("Global Address List"),
1293                                            phys_uri_prefix);
1294         setup_hierarchy (account, hier);
1295         g_free (phys_uri_prefix);
1296
1297         /* Other users' folders */
1298         d = g_dir_open (account->storage_dir, 0, NULL);
1299         if (d) {
1300                 while ((dent = g_dir_read_name (d))) {
1301                         if (!strchr (dent, '@'))
1302                                 continue;
1303                         dir = g_strdup_printf ("%s/%s", account->storage_dir, dent);
1304                         hier = exchange_hierarchy_foreign_new_from_dir (account, dir);
1305                         g_free (dir);
1306                         if (!hier)
1307                                 continue;
1308
1309                         setup_hierarchy_foreign (account, hier);
1310                 }
1311                 g_dir_close (d);
1312         }
1313
1314 hierarchies_created:
1315         
1316         /* Scan the personal and favorite folders so we can resolve references
1317          * to the Calendar, Contacts, etc even if the tree isn't
1318          * opened.
1319          */
1320
1321         /* Assuming the first element being personal hierarchy. */
1322         personal_hier = account->priv->hierarchies->pdata[0];
1323         
1324         fresult = exchange_hierarchy_scan_subtree (personal_hier,
1325                                                    personal_hier->toplevel,
1326                                                    mode);
1327         if (fresult != EXCHANGE_ACCOUNT_FOLDER_OK) {
1328                 account->priv->connecting = FALSE;
1329                 return FALSE;
1330         }
1331
1332         account->mbox_size = exchange_hierarchy_webdav_get_total_folder_size (
1333                                         EXCHANGE_HIERARCHY_WEBDAV (personal_hier));
1334
1335         fresult = exchange_hierarchy_scan_subtree (
1336                 account->priv->favorites_hierarchy,
1337                 account->priv->favorites_hierarchy->toplevel,
1338                 mode);
1339         if (fresult != EXCHANGE_ACCOUNT_FOLDER_OK && 
1340             fresult != EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST) {
1341                 account->priv->connecting = FALSE;
1342                 return FALSE;
1343         }
1344         return TRUE;
1345 }
1346
1347 /**
1348  * exchange_account_connect:
1349  * @account: an #ExchangeAccount
1350  *
1351  * This attempts to connect to @account. If the shell has enabled user
1352  * interaction, then it will prompt for a password if needed, and put
1353  * up an error message if the connection attempt failed.
1354  *
1355  * Return value: an #E2kContext, or %NULL if the connection attempt
1356  * failed.
1357  **/
1358 E2kContext *
1359 exchange_account_connect (ExchangeAccount *account, const char *pword, 
1360                           ExchangeAccountResult *info_result)
1361 {
1362         E2kAutoconfig *ac;
1363         E2kAutoconfigResult result;
1364         E2kHTTPStatus status;
1365         gboolean redirected = FALSE;
1366         E2kResult *results;
1367         int nresults = 0, mode;
1368         GByteArray *entryid;
1369         const char *tz;
1370         E2kGlobalCatalogStatus gcstatus;
1371         E2kGlobalCatalogEntry *entry;
1372         E2kOperation gcop;
1373         char *user_name = NULL;
1374
1375         *info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR; 
1376         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1377
1378         *info_result = EXCHANGE_ACCOUNT_CONNECT_SUCCESS;
1379         exchange_account_is_offline (account, &mode);
1380         
1381         g_mutex_lock (account->priv->connect_lock);
1382
1383         if (mode == UNSUPPORTED_MODE) {
1384                 *info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR;
1385                 account->priv->connecting = FALSE;
1386                 g_mutex_unlock (account->priv->connect_lock);
1387                 return NULL;
1388         }
1389         
1390         if (account->priv->connecting || mode == OFFLINE_MODE) {
1391                 g_mutex_unlock (account->priv->connect_lock);
1392                 if (mode == OFFLINE_MODE) {
1393                         setup_account_hierarchies (account);
1394                         *info_result = EXCHANGE_ACCOUNT_OFFLINE;
1395                 }
1396                 else {
1397                         *info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR;
1398                 }
1399                 return NULL;
1400         } else if (account->priv->ctx) {
1401                 g_mutex_unlock (account->priv->connect_lock);
1402                 return account->priv->ctx;
1403         }
1404
1405         account->priv->connecting = TRUE;
1406
1407         if (account->priv->windows_domain)
1408                 user_name = g_strdup_printf ("%s\\%s", account->priv->windows_domain, account->priv->username);
1409         else
1410                 user_name = g_strdup (account->priv->username);
1411
1412         ac = e2k_autoconfig_new (account->home_uri,
1413                                  user_name,
1414                                  NULL,
1415                                  account->priv->auth_pref);
1416         g_free (user_name);
1417
1418         e2k_autoconfig_set_gc_server (ac, account->priv->ad_server,
1419                                       account->priv->ad_limit);
1420
1421         if (!pword) {
1422                 account->priv->connecting = FALSE;
1423                 g_mutex_unlock (account->priv->connect_lock);
1424                 *info_result = EXCHANGE_ACCOUNT_PASSWORD_INCORRECT;
1425                 e2k_autoconfig_free (ac);
1426                 return NULL;
1427         }
1428
1429         e2k_autoconfig_set_password (ac, pword);
1430
1431  try_connect_again:
1432         account->priv->ctx = e2k_autoconfig_get_context (ac, NULL, &result);
1433
1434         if (!account->priv->nt_domain && ac->nt_domain)
1435                 account->priv->nt_domain = g_strdup (ac->nt_domain);
1436         else
1437                 account->priv->nt_domain = NULL;
1438
1439         if (result != E2K_AUTOCONFIG_OK) {
1440 #ifdef HAVE_KRB5
1441                 if ( is_password_expired (account, ac)) {
1442                         *info_result = EXCHANGE_ACCOUNT_PASSWORD_EXPIRED;
1443                         account->priv->connecting = FALSE;
1444                         g_mutex_unlock (account->priv->connect_lock);
1445                         e2k_autoconfig_free (ac);
1446                         return NULL;
1447                 }
1448 #endif
1449                 switch (result) {
1450
1451                 case E2K_AUTOCONFIG_AUTH_ERROR:
1452                         *info_result = EXCHANGE_ACCOUNT_PASSWORD_INCORRECT;
1453                         e2k_autoconfig_free (ac);
1454                         account->priv->connecting = FALSE;
1455                         g_mutex_unlock (account->priv->connect_lock);
1456                         return NULL;
1457
1458                 case E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN:
1459                         *info_result = EXCHANGE_ACCOUNT_DOMAIN_ERROR;
1460                         e2k_autoconfig_free (ac);
1461                         account->priv->connecting = FALSE;
1462                         g_mutex_unlock (account->priv->connect_lock);
1463                         return NULL;
1464
1465                 case E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM:
1466                         ac->use_ntlm = 1;
1467                         goto try_connect_again;
1468
1469                 case E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC:
1470                         ac->use_ntlm = 0;
1471                         goto try_connect_again;
1472
1473                 case E2K_AUTOCONFIG_REDIRECT:
1474                         if (!redirected && account_moved (account, ac))
1475                                 goto try_connect_again;
1476                         break;
1477
1478                 case E2K_AUTOCONFIG_TRY_SSL:
1479                         if (account_moved (account, ac))
1480                                 goto try_connect_again;
1481                         break;
1482
1483                 default:
1484                         break;
1485                 }
1486
1487                 e2k_autoconfig_free (ac);
1488                 account->priv->connecting = FALSE;
1489                 account->priv->account_online = OFFLINE_MODE; /* correct? */
1490
1491                 switch (result) {
1492                 case E2K_AUTOCONFIG_REDIRECT:
1493                 case E2K_AUTOCONFIG_TRY_SSL:
1494                         *info_result = EXCHANGE_ACCOUNT_MAILBOX_NA;
1495                         break;
1496                 case E2K_AUTOCONFIG_EXCHANGE_5_5:
1497                         *info_result = EXCHANGE_ACCOUNT_VERSION_ERROR;
1498                         break;
1499                 case E2K_AUTOCONFIG_NOT_EXCHANGE:
1500                 case E2K_AUTOCONFIG_NO_OWA:
1501                         *info_result = EXCHANGE_ACCOUNT_WSS_ERROR;
1502                         break;
1503                 case E2K_AUTOCONFIG_NO_MAILBOX:
1504                         *info_result = EXCHANGE_ACCOUNT_NO_MAILBOX;
1505                         break;
1506                 case E2K_AUTOCONFIG_CANT_RESOLVE:
1507                         *info_result = EXCHANGE_ACCOUNT_RESOLVE_ERROR;
1508                         break;
1509                 case E2K_AUTOCONFIG_CANT_CONNECT:
1510                         *info_result = EXCHANGE_ACCOUNT_CONNECT_ERROR;
1511                         break;
1512                 case E2K_AUTOCONFIG_CANCELLED:
1513                         break;
1514                 default:
1515                         *info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR;
1516                         break;
1517                 }
1518
1519                 g_mutex_unlock (account->priv->connect_lock);
1520                 return NULL;
1521         }
1522
1523         account->priv->gc = e2k_autoconfig_get_global_catalog (ac, NULL);
1524         e2k_autoconfig_free (ac);
1525
1526         status = e2k_context_propfind (account->priv->ctx, NULL,
1527                                        account->home_uri,
1528                                        mailbox_info_props,
1529                                        n_mailbox_info_props,
1530                                        &results, &nresults);
1531
1532         if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
1533                 account->priv->connecting = FALSE;
1534                 *info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR;
1535                 g_mutex_unlock (account->priv->connect_lock);
1536                 return NULL; /* FIXME: what error has happened? */
1537         }
1538
1539         if (nresults) {
1540                 account->priv->standard_uris =
1541                         g_hash_table_new (e2k_ascii_strcase_hash,
1542                                           e2k_ascii_strcase_equal);
1543                 e2k_properties_foreach (results[0].props, set_sf_prop, account);
1544
1545                 /* FIXME: we should get these from the autoconfig */
1546                 entryid = e2k_properties_get_prop (results[0].props, PR_STORE_ENTRYID);
1547                 if (entryid)
1548                         account->legacy_exchange_dn = g_strdup (e2k_entryid_to_dn (entryid));
1549
1550                 tz = e2k_properties_get_prop (results[0].props, E2K_PR_EXCHANGE_TIMEZONE);
1551                 if (tz)
1552                         account->default_timezone = g_strdup (tz);
1553         }
1554
1555         if (!setup_account_hierarchies (account)) {
1556                 *info_result = EXCHANGE_ACCOUNT_UNKNOWN_ERROR;
1557                 g_mutex_unlock (account->priv->connect_lock);
1558                 if (nresults)
1559                         e2k_results_free (results, nresults);
1560                 return NULL; /* FIXME: what error has happened? */
1561         }
1562
1563         account->priv->account_online = ONLINE_MODE;
1564         account->priv->connecting = FALSE;
1565         account->priv->connected = TRUE;
1566
1567         if (!account->priv->gc)
1568                 goto skip_quota;
1569         /* Check for quota usage */
1570         e2k_operation_init (&gcop);
1571         gcstatus = e2k_global_catalog_lookup (account->priv->gc, &gcop,
1572                                             E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL,
1573                                             account->priv->identity_email,
1574                                             E2K_GLOBAL_CATALOG_LOOKUP_QUOTA,
1575                                             &entry);    
1576         e2k_operation_free (&gcop);
1577
1578         /* FIXME: warning message should have quota limit value 
1579          */
1580         if (gcstatus == E2K_GLOBAL_CATALOG_OK) {
1581
1582                 if (entry->quota_norecv && 
1583                         account->mbox_size >= entry->quota_norecv) {
1584                                 *info_result = EXCHANGE_ACCOUNT_QUOTA_RECIEVE_ERROR;
1585                                 account->priv->quota_limit = entry->quota_norecv;
1586                 } else if (entry->quota_nosend && 
1587                                 account->mbox_size >= entry->quota_nosend) {
1588                                         *info_result = EXCHANGE_ACCOUNT_QUOTA_SEND_ERROR;
1589                                         account->priv->quota_limit = entry->quota_nosend;
1590                 } else if (entry->quota_warn && 
1591                                 account->mbox_size >= entry->quota_warn) {
1592                                         *info_result = EXCHANGE_ACCOUNT_QUOTA_WARN;
1593                                         account->priv->quota_limit = entry->quota_warn;
1594                 }
1595         }
1596
1597 skip_quota:
1598         g_signal_connect (account->priv->ctx, "redirect",
1599                           G_CALLBACK (context_redirect), account);
1600
1601         g_signal_emit (account, signals[CONNECTED], 0, account->priv->ctx);
1602         g_mutex_unlock (account->priv->connect_lock);
1603         if (nresults)
1604                 e2k_results_free (results, nresults);
1605         return account->priv->ctx;
1606 }
1607
1608 /**
1609  * exchange_account_is_offline_sync_set:
1610  * @account: an #ExchangeAccount
1611  *
1612  * Return value: TRUE if offline_sync is set for @account and FALSE if not.
1613  */
1614 void
1615 exchange_account_is_offline_sync_set (ExchangeAccount *account, int *mode)
1616 {
1617         *mode = UNSUPPORTED_MODE;
1618
1619         g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
1620
1621         if (account->priv->offline_sync)
1622                 *mode = OFFLINE_MODE;
1623         else
1624                 *mode = ONLINE_MODE;
1625 }
1626
1627 /**
1628  * exchange_account_get_context:
1629  * @account: an #ExchangeAccount
1630  *
1631  * Return value: @account's #E2kContext, if it is connected and
1632  * online, or %NULL if not.
1633  **/
1634 E2kContext *
1635 exchange_account_get_context (ExchangeAccount *account)
1636 {
1637         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1638
1639         return account->priv->ctx;
1640 }
1641
1642 /**
1643  * exchange_account_get_global_catalog:
1644  * @account: an #ExchangeAccount
1645  *
1646  * Return value: @account's #E2kGlobalCatalog, if it is connected and
1647  * online, or %NULL if not.
1648  **/
1649 E2kGlobalCatalog *
1650 exchange_account_get_global_catalog (ExchangeAccount *account)
1651 {
1652         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1653                 
1654         return account->priv->gc;
1655 }
1656
1657 /**
1658  * exchange_account_fetch:
1659  * @acct: an #ExchangeAccount
1660  *
1661  * Return value: @account's #EAccount, if it is connected and
1662  * online, or %NULL if not.
1663  **/    
1664 EAccount *
1665 exchange_account_fetch (ExchangeAccount *acct)
1666 {
1667         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (acct), NULL);
1668
1669         return acct->priv->account;
1670 }
1671
1672 /**
1673  * exchange_account_get_standard_uri:
1674  * @account: an #ExchangeAccount
1675  * @item: the short name of the standard URI
1676  *
1677  * Looks up the value of one of the standard URIs on @account.
1678  * Supported values for @item are:
1679  *   "calendar", "contacts", "deleteditems", "drafts", "inbox",
1680  *   "journal", "notes", "outbox", "sentitems", "tasks", and
1681  *   "sendmsg" (the special mail submission URI)
1682  *
1683  * Return value: the value of the standard URI, or %NULL if the
1684  * account is not connected or the property is invalid or not
1685  * defined on @account.
1686  **/
1687 const char *
1688 exchange_account_get_standard_uri (ExchangeAccount *account, const char *item)
1689 {
1690         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1691
1692         if (!account->priv->standard_uris)
1693                 return NULL;
1694
1695         return g_hash_table_lookup (account->priv->standard_uris, item);
1696 }
1697
1698 /**
1699  * exchange_account_get_standard_uri_for:
1700  * @account: an #ExchangeAccount
1701  * @home_uri: the home URI of a user
1702  * @std_uri_prop: the %E2K_PR_STD_FOLDER property to look up
1703  *
1704  * Looks up the URI of a folder in another user's mailbox.
1705  *
1706  * Return value: the URI of the folder, or %NULL if either the folder
1707  * doesn't exist or the user doesn't have permission to access it.
1708  **/
1709 char *
1710 exchange_account_get_standard_uri_for (ExchangeAccount *account,
1711                                        const char *home_uri,
1712                                        const char *std_uri_prop)
1713 {
1714         char *foreign_uri, *prop;
1715         E2kHTTPStatus status;
1716         E2kResult *results;
1717         int nresults = 0;
1718
1719         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1720
1721         foreign_uri = e2k_uri_concat (home_uri, "NON_IPM_SUBTREE");
1722         status = e2k_context_propfind (account->priv->ctx, NULL, foreign_uri,
1723                                        &std_uri_prop, 1,
1724                                        &results, &nresults);
1725         g_free (foreign_uri);
1726
1727         if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status) || nresults == 0)
1728                 return NULL;
1729
1730         prop = e2k_properties_get_prop (results[0].props, std_uri_prop);
1731         if (prop)
1732                 foreign_uri = e2k_strdup_with_trailing_slash (prop);
1733         else
1734                 foreign_uri = NULL;
1735         e2k_results_free (results, nresults);
1736
1737         return foreign_uri;
1738 }
1739
1740 /**
1741  * exchange_account_get_foreign_uri:
1742  * @account: an #ExchangeAccount
1743  * @entry: an #E2kGlobalCatalogEntry with mailbox data
1744  * @std_uri_prop: the %E2K_PR_STD_FOLDER property to look up, or %NULL
1745  *
1746  * Looks up the URI of a folder in another user's mailbox. If
1747  * @std_uri_prop is %NULL, the URI for the top level of the user's
1748  * mailbox is returned.
1749  *
1750  * Return value: the URI of the folder, or %NULL if either the folder
1751  * doesn't exist or the user doesn't have permission to access it.
1752  **/
1753 char *
1754 exchange_account_get_foreign_uri (ExchangeAccount *account,
1755                                   E2kGlobalCatalogEntry *entry,
1756                                   const char *std_uri_prop)
1757 {
1758         char *home_uri, *foreign_uri;
1759
1760         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1761
1762         if (account->priv->uris_use_email) {
1763                 char *mailbox;
1764
1765                 mailbox = g_strndup (entry->email, strcspn (entry->email, "@"));
1766                 home_uri = g_strdup_printf (account->priv->http_uri_schema,
1767                                             entry->exchange_server, mailbox);
1768                 g_free (mailbox);
1769         } else {
1770                 home_uri = g_strdup_printf (account->priv->http_uri_schema,
1771                                             entry->exchange_server,
1772                                             entry->mailbox);
1773         }
1774         if (!std_uri_prop)
1775                 return home_uri;
1776
1777         foreign_uri = exchange_account_get_standard_uri_for (account,
1778                                                              home_uri,
1779                                                              std_uri_prop);
1780         g_free (home_uri);
1781
1782         return foreign_uri;
1783 }
1784
1785 /**
1786  * exchange_account_get_hierarchy_by_email:
1787  * @account: an #ExchangeAccount
1788  * @email: email id of the foreign user
1789  *
1790  * If the hierarchy is present just return it back. Else try to get it
1791  * from the filesystem and return it. 
1792  *
1793  * Return value: Returns the ExchangeHierarchy of the foreign user's folder.
1794  **/
1795
1796 ExchangeHierarchy *
1797 exchange_account_get_hierarchy_by_email (ExchangeAccount *account, const char *email)
1798 {
1799         char *dir;
1800         ExchangeHierarchy *hier = NULL;
1801         int mode;
1802
1803         g_return_val_if_fail (email != NULL, NULL);
1804
1805         hier = g_hash_table_lookup (account->priv->foreign_hierarchies, email);
1806         if (!hier) {
1807                 dir = g_strdup_printf ("%s/%s", account->storage_dir, email);
1808                 if (g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
1809                         hier = exchange_hierarchy_foreign_new_from_dir (account, dir);
1810                         g_free (dir);
1811                         if (hier) {
1812                                 exchange_account_is_offline (account, &mode);
1813                                 setup_hierarchy_foreign (account, hier);
1814                         }
1815                 }
1816         }
1817
1818         return hier;
1819  }
1820
1821 /**
1822  * exchange_account_get_folder:
1823  * @account: an #ExchangeAccount
1824  * @path_or_uri: the shell path or URI referring to the folder
1825  *
1826  * Return value: an #EFolder corresponding to the indicated
1827  * folder.
1828  **/
1829 EFolder *
1830 exchange_account_get_folder (ExchangeAccount *account,
1831                              const char *path_or_uri)
1832 {
1833         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1834
1835         if (!path_or_uri)
1836                 return NULL;
1837         return g_hash_table_lookup (account->priv->folders, path_or_uri);
1838 }
1839
1840 static int
1841 folder_comparator (const void *a, const void *b)
1842 {
1843         EFolder **fa = (EFolder **)a;
1844         EFolder **fb = (EFolder **)b;
1845
1846         return strcmp (e_folder_exchange_get_path (*fa),
1847                        e_folder_exchange_get_path (*fb));
1848 }
1849
1850 struct _folders_tree {
1851         char *path;
1852         GPtrArray *folders;
1853 };
1854
1855
1856 static void
1857 add_folder (gpointer key, gpointer value, gpointer folders)
1858 {
1859         EFolder *folder = value;
1860
1861         d(g_print ("%s(%d):%s: key=[%s]\t folder-path=[%s]\n", __FILE__, __LINE__, G_GNUC_PRETTY_FUNCTION, 
1862                    key, e_folder_exchange_get_path (folder)));
1863
1864         /* Each folder appears under three different keys, but
1865          * we only want to add it to the results array once. So
1866          * we only add when we see the "path" key.
1867          */
1868         if (!strcmp (key, e_folder_exchange_get_path (folder))) 
1869                 g_ptr_array_add (folders, folder);
1870 }
1871
1872 static void
1873 add_folder_tree (gpointer key, gpointer value, gpointer folders)
1874 {
1875         EFolder *folder = value;
1876         struct _folders_tree *fld_tree = (struct _folders_tree *) folders;
1877
1878         if (!fld_tree || !fld_tree->path)
1879                 return;
1880
1881         if (g_str_has_prefix (key, fld_tree->path))
1882                 add_folder (key, folder, fld_tree->folders);
1883 }
1884
1885 /**
1886  * exchange_account_get_folders:
1887  * @account: an #ExchangeAccount
1888  *
1889  * Return an array of folders (sorted such that parents will occur
1890  * before children). If the caller wants to keep up to date with the
1891  * list of folders, he should also listen to %new_folder and
1892  * %removed_folder signals.
1893  *
1894  * Return value: an array of folders. The array should be freed with
1895  * g_ptr_array_free().
1896  **/
1897 GPtrArray *
1898 exchange_account_get_folders (ExchangeAccount *account)
1899 {
1900         GPtrArray *folders;
1901
1902         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1903
1904         folders = g_ptr_array_new ();
1905         /*      if (account->priv->fresh_folders)
1906                 g_hash_table_foreach (account->priv->fresh_folders, add_folder, folders);
1907         else
1908         */
1909                 g_hash_table_foreach (account->priv->folders, add_folder, folders);
1910
1911         qsort (folders->pdata, folders->len,
1912                sizeof (EFolder *), folder_comparator);
1913
1914         return folders;
1915 }       
1916
1917 /**
1918  * exchange_account_get_folder_tree:
1919  * @account: an #ExchangeAccount
1920  *
1921  * Return an array of folders (sorted such that parents will occur
1922  * before children). If the caller wants to keep up to date with the
1923  * list of folders, he should also listen to %new_folder and
1924  * %removed_folder signals.
1925  *
1926  * Return value: an array of folders. The array should be freed with
1927  * g_ptr_array_free().
1928  **/
1929 GPtrArray *
1930 exchange_account_get_folder_tree (ExchangeAccount *account, char* path)
1931 {
1932         GPtrArray *folders = NULL;
1933         EFolder *folder = NULL;
1934         ExchangeHierarchy *hier = NULL;
1935
1936         struct _folders_tree *fld_tree = NULL;
1937
1938         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1939
1940         if (!get_folder (account, path, &folder, &hier))
1941                 return folders;
1942
1943         exchange_hierarchy_scan_subtree (hier, folder, account->priv->account_online);
1944
1945         folders = g_ptr_array_new ();
1946         fld_tree = g_new0 (struct _folders_tree, 1);
1947         fld_tree->path = path;
1948         fld_tree->folders = folders;
1949
1950         /*      if (account->priv->fresh_folders)
1951                 g_hash_table_foreach (account->priv->fresh_folders, add_folder, folders);
1952         else
1953         */
1954         g_hash_table_foreach (account->priv->folders, add_folder_tree, fld_tree);
1955
1956         qsort (folders->pdata, folders->len,
1957                sizeof (EFolder *), folder_comparator);
1958
1959         g_free (fld_tree);
1960
1961         return folders;
1962 }       
1963
1964 /**
1965  * exchange_account_get_quota_limit:
1966  * @account: an #ExchangeAccount
1967  *
1968  * Return the value of the quota limit reached.
1969  *
1970  * Return value: an int
1971  **/
1972 int
1973 exchange_account_get_quota_limit (ExchangeAccount *account)
1974 {
1975         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 0);
1976
1977         return account->priv->quota_limit;
1978 }
1979
1980 int
1981 exchange_account_check_password_expiry (ExchangeAccount *account)
1982 {
1983         E2kGlobalCatalogEntry *entry; /* This is never set before it's used! */
1984         int max_pwd_age_days = -1;
1985
1986         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), 0);
1987
1988         max_pwd_age_days = find_passwd_exp_period (account, entry);
1989         return max_pwd_age_days;
1990 }
1991
1992 char *
1993 exchange_account_get_username (ExchangeAccount *account)
1994 {
1995         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
1996         
1997         return account->priv->username;
1998 }
1999
2000 /**
2001   * exchange_account_get_email_id :
2002   * @account : #ExchangeAccount
2003   *
2004   * Retunrs user's e-mail id. 
2005   *
2006   * Return value : e-mail id string.
2007   **/
2008 char *
2009 exchange_account_get_email_id (ExchangeAccount *account)
2010 {
2011         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
2012
2013         return account->priv->identity_email;
2014
2015
2016 /**
2017   * exchange_account_folder_size_add :
2018   * @account : #ExchangeAccount
2019   * @folder_name : 
2020   * @size : Size of @folder_name
2021   *
2022   * Updates the #ExchangeFolderSize object with the @size of @folder_name
2023   *
2024   * Return value : void
2025   **/
2026 void
2027 exchange_account_folder_size_add (ExchangeAccount *account,
2028                                      const char *folder_name,
2029                                      gdouble size)
2030 {
2031         g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
2032
2033         exchange_folder_size_update (account->priv->fsize, folder_name, size);
2034 }
2035
2036 /**
2037   * exchange_account_folder_size_remove :
2038   * @account : #ExchangeAccount
2039   * @folder_name : 
2040   *
2041   * Removes the entry for @folder_name in #ExchangeFolderSize object
2042   *
2043   * Return value : void
2044   **/
2045 void
2046 exchange_account_folder_size_remove (ExchangeAccount *account,
2047                                         const char *folder_name)
2048 {
2049         g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
2050
2051         exchange_folder_size_remove (account->priv->fsize, folder_name);
2052 }
2053
2054 /**
2055   * exchange_account_folder_size_rename :
2056   * @account : #ExchangeAccount
2057   * @old_name : Old name of the folder
2058   * @new_name : New name of the folder
2059   *
2060   * Removes the entry for @old_name in #ExchangeFolderSize object and adds
2061   * a new entry for @new_name with the same folder size
2062   *
2063   * Return value : void
2064   **/
2065 void 
2066 exchange_account_folder_size_rename (ExchangeAccount *account,
2067                                         const char *old_name,
2068                                         const char *new_name)
2069 {
2070         gdouble cached_size;
2071
2072         g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
2073
2074         cached_size = exchange_folder_size_get (account->priv->fsize,
2075                                         old_name);
2076         if (cached_size >= 0) {
2077                 exchange_folder_size_remove (account->priv->fsize, old_name);
2078                 exchange_folder_size_update (account->priv->fsize,
2079                                                 new_name, cached_size);         
2080         }
2081
2082 }
2083
2084 /**
2085   * exchange_account_folder_size_get_model :
2086   * @account : #ExchangeAccount
2087   *
2088   * Returns the model store of #ExchangeFolderSize object
2089   *
2090   * Return value : The model store. A GtkListStore
2091   **/
2092 GtkListStore *
2093 exchange_account_folder_size_get_model (ExchangeAccount *account)
2094 {
2095         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
2096
2097         return exchange_folder_size_get_model (account->priv->fsize);
2098 }
2099
2100 char *
2101 exchange_account_get_authtype (ExchangeAccount *account)
2102 {
2103         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
2104
2105         if (account->priv->auth_pref == E2K_AUTOCONFIG_USE_BASIC)
2106                 return g_strdup ("Basic");
2107         else if (account->priv->auth_pref == E2K_AUTOCONFIG_USE_NTLM)
2108                 return g_strdup ("NTLM");
2109
2110         return NULL;
2111 }
2112
2113
2114 /**
2115  * exchange_account_new:
2116  * @adata: an #EAccount
2117  *
2118  * An #ExchangeAccount is essentially an #E2kContext with
2119  * associated configuration.
2120  *
2121  * Return value: a new #ExchangeAccount corresponding to @adata
2122  **/
2123 ExchangeAccount *
2124 exchange_account_new (EAccountList *account_list, EAccount *adata)
2125 {
2126         ExchangeAccount *account;
2127         char *enc_user, *mailbox;
2128         const char *param, *proto="http", *owa_path, *pf_server, *owa_url; 
2129         const char *passwd_exp_warn_period, *offline_sync;
2130         E2kUri *uri;
2131
2132         uri = e2k_uri_new (adata->source->url);
2133         if (!uri) {
2134                 g_warning ("Could not parse exchange uri '%s'",
2135                            adata->source->url);
2136                 return NULL;
2137         }
2138
2139         account = g_object_new (EXCHANGE_TYPE_ACCOUNT, NULL);
2140         if (!account)
2141                 return NULL;
2142         account->priv->account_list = account_list;
2143         g_object_ref (account_list);
2144         account->priv->account = adata;
2145         g_object_ref (adata);
2146
2147         account->account_name = g_strdup (adata->name);
2148
2149         account->storage_dir = g_strdup_printf ("%s/.evolution/exchange/%s@%s",
2150                                                 g_get_home_dir (),
2151                                                 uri->user, uri->host);
2152         /*account->account_filename = strrchr (account->storage_dir, '/') + 1;
2153         e_filename_make_safe (account->account_filename); */
2154
2155         /* Identity info */
2156         account->priv->identity_name = g_strdup (adata->id->name);
2157         account->priv->identity_email = g_strdup (adata->id->address);
2158
2159         /* URI, etc, info */
2160         enc_user = e2k_uri_encode (uri->user, FALSE, "@/;:");
2161
2162         if (uri->authmech)
2163                 account->priv->uri_authority = g_strdup_printf ("%s;auth=%s@%s", enc_user,
2164                                                                 uri->authmech, uri->host);
2165         else
2166                 account->priv->uri_authority = g_strdup_printf ("%s@%s", enc_user,
2167                                                                 uri->host);
2168         g_free (enc_user);
2169
2170         account->account_filename = account->priv->uri_authority;
2171
2172         account->priv->source_uri = g_strdup_printf ("exchange://%s/", account->priv->uri_authority);
2173
2174         /* Backword compatibility; FIXME, we should just migrate the
2175          * password from this to source_uri.
2176          * old_uri_authority = g_strdup_printf ("%s@%s", enc_user,
2177          *                                      uri->host);
2178          * old_uri_authority needs to be used in the key for migrating 
2179          * passwords remembered.
2180          */
2181         account->priv->password_key = g_strdup_printf ("exchange://%s/", 
2182                                                         account->priv->uri_authority);
2183
2184         account->priv->username = g_strdup (uri->user);
2185         if (uri->domain)
2186                 account->priv->windows_domain = g_strdup (uri->domain);
2187         else
2188                 account->priv->windows_domain = NULL;
2189         account->exchange_server = g_strdup (uri->host);
2190         if (uri->authmech && !strcmp (uri->authmech, "Basic"))
2191                 account->priv->auth_pref = E2K_AUTOCONFIG_USE_BASIC;
2192         else
2193                 account->priv->auth_pref = E2K_AUTOCONFIG_USE_NTLM;
2194         param = e2k_uri_get_param (uri, "ad_server");
2195         if (param && *param) {
2196                 account->priv->ad_server = g_strdup (param);
2197                 param = e2k_uri_get_param (uri, "ad_limit");
2198                 if (param)
2199                         account->priv->ad_limit = atoi (param);
2200         }
2201         
2202         passwd_exp_warn_period = e2k_uri_get_param (uri, "passwd_exp_warn_period");
2203         if (!passwd_exp_warn_period || !*passwd_exp_warn_period)
2204                 account->priv->passwd_exp_warn_period = -1;
2205         else
2206                 account->priv->passwd_exp_warn_period = atoi (passwd_exp_warn_period);
2207
2208         offline_sync = e2k_uri_get_param (uri, "offline_sync");
2209         if (!offline_sync) 
2210                 account->priv->offline_sync = FALSE;
2211         else 
2212                 account->priv->offline_sync = TRUE;
2213
2214         owa_path = e2k_uri_get_param (uri, "owa_path");
2215         if (!owa_path || !*owa_path)
2216                 owa_path = "exchange";
2217         else if (*owa_path == '/')
2218                 owa_path++;
2219
2220         pf_server = e2k_uri_get_param (uri, "pf_server");
2221         if (!pf_server || !*pf_server)
2222                 pf_server = uri->host;
2223
2224         /* We set protocol reading owa_url, instead of having use_ssl parameter 
2225          * because we don't have SSL section anymore in the account creation
2226          * druid and account editor
2227          */
2228         /* proto = e2k_uri_get_param (uri, "use_ssl") ? "https" : "http"; */
2229
2230         owa_url = e2k_uri_get_param (uri, "owa_url");
2231         if (owa_url) {
2232                 account->priv->owa_url = g_strdup (owa_url); 
2233                 if (!strncmp (owa_url, "https:", 6))
2234                         proto = "https";
2235         }
2236
2237         if (uri->port != 0) {
2238                 account->priv->http_uri_schema =
2239                         g_strdup_printf ("%s://%%s:%d/%s/%%s/",
2240                                          proto, uri->port, owa_path);
2241                 account->public_uri =
2242                         g_strdup_printf ("%s://%s:%d/public",
2243                                          proto, pf_server, uri->port);
2244         } else {
2245                 account->priv->http_uri_schema =
2246                         g_strdup_printf ("%s://%%s/%s/%%s/", proto, owa_path);
2247                 account->public_uri =
2248                         g_strdup_printf ("%s://%s/public", proto, pf_server);
2249         }
2250
2251         param = e2k_uri_get_param (uri, "mailbox");
2252         if (!param || !*param)
2253                 param = uri->user;
2254         else if (!g_ascii_strncasecmp (param, account->priv->identity_email, strlen (param)))
2255                 account->priv->uris_use_email = TRUE;
2256         mailbox = e2k_uri_encode (param, TRUE, "/");
2257         account->home_uri = g_strdup_printf (account->priv->http_uri_schema,
2258                                              uri->host, mailbox);
2259         g_free (mailbox);
2260
2261         param = e2k_uri_get_param (uri, "filter");
2262         if (param)
2263                 account->filter_inbox = TRUE;
2264         param = e2k_uri_get_param (uri, "filter_junk");
2265         if (param)
2266                 account->filter_junk = TRUE;
2267         param = e2k_uri_get_param (uri, "filter_junk_inbox");
2268         if (param)
2269                 account->filter_junk_inbox_only = TRUE;
2270
2271         e2k_uri_free (uri);
2272
2273         return account;
2274 }
2275
2276 /**
2277  * exchange_account_get_hierarchy_by_type:
2278  * @account: an #ExchangeAccount
2279  * @type: Hierarchy type
2280  *
2281  * Returns the non-foreign hierarchy pointer for the requested type 
2282  *
2283  * Return value: Returns the hierarchy pointer for the requested type
2284  **/
2285
2286 ExchangeHierarchy*
2287 exchange_account_get_hierarchy_by_type (ExchangeAccount* acct, 
2288                                         ExchangeHierarchyType type)
2289 {
2290         int i;
2291         
2292         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (acct), NULL);
2293         g_return_val_if_fail (type != EXCHANGE_HIERARCHY_FOREIGN, NULL);
2294
2295         for (i = 0; i < acct->priv->hierarchies->len; i++) {
2296                 if (EXCHANGE_HIERARCHY (acct->priv->hierarchies->pdata[i])->type == type)
2297                         return EXCHANGE_HIERARCHY (acct->priv->hierarchies->pdata[i]);
2298         }
2299         return NULL;
2300 }