Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / lib / e2k-autoconfig.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2003, 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 /* e2k-autoconfig: Automatic account configuration backend code */
21
22 /* Note on gtk-doc: Several functions in this file have intentionally-
23  * broken gtk-doc comments (that have only a single "*" after the
24  * opening "/") so that they can be overridden by versions in
25  * docs/reference/tmpl/e2k-autoconfig.sgml that use better markup.
26  * If you change the docs here, be sure to change them there as well.
27  */
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32
33 #include <ctype.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <glib.h>
41 #include <glib/gstdio.h>
42
43 #ifndef G_OS_WIN32
44 #include <netinet/in.h>
45 #include <arpa/nameser.h>
46 #include <resolv.h>
47 #else
48 #include <winsock2.h>
49 #include <ws2tcpip.h>
50 #include <windns.h>
51 #ifndef DNS_TYPE_SRV
52 #define DNS_TYPE_SRV 33
53 #endif
54 #endif
55
56 #include "e2k-autoconfig.h"
57 #include "e2k-encoding-utils.h"
58 #include "e2k-context.h"
59 #include "e2k-global-catalog.h"
60 #include "e2k-propnames.h"
61 #include "e2k-uri.h"
62 #include "e2k-utils.h"
63 #include "e2k-xml-utils.h"
64 #include "xntlm.h"
65
66 #include <libedataserver/e-data-server-util.h>
67 #include <libedataserver/e-url.h>
68 #include <libedataserverui/e-passwords.h>
69 #include <gconf/gconf-client.h>
70 #include <libxml/tree.h>
71 #include <libxml/HTMLparser.h>
72
73 #include <gtk/gtk.h>
74
75 #ifdef G_OS_WIN32
76 #undef CONNECTOR_PREFIX
77 #define CONNECTOR_PREFIX e_util_get_prefix ()
78 #endif
79
80 static char *find_olson_timezone (const char *windows_timezone);
81 static void set_account_uri_string (E2kAutoconfig *ac);
82
83 /**
84  * e2k_autoconfig_new:
85  * @owa_uri: the OWA URI, or %NULL to (try to) use a default
86  * @username: the username (or DOMAIN\username), or %NULL to use a default
87  * @password: the password, or %NULL if not yet known
88  * @auth_pref: information about what auth type to use
89  *
90  * Creates an autoconfig context, based on information stored in the
91  * config file or provided as arguments.
92  *
93  * Return value: an autoconfig context
94  **/
95 E2kAutoconfig *
96 e2k_autoconfig_new (const char *owa_uri, const char *username,
97                     const char *password, E2kAutoconfigAuthPref auth_pref)
98 {
99         E2kAutoconfig *ac;
100
101         ac = g_new0 (E2kAutoconfig, 1);
102
103         if (e2k_autoconfig_lookup_option ("Disable-Plaintext")) {
104                 ac->auth_pref = E2K_AUTOCONFIG_USE_NTLM;
105                 ac->require_ntlm = TRUE;
106         } else
107                 ac->auth_pref = auth_pref;
108
109         e2k_autoconfig_set_owa_uri (ac, owa_uri);
110         e2k_autoconfig_set_gc_server (ac, NULL, -1);
111         e2k_autoconfig_set_username (ac, username);
112         e2k_autoconfig_set_password (ac, password);
113
114         return ac;
115 }
116
117 /**
118  * e2k_autoconfig_free:
119  * @ac: an autoconfig context
120  *
121  * Frees @ac.
122  **/
123 void
124 e2k_autoconfig_free (E2kAutoconfig *ac)
125 {
126         g_free (ac->owa_uri);
127         g_free (ac->gc_server);
128         g_free (ac->username);
129         g_free (ac->password);
130         g_free (ac->display_name);
131         g_free (ac->email);
132         g_free (ac->account_uri);
133         g_free (ac->exchange_server);
134         g_free (ac->timezone);
135         g_free (ac->nt_domain);
136         g_free (ac->w2k_domain);
137         g_free (ac->home_uri);
138         g_free (ac->exchange_dn);
139         g_free (ac->pf_server);
140
141         g_free (ac);
142 }
143
144 static void
145 reset_gc_derived (E2kAutoconfig *ac)
146 {
147         if (ac->display_name) {
148                 g_free (ac->display_name);
149                 ac->display_name = NULL;
150         }
151         if (ac->email) {
152                 g_free (ac->email);
153                 ac->email = NULL;
154         }
155         if (ac->account_uri) {
156                 g_free (ac->account_uri);
157                 ac->account_uri = NULL;
158         }
159 }
160
161 static void
162 reset_owa_derived (E2kAutoconfig *ac)
163 {
164         /* Clear the information we explicitly get from OWA */
165         if (ac->timezone) {
166                 g_free (ac->timezone);
167                 ac->timezone = NULL;
168         }
169         if (ac->exchange_dn) {
170                 g_free (ac->exchange_dn);
171                 ac->exchange_dn = NULL;
172         }
173         if (ac->pf_server) {
174                 g_free (ac->pf_server);
175                 ac->pf_server = NULL;
176         }
177         if (ac->home_uri) {
178                 g_free (ac->home_uri);
179                 ac->home_uri = NULL;
180         }
181
182         /* Reset domain info we may have implicitly got */
183         ac->use_ntlm = (ac->auth_pref != E2K_AUTOCONFIG_USE_BASIC);
184         if (ac->nt_domain_defaulted) {
185                 g_free (ac->nt_domain);
186                 ac->nt_domain = g_strdup (e2k_autoconfig_lookup_option ("NT-Domain"));
187                 ac->nt_domain_defaulted = FALSE;
188         }
189         if (ac->w2k_domain)
190                 g_free (ac->w2k_domain);
191         ac->w2k_domain = g_strdup (e2k_autoconfig_lookup_option ("Domain"));
192
193         /* Reset GC-derived information since it depends on the
194          * OWA-derived information too.
195          */
196         reset_gc_derived (ac);
197 }
198
199 /**
200  * e2k_autoconfig_set_owa_uri:
201  * @ac: an autoconfig context
202  * @owa_uri: the new OWA URI, or %NULL
203  *
204  * Sets @ac's #owa_uri field to @owa_uri (or the default if @owa_uri is
205  * %NULL), and resets any fields whose values had been set based on
206  * the old value of #owa_uri.
207  **/
208 void
209 e2k_autoconfig_set_owa_uri (E2kAutoconfig *ac, const char *owa_uri)
210 {
211         reset_owa_derived (ac);
212         if (ac->gc_server_autodetected)
213                 e2k_autoconfig_set_gc_server (ac, NULL, -1);
214         g_free (ac->owa_uri);
215
216         if (owa_uri) {
217                 if (!strncmp (owa_uri, "http", 4))
218                         ac->owa_uri = g_strdup (owa_uri);
219                 else
220                         ac->owa_uri = g_strdup_printf ("http://%s", owa_uri);
221         } else
222                 ac->owa_uri = g_strdup (e2k_autoconfig_lookup_option ("OWA-URL"));
223 }
224
225 /**
226  * e2k_autoconfig_set_gc_server:
227  * @ac: an autoconfig context
228  * @gc_server: the new GC server, or %NULL
229  * @gal_limit: GAL search size limit, or -1 for no limit
230  *
231  * Sets @ac's #gc_server field to @gc_server (or the default if
232  * @gc_server is %NULL) and the #gal_limit field to @gal_limit, and
233  * resets any fields whose values had been set based on the old value
234  * of #gc_server.
235  **/
236 void
237 e2k_autoconfig_set_gc_server (E2kAutoconfig *ac, const char *gc_server,
238                               int gal_limit)
239 {
240         const char *default_gal_limit;
241
242         reset_gc_derived (ac);
243         g_free (ac->gc_server);
244
245         if (gc_server)
246                 ac->gc_server = g_strdup (gc_server);
247         else
248                 ac->gc_server = g_strdup (e2k_autoconfig_lookup_option ("Global-Catalog"));
249         ac->gc_server_autodetected = FALSE;
250
251         if (gal_limit == -1) {
252                 default_gal_limit = e2k_autoconfig_lookup_option ("GAL-Limit");
253                 if (default_gal_limit)
254                         gal_limit = atoi (default_gal_limit);
255         }
256         ac->gal_limit = gal_limit;
257 }
258
259 /**
260  * e2k_autoconfig_set_username:
261  * @ac: an autoconfig context
262  * @username: the new username (or DOMAIN\username), or %NULL
263  *
264  * Sets @ac's #username field to @username (or the default if
265  * @username is %NULL), and resets any fields whose values had been
266  * set based on the old value of #username.
267  **/
268 void
269 e2k_autoconfig_set_username (E2kAutoconfig *ac, const char *username)
270 {
271         int dlen;
272
273         reset_owa_derived (ac);
274         g_free (ac->username);
275
276         if (username) {
277                 /* If the username includes a domain name, split it out */
278                 dlen = strcspn (username, "/\\");
279                 if (username[dlen]) {
280                         g_free (ac->nt_domain);
281                         ac->nt_domain = g_strndup (username, dlen);
282                         ac->username = g_strdup (username + dlen + 1);
283                         ac->nt_domain_defaulted = FALSE;
284                 } else
285                         ac->username = g_strdup (username);
286         } else
287                 ac->username = g_strdup (g_get_user_name ());
288 }
289
290 /**
291  * e2k_autoconfig_set_password:
292  * @ac: an autoconfig context
293  * @password: the new password, or %NULL to clear
294  *
295  * Sets or clears @ac's #password field.
296  **/
297 void
298 e2k_autoconfig_set_password (E2kAutoconfig *ac, const char *password)
299 {
300         g_free (ac->password);
301         ac->password = g_strdup (password);
302 }
303
304 static void
305 get_ctx_auth_handler (SoupMessage *msg, gpointer user_data)
306 {
307         E2kAutoconfig *ac = user_data;
308         const GSList *headers;
309         const char *challenge_hdr;
310         GByteArray *challenge;
311
312         ac->saw_ntlm = ac->saw_basic = FALSE;
313         headers = soup_message_get_header_list (msg->response_headers,
314                                                 "WWW-Authenticate");
315         while (headers) {
316                 challenge_hdr = headers->data;
317
318                 if (!strcmp (challenge_hdr, "NTLM"))
319                         ac->saw_ntlm = TRUE;
320                 else if (!strncmp (challenge_hdr, "Basic ", 6))
321                         ac->saw_basic = TRUE;
322
323                 if (!strncmp (challenge_hdr, "NTLM ", 5) &&
324                     (!ac->w2k_domain || !ac->nt_domain)) {
325                         challenge = e2k_base64_decode (challenge_hdr + 5);
326                         if (!ac->nt_domain)
327                                 ac->nt_domain_defaulted = TRUE;
328                         xntlm_parse_challenge (challenge->data, challenge->len,
329                                                NULL,
330                                                ac->nt_domain ? NULL : &ac->nt_domain,
331                                                ac->w2k_domain ? NULL : &ac->w2k_domain);
332                         g_byte_array_free (challenge, TRUE);
333                         ac->saw_ntlm = TRUE;
334                         return;
335                 }
336
337                 headers = headers->next;
338         }
339 }
340
341 /*
342  * e2k_autoconfig_get_context:
343  * @ac: an autoconfig context
344  * @op: an #E2kOperation, for cancellation
345  * @result: on output, a result code
346  *
347  * Checks if @ac's URI and authentication parameters work, and if so
348  * returns an #E2kContext using them. On return, *@result (which
349  * may not be %NULL) will contain a result code as follows:
350  *
351  *   %E2K_AUTOCONFIG_OK: success
352  *   %E2K_AUTOCONFIG_REDIRECT: The server issued a valid-looking
353  *     redirect. @ac->owa_uri has been updated and the caller
354  *     should try again.
355  *   %E2K_AUTOCONFIG_TRY_SSL: The server requires SSL.
356  *     @ac->owa_uri has been updated and the caller should try
357  *     again.
358  *   %E2K_AUTOCONFIG_AUTH_ERROR: Generic authentication failure.
359  *     Probably password incorrect
360  *   %E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN: Authentication failed.
361  *     Including an NT domain with the username (or using NTLM)
362  *     may fix the problem.
363  *   %E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC: Caller requested NTLM
364  *     auth, but only Basic was available.
365  *   %E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM: Caller requested Basic
366  *     auth, but only NTLM was available.
367  *   %E2K_AUTOCONFIG_EXCHANGE_5_5: Server appears to be Exchange 5.5.
368  *   %E2K_AUTOCONFIG_NOT_EXCHANGE: Server does not appear to be
369  *     any version of Exchange
370  *   %E2K_AUTOCONFIG_NO_OWA: Server may be Exchange 2000, but OWA
371  *     is not present at the given URL.
372  *   %E2K_AUTOCONFIG_NO_MAILBOX: OWA claims the user has no mailbox.
373  *   %E2K_AUTOCONFIG_CANT_RESOLVE: Could not resolve hostname.
374  *   %E2K_AUTOCONFIG_CANT_CONNECT: Could not connect to server.
375  *   %E2K_AUTOCONFIG_CANCELLED: User cancelled
376  *   %E2K_AUTOCONFIG_FAILED: Other error.
377  *
378  * Return value: the new context, or %NULL
379  *
380  * (If you change this comment, see the note at the top of this file.)
381  **/
382 E2kContext *
383 e2k_autoconfig_get_context (E2kAutoconfig *ac, E2kOperation *op,
384                             E2kAutoconfigResult *result)
385 {
386         E2kContext *ctx;
387         SoupMessage *msg;
388         E2kHTTPStatus status;
389         const char *ms_webstorage;
390         xmlDoc *doc;
391         xmlNode *node;
392         xmlChar *equiv, *content, *href;
393
394         ctx = e2k_context_new (ac->owa_uri);
395         if (!ctx) {
396                 *result = E2K_AUTOCONFIG_FAILED;
397                 return NULL;
398         }
399         e2k_context_set_auth (ctx, ac->username, ac->nt_domain,
400                               ac->use_ntlm ? "NTLM" : "Basic", ac->password);
401
402         msg = e2k_soup_message_new (ctx, ac->owa_uri, SOUP_METHOD_GET);
403         soup_message_add_header (msg->request_headers, "Accept-Language",
404                                  e2k_http_accept_language ());
405         soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
406
407         soup_message_add_status_code_handler (msg, E2K_HTTP_UNAUTHORIZED,
408                                               SOUP_HANDLER_PRE_BODY,
409                                               get_ctx_auth_handler, ac);
410
411  try_again:
412         e2k_context_send_message (ctx, op, msg);
413         status = msg->status_code;
414
415         /* Check for cancellation or other transport error. */
416         if (E2K_HTTP_STATUS_IS_TRANSPORT_ERROR (status)) {
417                 if (status == E2K_HTTP_CANCELLED)
418                         *result = E2K_AUTOCONFIG_CANCELLED;
419                 else if (status == E2K_HTTP_CANT_RESOLVE)
420                         *result = E2K_AUTOCONFIG_CANT_RESOLVE;
421                 else
422                         *result = E2K_AUTOCONFIG_CANT_CONNECT;
423                 goto done;
424         }
425
426         /* Check for an authentication failure. This could be because
427          * the password is incorrect, or because we used Basic auth
428          * without specifying a domain and the server doesn't have a
429          * default domain, or because we tried to use an auth type the
430          * server doesn't allow.
431          */
432         if (status == E2K_HTTP_UNAUTHORIZED) {
433                 if (!ac->use_ntlm && !ac->nt_domain)
434                         *result = E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN;
435                 else if (ac->use_ntlm && !ac->saw_ntlm)
436                         *result = E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC;
437                 else if (!ac->use_ntlm && !ac->saw_basic)
438                         *result = E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM;
439                 else
440                         *result = E2K_AUTOCONFIG_AUTH_ERROR;
441                 goto done;
442         }
443
444         /* A redirection to "logon.asp" means this is Exchange 5.5
445          * OWA. A redirection to "owalogon.asp" means this is Exchange
446          * 2003 forms-based authentication. A redirection to
447          * "CookieAuth.dll" means that it's an Exchange 2003 server
448          * behind an ISA Server 2004 proxy. Other redirections most
449          * likely indicate that the user's mailbox has been moved to a
450          * new server.
451          */
452         if (E2K_HTTP_STATUS_IS_REDIRECTION (status)) {
453                 const char *location;
454                 char *new_uri;
455
456                 location = soup_message_get_header (msg->response_headers,
457                                                    "Location");
458                 if (!location) {
459                         *result = E2K_AUTOCONFIG_FAILED;
460                         goto done;
461                 }
462
463                 if (strstr (location, "/logon.asp")) {
464                         *result = E2K_AUTOCONFIG_EXCHANGE_5_5;
465                         goto done;
466                 } else if (strstr (location, "/owalogon.asp") ||
467                            strstr (location, "/CookieAuth.dll")) {
468                         if (e2k_context_fba (ctx, msg))
469                                 goto try_again;
470                         *result = E2K_AUTOCONFIG_AUTH_ERROR;
471                         goto done;
472                 }
473
474                 new_uri = e2k_strdup_with_trailing_slash (location);
475                 e2k_autoconfig_set_owa_uri (ac, new_uri);
476                 g_free (new_uri);
477                 *result = E2K_AUTOCONFIG_REDIRECT;
478                 goto done;
479         }
480
481         /* If the server requires SSL, it will send back 403 Forbidden
482          * with a body explaining that.
483          */
484         if (status == E2K_HTTP_FORBIDDEN &&
485             !strncmp (ac->owa_uri, "http:", 5) &&
486             msg->response.length > 0) {
487                 msg->response.body[msg->response.length - 1] = '\0';
488                 if (strstr (msg->response.body, "SSL")) {
489                         char *new_uri =
490                                 g_strconcat ("https:", ac->owa_uri + 5, NULL);
491                         e2k_autoconfig_set_owa_uri (ac, new_uri);
492                         g_free (new_uri);
493                         *result = E2K_AUTOCONFIG_TRY_SSL;
494                         goto done;
495                 }
496         }
497
498         /* Figure out some stuff about the server */
499         ms_webstorage = soup_message_get_header (msg->response_headers,
500                                                  "MS-WebStorage");
501         if (ms_webstorage) {
502                 if (!strncmp (ms_webstorage, "6.0.", 4))
503                         ac->version = E2K_EXCHANGE_2000;
504                 else if (!strncmp (ms_webstorage, "6.5.", 4))
505                         ac->version = E2K_EXCHANGE_2003;
506                 else
507                         ac->version = E2K_EXCHANGE_FUTURE;
508         } else {
509                 const char *server = soup_message_get_header (msg->response_headers, "Server");
510
511                 /* If the server explicitly claims to be something
512                  * other than IIS, then return the "not windows"
513                  * error.
514                  */
515                 if (server && !strstr (server, "IIS")) {
516                         *result = E2K_AUTOCONFIG_NOT_EXCHANGE;
517                         goto done;
518                 }
519
520                 /* It's probably Exchange 2000... older versions
521                  * didn't include the MS-WebStorage header here. But
522                  * we don't know for sure.
523                  */
524                 ac->version = E2K_EXCHANGE_UNKNOWN;
525         }
526
527         /* If we're talking to OWA, then 404 Not Found means you don't
528          * have a mailbox. Otherwise, it means you're not talking to
529          * Exchange (even 5.5).
530          */
531         if (status == E2K_HTTP_NOT_FOUND) {
532                 if (ms_webstorage)
533                         *result = E2K_AUTOCONFIG_NO_MAILBOX;
534                 else
535                         *result = E2K_AUTOCONFIG_NOT_EXCHANGE;
536                 goto done;
537         }
538
539         /* Any other error else gets generic failure */
540         if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
541                 *result = E2K_AUTOCONFIG_FAILED;
542                 goto done;
543         }
544
545         /* Parse the returned HTML. */
546         doc = e2k_parse_html (msg->response.body, msg->response.length);
547         if (!doc) {
548                 /* Not HTML? */
549                 *result = ac->version == E2K_EXCHANGE_UNKNOWN ?
550                         E2K_AUTOCONFIG_NO_OWA :
551                         E2K_AUTOCONFIG_FAILED;
552                 goto done;
553         }
554
555         /* Make sure it's not Exchange 5.5 */
556         if (ac->version == E2K_EXCHANGE_UNKNOWN &&
557             strstr (ac->owa_uri, "/logon.asp")) {
558                 *result = E2K_AUTOCONFIG_EXCHANGE_5_5;
559                 goto done;
560         }
561
562         /* Make sure it's not trying to redirect us to Exchange 5.5 */
563         for (node = doc->children; node; node = e2k_xml_find (node, "meta")) {
564                 gboolean ex55 = FALSE;
565
566                 equiv = xmlGetProp (node, "http-equiv");
567                 content = xmlGetProp (node, "content");
568                 if (equiv && content &&
569                     !g_ascii_strcasecmp (equiv, "REFRESH") &&
570                     strstr (content, "/logon.asp"))
571                         ex55 = TRUE;
572                 if (equiv)
573                         xmlFree (equiv);
574                 if (content)
575                         xmlFree (content);
576
577                 if (ex55) {
578                         *result = E2K_AUTOCONFIG_EXCHANGE_5_5;
579                         goto done;
580                 }
581         }
582
583         /* Try to find the base URI */
584         node = e2k_xml_find (doc->children, "base");
585         if (node) {
586                 /* We won */
587                 *result = E2K_AUTOCONFIG_OK;
588                 href = xmlGetProp (node, "href");
589                 g_free (ac->home_uri);
590                 ac->home_uri = g_strdup (href);
591                 xmlFree (href);
592         } else
593                 *result = E2K_AUTOCONFIG_FAILED;
594         xmlFreeDoc (doc);
595
596  done:
597         g_object_unref (msg);
598
599         if (*result != E2K_AUTOCONFIG_OK) {
600                 g_object_unref (ctx);
601                 ctx = NULL;
602         }
603         return ctx;
604 }
605
606 static const char *home_properties[] = {
607         PR_STORE_ENTRYID,
608         E2K_PR_EXCHANGE_TIMEZONE
609 };
610 static const int n_home_properties = sizeof (home_properties) / sizeof (home_properties[0]);
611
612 /*
613  * e2k_autoconfig_check_exchange:
614  * @ac: an autoconfiguration context
615  * @op: an #E2kOperation, for cancellation
616  *
617  * Tries to connect to the the Exchange server using the OWA URL,
618  * username, and password in @ac. Attempts to determine the domain
619  * name and home_uri, and then given the home_uri, looks up the
620  * user's mailbox entryid (used to find his Exchange 5.5 DN) and
621  * default timezone.
622  *
623  * The returned codes are the same as for e2k_autoconfig_get_context()
624  * with the following changes/additions/removals:
625  *
626  *   %E2K_AUTOCONFIG_REDIRECT: URL returned in first redirect returned
627  *     another redirect, which was not followed.
628  *   %E2K_AUTOCONFIG_CANT_BPROPFIND: The server does not allow
629  *     BPROPFIND due to IIS Lockdown configuration
630  *   %E2K_AUTOCONFIG_TRY_SSL: Not used; always handled internally by
631  *     e2k_autoconfig_check_exchange()
632  *
633  * Return value: an #E2kAutoconfigResult
634  *
635  * (If you change this comment, see the note at the top of this file.)
636  **/
637 E2kAutoconfigResult
638 e2k_autoconfig_check_exchange (E2kAutoconfig *ac, E2kOperation *op)
639 {
640         xmlDoc *doc;
641         xmlNode *node;
642         E2kHTTPStatus status;
643         E2kAutoconfigResult result;
644         char *new_uri, *pf_uri;
645         E2kContext *ctx;
646         gboolean redirected = FALSE;
647         E2kResultIter *iter;
648         E2kResult *results;
649         GByteArray *entryid;
650         const char *exchange_dn, *timezone, *hrefs[] = { "" };
651         xmlChar *prop;
652         char *body;
653         int len;
654         E2kUri *euri;
655
656         g_return_val_if_fail (ac->owa_uri != NULL, E2K_AUTOCONFIG_FAILED);
657         g_return_val_if_fail (ac->username != NULL, E2K_AUTOCONFIG_FAILED);
658         g_return_val_if_fail (ac->password != NULL, E2K_AUTOCONFIG_FAILED);
659
660  try_again:
661         ctx = e2k_autoconfig_get_context (ac, op, &result);
662
663         switch (result) {
664         case E2K_AUTOCONFIG_OK:
665                 break;
666
667         case E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC:
668                 if (ac->use_ntlm && !ac->require_ntlm) {
669                         ac->use_ntlm = FALSE;
670                         goto try_again;
671                 } else
672                         return E2K_AUTOCONFIG_AUTH_ERROR;
673
674         case E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM:
675                 return E2K_AUTOCONFIG_AUTH_ERROR;
676
677         case E2K_AUTOCONFIG_REDIRECT:
678                 if (!redirected) {
679                         redirected = TRUE;
680                         goto try_again;
681                 } else
682                         return result;
683
684         case E2K_AUTOCONFIG_TRY_SSL:
685                 goto try_again;
686
687         case E2K_AUTOCONFIG_NO_OWA:
688         default:
689                 /* If the provided OWA URI had no path, try appending
690                  * /exchange.
691                  */
692                 euri = e2k_uri_new (ac->owa_uri);
693                 g_return_val_if_fail (euri != NULL, result);
694                 if (!euri->path || !strcmp (euri->path, "/")) {
695                         e2k_uri_free (euri);
696                         new_uri = e2k_uri_concat (ac->owa_uri, "exchange/");
697                         e2k_autoconfig_set_owa_uri (ac, new_uri);
698                         g_free (new_uri);
699                         goto try_again;
700                 }
701                 e2k_uri_free (euri);
702                 return result;
703         }
704
705         /* Find the link to the public folders */
706         if (ac->version < E2K_EXCHANGE_2003)
707                 pf_uri = g_strdup_printf ("%s/?Cmd=contents", ac->owa_uri);
708         else
709                 pf_uri = g_strdup_printf ("%s/?Cmd=navbar", ac->owa_uri);
710
711         status = e2k_context_get_owa (ctx, NULL, pf_uri, FALSE, &body, &len);
712         g_free (pf_uri);
713         if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
714                 doc = e2k_parse_html (body, len);
715                 g_free (body);
716         } else
717                 doc = NULL;
718
719         if (doc) {
720                 for (node = e2k_xml_find (doc->children, "img"); node; node = e2k_xml_find (node, "img")) {
721                         prop = xmlGetProp (node, "src");
722                         if (prop && strstr (prop, "public") && node->parent) {
723                                 node = node->parent;
724                                 xmlFree (prop);
725                                 prop = xmlGetProp (node, "href");
726                                 if (prop) {
727                                         euri = e2k_uri_new (prop);
728                                         ac->pf_server = g_strdup (euri->host);
729                                         e2k_uri_free (euri);
730                                         xmlFree (prop);
731                                 }
732                                 break;
733                         }
734                 }
735                 xmlFreeDoc (doc);
736         } else
737                 g_warning ("Could not parse pf page");
738
739         /* Now find the store entryid and default timezone. We
740          * gratuitously use BPROPFIND in order to test if they
741          * have the IIS Lockdown problem.
742          */
743         iter = e2k_context_bpropfind_start (ctx, op,
744                                             ac->home_uri, hrefs, 1,
745                                             home_properties,
746                                             n_home_properties);
747         results = e2k_result_iter_next (iter);
748         if (results) {
749                 timezone = e2k_properties_get_prop (results->props,
750                                                     E2K_PR_EXCHANGE_TIMEZONE);
751                 if (timezone)
752                         ac->timezone = find_olson_timezone (timezone);
753
754                 entryid = e2k_properties_get_prop (results->props,
755                                                    PR_STORE_ENTRYID);
756                 if (entryid) {
757                         exchange_dn = e2k_entryid_to_dn (entryid);
758                         if (exchange_dn)
759                                 ac->exchange_dn = g_strdup (exchange_dn);
760                 }
761         }
762         status = e2k_result_iter_free (iter);
763         g_object_unref (ctx);
764
765         if (status == E2K_HTTP_UNAUTHORIZED) {
766                 if (ac->use_ntlm && !ac->require_ntlm) {
767                         ac->use_ntlm = FALSE;
768                         goto try_again;
769                 } else
770                         return E2K_AUTOCONFIG_AUTH_ERROR;
771         } else if (status == E2K_HTTP_NOT_FOUND)
772                 return E2K_AUTOCONFIG_CANT_BPROPFIND;
773         else if (status == E2K_HTTP_CANCELLED)
774                 return E2K_AUTOCONFIG_CANCELLED;
775         else if (status == E2K_HTTP_CANT_RESOLVE)
776                 return E2K_AUTOCONFIG_CANT_RESOLVE;
777         else if (E2K_HTTP_STATUS_IS_TRANSPORT_ERROR (status))
778                 return E2K_AUTOCONFIG_CANT_CONNECT;
779         else if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status))
780                 return E2K_AUTOCONFIG_FAILED;
781
782         return ac->exchange_dn ? E2K_AUTOCONFIG_OK : E2K_AUTOCONFIG_FAILED;
783 }
784
785
786 /* FIXME: make this cancellable */
787 static void
788 find_global_catalog (E2kAutoconfig *ac)
789 {
790 #ifndef G_OS_WIN32
791         int count, len;
792         unsigned char answer[1024], namebuf[1024], *end, *p;
793         guint16 type, qclass, rdlength, priority, weight, port;
794         guint32 ttl;
795         HEADER *header;
796
797         if (!ac->w2k_domain)
798                 return;
799
800         len = res_querydomain ("_gc._tcp", ac->w2k_domain, C_IN, T_SRV,
801                                answer, sizeof (answer));
802         if (len == -1)
803                 return;
804
805         header = (HEADER *)answer;
806         p = answer + sizeof (HEADER);
807         end = answer + len;
808
809         /* See RFCs 1035 and 2782 for details of the parsing */
810
811         /* Skip query */
812         count = ntohs (header->qdcount);
813         while (count-- && p < end) {
814                 p += dn_expand (answer, end, p, namebuf, sizeof (namebuf));
815                 p += 4;
816         }
817
818         /* Read answers */
819         while (count-- && p < end) {
820                 p += dn_expand (answer, end, p, namebuf, sizeof (namebuf));
821                 GETSHORT (type, p);
822                 GETSHORT (qclass, p);
823                 GETLONG (ttl, p);
824                 GETSHORT (rdlength, p);
825
826                 if (type != T_SRV || qclass != C_IN) {
827                         p += rdlength;
828                         continue;
829                 }
830
831                 GETSHORT (priority, p);
832                 GETSHORT (weight, p);
833                 GETSHORT (port, p);
834                 p += dn_expand (answer, end, p, namebuf, sizeof (namebuf));
835
836                 /* FIXME: obey priority and weight */
837                 ac->gc_server = g_strdup (namebuf);
838                 ac->gc_server_autodetected = TRUE;
839                 return;
840         }
841
842         return;
843 #else
844         gchar *name, *casefolded_name;
845         PDNS_RECORD dnsrecp, rover;
846
847         name = g_strconcat ("_gc._tcp.", ac->w2k_domain, NULL);
848         casefolded_name = g_utf8_strdown (name, -1);
849         g_free (name);
850
851         if (DnsQuery_UTF8 (casefolded_name, DNS_TYPE_SRV, DNS_QUERY_STANDARD,
852                            NULL, &dnsrecp, NULL) != ERROR_SUCCESS) {
853                 g_free (casefolded_name);
854                 return;
855         }
856
857         for (rover = dnsrecp; rover != NULL; rover = rover->pNext) {
858                 if (rover->wType != DNS_TYPE_SRV ||
859                     strcmp (rover->pName, casefolded_name) != 0)
860                         continue;
861                 ac->gc_server = g_strdup (rover->Data.SRV.pNameTarget);
862                 ac->gc_server_autodetected = TRUE;
863                 g_free (casefolded_name);
864                 DnsRecordListFree (dnsrecp, DnsFreeRecordList);
865                 return;
866         }
867
868         g_free (casefolded_name);
869         DnsRecordListFree (dnsrecp, DnsFreeRecordList);
870         return;
871 #endif
872 }
873
874 /**
875  * e2k_autoconfig_get_global_catalog
876  * @ac: an autoconfig context
877  * @op: an #E2kOperation, for cancellation
878  *
879  * Tries to connect to the global catalog associated with @ac
880  * (trying to figure it out from the domain name if the server
881  * name is not yet known).
882  *
883  * Return value: the global catalog, or %NULL if the GC server name
884  * wasn't provided and couldn't be autodetected.
885  */
886 E2kGlobalCatalog *
887 e2k_autoconfig_get_global_catalog (E2kAutoconfig *ac, E2kOperation *op)
888 {
889         if (!ac->gc_server) {
890                 find_global_catalog (ac);
891                 if (!ac->gc_server)
892                         return NULL;
893         }
894
895         return e2k_global_catalog_new (ac->gc_server, ac->gal_limit,
896                                        ac->username, ac->nt_domain,
897                                        ac->password);
898 }
899
900 /*
901  * e2k_autoconfig_check_global_catalog
902  * @ac: an autoconfig context
903  * @op: an #E2kOperation, for cancellation
904  *
905  * Tries to connect to the global catalog associated with @ac
906  * (trying to figure it out from the domain name if the server
907  * name is not yet known). On success it will look up the user's
908  * full name and email address (based on his Exchange DN).
909  *
910  * Possible return values are:
911  *
912  *   %E2K_AUTOCONFIG_OK: Success
913  *   %E2K_AUTOCONFIG_CANT_RESOLVE: Could not determine GC server
914  *   %E2K_AUTOCONFIG_NO_MAILBOX: Could not find information for
915  *     the user
916  *   %E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN: Plaintext password auth
917  *     failed: need to specify NT domain
918  *   %E2K_AUTOCONFIG_CANCELLED: Operation was cancelled
919  *   %E2K_AUTOCONFIG_FAILED: Other error.
920  *
921  * Return value: an #E2kAutoconfigResult.
922  *
923  * (If you change this comment, see the note at the top of this file.)
924  */
925 E2kAutoconfigResult
926 e2k_autoconfig_check_global_catalog (E2kAutoconfig *ac, E2kOperation *op)
927 {
928         E2kGlobalCatalog *gc;
929         E2kGlobalCatalogEntry *entry;
930         E2kGlobalCatalogStatus status;
931         E2kAutoconfigResult result;
932
933         g_return_val_if_fail (ac->exchange_dn != NULL, E2K_AUTOCONFIG_FAILED);
934
935         gc = e2k_autoconfig_get_global_catalog (ac, op);
936         if (!gc)
937                 return E2K_AUTOCONFIG_CANT_RESOLVE;
938
939         set_account_uri_string (ac);
940
941         status = e2k_global_catalog_lookup (
942                 gc, op, E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN,
943                 ac->exchange_dn, E2K_GLOBAL_CATALOG_LOOKUP_EMAIL |
944                 E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX, &entry);
945
946         if (status == E2K_GLOBAL_CATALOG_OK) {
947                 ac->display_name = g_strdup (entry->display_name);
948                 ac->email = g_strdup (entry->email);
949                 result = E2K_AUTOCONFIG_OK;
950         } else if (status == E2K_GLOBAL_CATALOG_CANCELLED)
951                 result = E2K_AUTOCONFIG_CANCELLED;
952 #ifndef HAVE_LDAP_NTLM_BIND
953         else if (status == E2K_GLOBAL_CATALOG_AUTH_FAILED &&
954                  !ac->nt_domain)
955                 result = E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN;
956 #endif
957         else if (status == E2K_GLOBAL_CATALOG_ERROR)
958                 result = E2K_AUTOCONFIG_FAILED;
959         else
960                 result = E2K_AUTOCONFIG_NO_MAILBOX;
961
962         g_object_unref (gc);
963         return result;
964 }
965
966 static void
967 set_account_uri_string (E2kAutoconfig *ac)
968 {
969         E2kUri *owa_uri, *home_uri;
970         char *path, *mailbox;
971         GString *uri;
972
973         owa_uri = e2k_uri_new (ac->owa_uri);
974         home_uri = e2k_uri_new (ac->home_uri);
975
976         uri = g_string_new ("exchange://");
977         if (ac->nt_domain && (!ac->use_ntlm || !ac->nt_domain_defaulted)) {
978                 e2k_uri_append_encoded (uri, ac->nt_domain, FALSE, "\\;:@/");
979                 g_string_append_c (uri, '\\');
980         }
981         e2k_uri_append_encoded (uri, ac->username, FALSE, ";:@/");
982
983         if (!ac->use_ntlm)
984                 g_string_append (uri, ";auth=Basic");
985
986         g_string_append_c (uri, '@');
987         e2k_uri_append_encoded (uri, owa_uri->host, FALSE, ":/");
988         if (owa_uri->port)
989                 g_string_append_printf (uri, ":%d", owa_uri->port);
990         g_string_append_c (uri, '/');
991
992         if (!strcmp (owa_uri->protocol, "https"))
993                 g_string_append (uri, ";use_ssl=always");
994         g_string_append (uri, ";ad_server=");
995         e2k_uri_append_encoded (uri, ac->gc_server, FALSE, ";?");
996         if (ac->gal_limit != -1)
997                 g_string_append_printf (uri, ";ad_limit=%d", ac->gal_limit);
998
999         path = g_strdup (home_uri->path + 1);
1000         mailbox = strrchr (path, '/');
1001         if (mailbox && !mailbox[1]) {
1002                 *mailbox = '\0';
1003                 mailbox = strrchr (path, '/');
1004         }
1005         if (mailbox) {
1006                 *mailbox++ = '\0';
1007                 g_string_append (uri, ";mailbox=");
1008                 e2k_uri_append_encoded (uri, mailbox, FALSE, ";?");
1009         }
1010         g_string_append (uri, ";owa_path=/");
1011         e2k_uri_append_encoded (uri, path, FALSE, ";?");
1012         g_free (path);
1013
1014         g_string_append (uri, ";pf_server=");
1015         e2k_uri_append_encoded (uri, ac->pf_server ? ac->pf_server : home_uri->host, FALSE, ";?");
1016
1017         ac->account_uri = uri->str;
1018         ac->exchange_server = g_strdup (home_uri->host);
1019         g_string_free (uri, FALSE);
1020         e2k_uri_free (home_uri);
1021         e2k_uri_free (owa_uri);
1022 }
1023
1024
1025 /* Approximate mapping from Exchange timezones to Olson ones. Exchange
1026  * is less specific, so we factor in the language/country info from
1027  * the locale in our guess.
1028  *
1029  * We strip " Standard Time" / " Daylight Time" from the Windows
1030  * timezone names. (Actually, we just strip the last two words.)
1031  */
1032 static struct {
1033         const char *windows_name, *lang, *country, *olson_name;
1034 } zonemap[] = {
1035         /* (GMT-12:00) Eniwetok, Kwajalein */
1036         { "Dateline", NULL, NULL, "Pacific/Kwajalein" },
1037
1038         /* (GMT-11:00) Midway Island, Samoa */
1039         { "Samoa", NULL, NULL, "Pacific/Midway" },
1040
1041         /* (GMT-10:00) Hawaii */
1042         { "Hawaiian", NULL, NULL, "Pacific/Honolulu" },
1043
1044         /* (GMT-09:00) Alaska */
1045         { "Alaskan", NULL, NULL, "America/Juneau" },
1046
1047         /* (GMT-08:00) Pacific Time (US & Canada); Tijuana */
1048         { "Pacific", NULL, "CA", "America/Vancouver" },
1049         { "Pacific", "es", "MX", "America/Tijuana" },
1050         { "Pacific", NULL, NULL, "America/Los_Angeles" },
1051
1052         /* (GMT-07:00) Arizona */
1053         { "US Mountain", NULL, NULL, "America/Phoenix" },
1054
1055         /* (GMT-07:00) Mountain Time (US & Canada) */
1056         { "Mountain", NULL, "CA", "America/Edmonton" },
1057         { "Mountain", NULL, NULL, "America/Denver" },
1058
1059         /* (GMT-06:00) Central America */
1060         { "Central America", NULL, "BZ", "America/Belize" },
1061         { "Central America", NULL, "CR", "America/Costa_Rica" },
1062         { "Central America", NULL, "GT", "America/Guatemala" },
1063         { "Central America", NULL, "HN", "America/Tegucigalpa" },
1064         { "Central America", NULL, "NI", "America/Managua" },
1065         { "Central America", NULL, "SV", "America/El_Salvador" },
1066
1067         /* (GMT-06:00) Central Time (US & Canada) */
1068         { "Central", NULL, NULL, "America/Chicago" },
1069
1070         /* (GMT-06:00) Mexico City */
1071         { "Mexico", NULL, NULL, "America/Mexico_City" },
1072
1073         /* (GMT-06:00) Saskatchewan */
1074         { "Canada Central", NULL, NULL, "America/Regina" },
1075
1076         /* (GMT-05:00) Bogota, Lima, Quito */
1077         { "SA Pacific", NULL, "BO", "America/Bogota" },
1078         { "SA Pacific", NULL, "EC", "America/Guayaquil" },
1079         { "SA Pacific", NULL, "PA", "America/Panama" },
1080         { "SA Pacific", NULL, "PE", "America/Lima" },
1081
1082         /* (GMT-05:00) Eastern Time (US & Canada) */
1083         { "Eastern", "fr", "CA", "America/Montreal" },
1084         { "Eastern", NULL, NULL, "America/New_York" },
1085
1086         /* (GMT-05:00) Indiana (East) */
1087         { "US Eastern", NULL, NULL, "America/Indiana/Indianapolis" },
1088
1089         /* (GMT-04:00) Atlantic Time (Canada) */
1090         { "Atlantic", "es", "US", "America/Puerto_Rico" },
1091         { "Atlantic", NULL, "VI", "America/St_Thomas" },
1092         { "Atlantic", NULL, "CA", "America/Halifax" },
1093
1094         /* (GMT-04:00) Caracas, La Paz */
1095         { "SA Western", NULL, "BO", "America/La_Paz" },
1096         { "SA Western", NULL, "VE", "America/Caracas" },
1097
1098         /* (GMT-04:00) Santiago */
1099         { "Pacific SA", NULL, NULL, "America/Santiago" },
1100
1101         /* (GMT-03:30) Newfoundland */
1102         { "Newfoundland", NULL, NULL, "America/St_Johns" },
1103
1104         /* (GMT-03:00) Brasilia */
1105         { "E. South America", NULL, NULL, "America/Sao_Paulo" },
1106
1107         /* (GMT-03:00) Greenland */
1108         { "Greenland", NULL, NULL, "America/Godthab" },
1109
1110         /* (GMT-03:00) Buenos Aires, Georgetown */
1111         { "SA Eastern", NULL, NULL, "America/Buenos_Aires" },
1112
1113         /* (GMT-02:00) Mid-Atlantic */
1114         { "Mid-Atlantic", NULL, NULL, "America/Noronha" },
1115
1116         /* (GMT-01:00) Azores */
1117         { "Azores", NULL, NULL, "Atlantic/Azores" },
1118
1119         /* (GMT-01:00) Cape Verde Is. */
1120         { "Cape Verde", NULL, NULL, "Atlantic/Cape_Verde" },
1121
1122         /* (GMT) Casablanca, Monrovia */
1123         { "Greenwich", NULL, "LR", "Africa/Monrovia" },
1124         { "Greenwich", NULL, "MA", "Africa/Casablanca" },
1125
1126         /* (GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London */
1127         { "GMT", "ga", "IE", "Europe/Dublin" },
1128         { "GMT", "pt", "PT", "Europe/Lisbon" },
1129         { "GMT", NULL, NULL, "Europe/London" },
1130
1131         /* (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */
1132         { "W. Europe", "nl", "NL", "Europe/Amsterdam" },
1133         { "W. Europe", "it", "IT", "Europe/Rome" },
1134         { "W. Europe", "sv", "SE", "Europe/Stockholm" },
1135         { "W. Europe", NULL, "CH", "Europe/Zurich" },
1136         { "W. Europe", NULL, "AT", "Europe/Vienna" },
1137         { "W. Europe", "de", "DE", "Europe/Berlin" },
1138
1139         /* (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */
1140         { "Central Europe", "sr", "YU", "Europe/Belgrade" },
1141         { "Central Europe", "sk", "SK", "Europe/Bratislava" },
1142         { "Central Europe", "hu", "HU", "Europe/Budapest" },
1143         { "Central Europe", "sl", "SI", "Europe/Ljubljana" },
1144         { "Central Europe", "cz", "CZ", "Europe/Prague" },
1145
1146         /* (GMT+01:00) Brussels, Copenhagen, Madrid, Paris */
1147         { "Romance", NULL, "BE", "Europe/Brussels" },
1148         { "Romance", "da", "DK", "Europe/Copenhagen" },
1149         { "Romance", "es", "ES", "Europe/Madrid" },
1150         { "Romance", "fr", "FR", "Europe/Paris" },
1151
1152         /* (GMT+01:00) Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb */
1153         { "Central European", "bs", "BA", "Europe/Sarajevo" },
1154         { "Central European", "mk", "MK", "Europe/Skopje" },
1155         { "Central European", "bg", "BG", "Europe/Sofia" },
1156         { "Central European", "lt", "LT", "Europe/Vilnius" },
1157         { "Central European", "pl", "PL", "Europe/Warsaw" },
1158         { "Central European", "hr", "HR", "Europe/Zagreb" },
1159
1160         /* (GMT+01:00) West Central Africa */
1161         { "W. Central Africa", NULL, NULL, "Africa/Kinshasa" },
1162
1163         /* (GMT+02:00) Athens, Istanbul, Minsk */
1164         { "GTB", "el", "GR", "Europe/Athens" },
1165         { "GTB", "tr", "TR", "Europe/Istanbul" },
1166         { "GTB", "be", "BY", "Europe/Minsk" },
1167
1168         /* (GMT+02:00) Bucharest */
1169         { "E. Europe", NULL, NULL, "Europe/Bucharest" },
1170
1171         /* (GMT+02:00) Cairo */
1172         { "Egypt", NULL, NULL, "Africa/Cairo" },
1173
1174         /* (GMT+02:00) Harare, Pretoria */
1175         { "South Africa", NULL, NULL, "Africa/Johannesburg" },
1176
1177         /* (GMT+02:00) Helsinki, Riga, Tallinn */
1178         { "FLE", "lv", "LV", "Europe/Riga" },
1179         { "FLE", "et", "EE", "Europe/Tallinn" },
1180         { "FLE", "fi", "FI", "Europe/Helsinki" },
1181
1182         /* (GMT+02:00) Jerusalem */
1183         { "Israel", NULL, NULL, "Asia/Jerusalem" },
1184
1185         /* (GMT+03:00) Baghdad */
1186         { "Arabic", NULL, NULL, "Asia/Baghdad" },
1187
1188         /* (GMT+03:00) Kuwait, Riyadh */
1189         { "Arab", NULL, "KW", "Asia/Kuwait" },
1190         { "Arab", NULL, "SA", "Asia/Riyadh" },
1191
1192         /* (GMT+03:00) Moscow, St. Petersburg, Volgograd */
1193         { "Russian", NULL, NULL, "Europe/Moscow" },
1194
1195         /* (GMT+03:00) Nairobi */
1196         { "E. Africa", NULL, NULL, "Africa/Nairobi" },
1197
1198         /* (GMT+03:30) Tehran */
1199         { "Iran", NULL, NULL, "Asia/Tehran" },
1200
1201         /* (GMT+04:00) Abu Dhabi, Muscat */
1202         { "Arabian", NULL, NULL, "Asia/Muscat" },
1203
1204         /* (GMT+04:00) Baku, Tbilisi, Yerevan */
1205         { "Caucasus", NULL, NULL, "Asia/Baku" },
1206
1207         /* (GMT+04:30) Kabul */
1208         { "Afghanistan", NULL, NULL, "Asia/Kabul" },
1209
1210         /* (GMT+05:00) Ekaterinburg */
1211         { "Ekaterinburg", NULL, NULL, "Asia/Yekaterinburg" },
1212
1213         /* (GMT+05:00) Islamabad, Karachi, Tashkent */
1214         { "West Asia", NULL, NULL, "Asia/Karachi" },
1215
1216         /* (GMT+05:30) Kolkata, Chennai, Mumbai, New Delhi */
1217         { "India", NULL, NULL, "Asia/Calcutta" },
1218
1219         /* (GMT+05:45) Kathmandu */
1220         { "Nepal", NULL, NULL, "Asia/Katmandu" },
1221
1222         /* (GMT+06:00) Almaty, Novosibirsk */
1223         { "N. Central Asia", NULL, NULL, "Asia/Almaty" },
1224
1225         /* (GMT+06:00) Astana, Dhaka */
1226         { "Central Asia", NULL, NULL, "Asia/Dhaka" },
1227
1228         /* (GMT+06:00) Sri Jayawardenepura */
1229         { "Sri Lanka", NULL, NULL, "Asia/Colombo" },
1230
1231         /* (GMT+06:30) Rangoon */
1232         { "Myanmar", NULL, NULL, "Asia/Rangoon" },
1233
1234         /* (GMT+07:00) Bangkok, Hanoi, Jakarta */
1235         { "SE Asia", "th", "TH", "Asia/Bangkok" },
1236         { "SE Asia", "vi", "VN", "Asia/Saigon" },
1237         { "SE Asia", "id", "ID", "Asia/Jakarta" },
1238
1239         /* (GMT+07:00) Krasnoyarsk */
1240         { "North Asia", NULL, NULL, "Asia/Krasnoyarsk" },
1241
1242         /* (GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi */
1243         { "China", NULL, "HK", "Asia/Hong_Kong" },
1244         { "China", NULL, NULL, "Asia/Shanghai" },
1245
1246         /* (GMT+08:00) Irkutsk, Ulaan Bataar */
1247         { "North Asia East", NULL, NULL, "Asia/Irkutsk" },
1248
1249         /* (GMT+08:00) Perth */
1250         { "W. Australia", NULL, NULL, "Australia/Perth" },
1251
1252         /* (GMT+08:00) Kuala Lumpur, Singapore */
1253         { "Singapore", NULL, NULL, "Asia/Kuala_Lumpur" },
1254
1255         /* (GMT+08:00) Taipei */
1256         { "Taipei", NULL, NULL, "Asia/Taipei" },
1257
1258         /* (GMT+09:00) Osaka, Sapporo, Tokyo */
1259         { "Tokyo", NULL, NULL, "Asia/Tokyo" },
1260
1261         /* (GMT+09:00) Seoul */
1262         { "Korea", NULL, "KP", "Asia/Pyongyang" },
1263         { "Korea", NULL, "KR", "Asia/Seoul" },
1264
1265         /* (GMT+09:00) Yakutsk */
1266         { "Yakutsk", NULL, NULL, "Asia/Yakutsk" },
1267
1268         /* (GMT+09:30) Adelaide */
1269         { "Cen. Australia", NULL, NULL, "Australia/Adelaide" },
1270
1271         /* (GMT+09:30) Darwin */
1272         { "AUS Central", NULL, NULL, "Australia/Darwin" },
1273
1274         /* (GMT+10:00) Brisbane */
1275         { "E. Australia", NULL, NULL, "Australia/Brisbane" },
1276
1277         /* (GMT+10:00) Canberra, Melbourne, Sydney */
1278         { "AUS Eastern", NULL, NULL, "Australia/Sydney" },
1279
1280         /* (GMT+10:00) Guam, Port Moresby */
1281         { "West Pacific", NULL, NULL, "Pacific/Guam" },
1282
1283         /* (GMT+10:00) Hobart */
1284         { "Tasmania", NULL, NULL, "Australia/Hobart" },
1285
1286         /* (GMT+10:00) Vladivostok */
1287         { "Vladivostok", NULL, NULL, "Asia/Vladivostok" },
1288
1289         /* (GMT+11:00) Magadan, Solomon Is., New Caledonia */
1290         { "Central Pacific", NULL, NULL, "Pacific/Midway" },
1291
1292         /* (GMT+12:00) Auckland, Wellington */
1293         { "New Zealand", NULL, NULL, "Pacific/Auckland" },
1294
1295         /* (GMT+12:00) Fiji, Kamchatka, Marshall Is. */
1296         { "Fiji", "ru", "RU", "Asia/Kamchatka" },
1297         { "Fiji", NULL, NULL, "Pacific/Fiji" },
1298
1299         /* (GMT+13:00) Nuku'alofa */
1300         { "Tonga", NULL, NULL, "Pacific/Tongatapu" }
1301 };
1302 static const int n_zone_mappings = sizeof (zonemap) / sizeof (zonemap[0]);
1303
1304 static char *
1305 find_olson_timezone (const char *windows_timezone)
1306 {
1307         int i, tzlen;
1308         const char *locale, *p;
1309         char lang[3] = { 0 }, country[3] = { 0 };
1310
1311         /* Strip " Standard Time" / " Daylight Time" from name */
1312         p = windows_timezone + strlen (windows_timezone) - 1;
1313         while (p > windows_timezone && *p-- != ' ')
1314                 ;
1315         while (p > windows_timezone && *p-- != ' ')
1316                 ;
1317         tzlen = p - windows_timezone + 1;
1318
1319         /* Find the first entry in zonemap with a matching name */
1320         for (i = 0; i < n_zone_mappings; i++) {
1321                 if (!g_ascii_strncasecmp (windows_timezone,
1322                                           zonemap[i].windows_name,
1323                                           tzlen))
1324                         break;
1325         }
1326         if (i == n_zone_mappings)
1327                 return NULL; /* Shouldn't happen... */
1328
1329         /* If there's only one choice, go with it */
1330         if (!zonemap[i].lang && !zonemap[i].country)
1331                 return g_strdup (zonemap[i].olson_name);
1332
1333         /* Find our language/country (hopefully). */
1334 #ifndef G_OS_WIN32
1335         locale = getenv ("LANG");
1336 #else
1337         locale = g_win32_getlocale ();
1338 #endif
1339         if (locale) {
1340                 strncpy (lang, locale, 2);
1341                 locale = strchr (locale, '_');
1342                 if (locale++)
1343                         strncpy (country, locale, 2);
1344         }
1345 #ifdef G_OS_WIN32
1346         g_free ((char *) locale);
1347 #endif
1348
1349         /* Look for an entry where either the country or the
1350          * language matches.
1351          */
1352         do {
1353                 if ((zonemap[i].lang && !strcmp (zonemap[i].lang, lang)) ||
1354                     (zonemap[i].country && !strcmp (zonemap[i].country, country)))
1355                         return g_strdup (zonemap[i].olson_name);
1356         } while (++i < n_zone_mappings &&
1357                  !g_ascii_strncasecmp (windows_timezone,
1358                                        zonemap[i].windows_name,
1359                                        tzlen));
1360
1361         /* None of the hints matched, so (semi-arbitrarily) return the
1362          * last of the entries with the right Windows timezone name.
1363          */
1364         return g_strdup (zonemap[i - 1].olson_name);
1365 }
1366
1367
1368 /* Config file handling */
1369
1370 static GHashTable *config_options;
1371
1372 static void
1373 read_config (void)
1374 {
1375         struct stat st;
1376         char *p, *name, *value;
1377         char *config_data;
1378         int fd = -1;
1379
1380         config_options = g_hash_table_new (e2k_ascii_strcase_hash,
1381                                             e2k_ascii_strcase_equal);
1382
1383 #ifndef G_OS_WIN32
1384         fd = g_open ("/etc/ximian/connector.conf", O_RDONLY, 0);
1385 #endif
1386         if (fd == -1) {
1387                 gchar *filename = g_build_filename (CONNECTOR_PREFIX,
1388                                                     "etc/connector.conf",
1389                                                     NULL);
1390                 
1391                 fd = g_open (filename, O_RDONLY, 0);
1392                 g_free (filename);
1393         }
1394         if (fd == -1)
1395                 return;
1396         if (fstat (fd, &st) == -1) {
1397                 g_warning ("Could not stat connector.conf: %s",
1398                            g_strerror (errno));
1399                 close (fd);
1400                 return;
1401         }
1402
1403         config_data = g_malloc (st.st_size + 1);
1404         if (read (fd, config_data, st.st_size) != st.st_size) {
1405                 g_warning ("Could not read connector.conf: %s",
1406                            g_strerror (errno));
1407                 close (fd);
1408                 g_free (config_data);
1409                 return;
1410         }
1411         close (fd);
1412         config_data[st.st_size] = '\0';
1413
1414         /* Read config data */
1415         p = config_data;
1416
1417         while (1) {
1418                 for (name = p; isspace ((unsigned char)*name); name++)
1419                         ;
1420
1421                 p = strchr (name, ':');
1422                 if (!p || !p[1])
1423                         break;
1424                 *p = '\0';
1425                 value = p + 2;
1426                 p = strchr (value, '\n');
1427                 if (!p)
1428                         break;
1429                 if (*(p - 1) == '\r')
1430                         *(p - 1) = '\0';
1431                 *p = '\0';
1432                 p++;
1433
1434                 if (g_ascii_strcasecmp (value, "false") &&
1435                     g_ascii_strcasecmp (value, "no"))
1436                         g_hash_table_insert (config_options, name, value);
1437         };
1438
1439         g_free (config_data);
1440 }
1441
1442 /**
1443  * e2k_autoconfig_lookup_option:
1444  * @option: option name to look up
1445  *
1446  * Looks up an autoconfiguration hint in the config file (if present)
1447  *
1448  * Return value: the string value of the option, or %NULL if it is unset.
1449  **/
1450 const char *
1451 e2k_autoconfig_lookup_option (const char *option)
1452 {
1453         if (!config_options)
1454                 read_config ();
1455         return g_hash_table_lookup (config_options, option);
1456 }
1457
1458 static gboolean 
1459 validate (const char *owa_url, char *user, char *password, ExchangeParams *exchange_params, E2kAutoconfigResult *result)
1460 {
1461         E2kAutoconfig *ac;
1462         E2kOperation op;        /* FIXME */
1463         E2kUri *euri;
1464         gboolean valid = FALSE;
1465         const char *old, *new;
1466         char *path, *mailbox;
1467
1468         ac = e2k_autoconfig_new (owa_url, user, password, 
1469                                  E2K_AUTOCONFIG_USE_EITHER);
1470
1471         e2k_operation_init (&op);
1472         // e2k_autoconfig_set_gc_server (ac, ad_server, gal_limit) FIXME
1473         // e2k_autoconfig_set_gc_server (ac, NULL, -1);
1474         *result = e2k_autoconfig_check_exchange (ac, &op);
1475
1476         if (*result == E2K_AUTOCONFIG_OK) {
1477                 /*
1478                  * On error code 403 and SSL seen in server response 
1479                  * e2k_autoconfig_get_context() tries to
1480                  * connect using https if owa url has http and vice versa.
1481                  * And also re-sets the owa_uri in E2kAutoconfig.
1482                  * So even if the uri is incorrect, 
1483                  * e2k_autoconfig_check_exchange() will return success.
1484                  * In this case of account set up, owa_url paramter will still
1485                  * have wrong url entered, and we throw the error, instead of
1486                  * going ahead with account setup and failing later. 
1487                  */
1488                 if (g_str_has_prefix (ac->owa_uri, "http:")) {
1489                     if (!g_str_has_prefix (owa_url, "http:"))
1490                         *result = E2K_AUTOCONFIG_CANT_CONNECT;
1491                 }
1492                 else if (!g_str_has_prefix (owa_url, "https:"))
1493                         *result = E2K_AUTOCONFIG_CANT_CONNECT;
1494         }
1495
1496         if (*result == E2K_AUTOCONFIG_OK) {
1497                 *result = e2k_autoconfig_check_global_catalog (ac, &op);
1498                 e2k_operation_free (&op);
1499                 
1500                 /* find mailbox and owa_path values */  
1501                 euri = e2k_uri_new (ac->home_uri);
1502                 path = g_strdup (euri->path + 1);
1503                 e2k_uri_free (euri);
1504                 mailbox = strrchr (path, '/');
1505                 if (mailbox && !mailbox[1]) {
1506                         *mailbox = '\0';
1507                         mailbox = strrchr (path, '/');
1508                 }
1509                 if (mailbox)
1510                         *mailbox++ = '\0';
1511
1512                 exchange_params->mailbox  = g_strdup (mailbox);
1513                 exchange_params->owa_path = g_strdup_printf ("%s%s", "/", path);
1514                 g_free (path);
1515                 exchange_params->host = g_strdup (ac->pf_server);
1516                 if (ac->gc_server) 
1517                         exchange_params->ad_server = g_strdup (ac->gc_server);
1518                 exchange_params->is_ntlm = ac->saw_ntlm;
1519
1520                 valid = TRUE;
1521         }
1522         else {
1523                 switch (*result) {
1524
1525                 case E2K_AUTOCONFIG_CANT_CONNECT:
1526                         if (!strncmp (ac->owa_uri, "http:", 5)) {
1527                                 old = "http";
1528                                 new = "https";
1529                         } else {
1530                                 old = "https";
1531                                 new = "http";
1532                         }
1533
1534                         /* SURF : e_notice (NULL, GTK_MESSAGE_ERROR,
1535                                   _("Could not connect to the Exchange "
1536                                     "server.\nMake sure the URL is correct "
1537                                     "(try \"%s\" instead of \"%s\"?) "
1538                                     "and try again."), new, old);
1539                         */
1540                         valid = FALSE;
1541                         break;
1542
1543                 case E2K_AUTOCONFIG_CANT_RESOLVE:
1544                 /* SURF :       e_notice (NULL, GTK_MESSAGE_ERROR,
1545                                 _("Could not locate Exchange server.\n"
1546                                   "Make sure the server name is spelled correctly "
1547                                   "and try again."));
1548                 */
1549                         valid = FALSE;
1550                         break;
1551
1552                 case E2K_AUTOCONFIG_AUTH_ERROR:
1553                 case E2K_AUTOCONFIG_AUTH_ERROR_TRY_NTLM:
1554                 case E2K_AUTOCONFIG_AUTH_ERROR_TRY_BASIC:
1555                 /* SURF :       e_notice (NULL, GTK_MESSAGE_ERROR,
1556                                 _("Could not authenticate to the Exchange "
1557                                   "server.\nMake sure the username and "
1558                                   "password are correct and try again."));
1559                 */
1560                         valid = FALSE;
1561                         break;
1562
1563                 case E2K_AUTOCONFIG_AUTH_ERROR_TRY_DOMAIN:
1564                 /* SURF :               e_notice (NULL, GTK_MESSAGE_ERROR,
1565                                 _("Could not authenticate to the Exchange "
1566                                   "server.\nMake sure the username and "
1567                                   "password are correct and try again.\n\n"
1568                                   "You may need to specify the Windows "
1569                                   "domain name as part of your username "
1570                                   "(eg, \"MY-DOMAIN\\%s\")."),
1571                                   ac->username);
1572                 */
1573                         valid = FALSE;
1574                         break;
1575
1576                 case E2K_AUTOCONFIG_NO_OWA:
1577                 case E2K_AUTOCONFIG_NOT_EXCHANGE:
1578                 /* SURF :       e_notice (NULL, GTK_MESSAGE_ERROR,
1579                                 _("Could not find OWA data at the indicated URL.\n"
1580                                   "Make sure the URL is correct and try again."));
1581                 */
1582                         valid = FALSE;
1583                         break;
1584
1585                 case E2K_AUTOCONFIG_CANT_BPROPFIND:
1586                 /* SURF :       e_notice (
1587                                 NULL, GTK_MESSAGE_ERROR,
1588                                 _("Ximian Connector requires access to certain "
1589                                 "functionality on the Exchange Server that appears "
1590                                 "to be disabled or blocked.  (This is usually "
1591                                 "unintentional.)  Your Exchange Administrator will "
1592                                 "need to enable this functionality in order for "
1593                                 "you to be able to use Ximian Connector.\n\n"
1594                                 "For information to provide to your Exchange "
1595                                 "administrator, please follow the link below:\n"
1596                                 "http://support.novell.com/cgi-bin/search/searchtid.cgi?/ximian/ximian328.html "));
1597                 */
1598                         valid = FALSE;
1599                         break;
1600
1601                 case E2K_AUTOCONFIG_EXCHANGE_5_5:
1602                 /* SURF :       e_notice (
1603                                 NULL, GTK_MESSAGE_ERROR,
1604                                 _("The Exchange server URL you provided is for an "
1605                                 "Exchange 5.5 Server. Ximian Connector supports "
1606                                 "Microsoft Exchange 2000 and 2003 only."));
1607                 */
1608                         valid = FALSE;
1609                         break;
1610
1611                 default:
1612                 /* SURF :       e_notice (NULL, GTK_MESSAGE_ERROR,
1613                                 _("Could not configure Exchange account because "
1614                                   "an unknown error occurred. Check the URL, "
1615                                   "username, and password, and try again."));
1616                 */
1617                         valid = FALSE; /* FIXME return valid */
1618                         break;
1619                 }
1620         }
1621
1622         e2k_autoconfig_free (ac);
1623         return valid;
1624 }
1625
1626 gboolean
1627 e2k_validate_user (const char *owa_url, char *pkey, char **user,
1628                    ExchangeParams *exchange_params, gboolean *remember_password,
1629                    E2kAutoconfigResult *result, GtkWindow *parent)
1630 {
1631         gboolean valid = FALSE, remember=FALSE;
1632         char *key, *password, *prompt;
1633         char *username;
1634         gchar **usernames;
1635         int try = 0;
1636         EUri *uri;
1637
1638         uri = e_uri_new (owa_url);
1639         key = g_strdup_printf ("%s%s/", pkey, uri->host); /* FIXME */
1640         e_uri_free (uri);
1641         
1642 try_auth_again:
1643         username = g_strdup (*user);
1644
1645
1646         password = e_passwords_get_password ("Exchange", key);
1647         if (password) {
1648                 /* This can be the case, where user presses authenticate button and
1649                  * later cancels the account setup or removal of account fails for
1650                  * some reason. We need to prompt for the password always when 
1651                  * authenticate button is pressed */
1652                 e_passwords_forget_password ("Exchange", key);
1653         }
1654         
1655         prompt = g_strdup_printf (_("Enter password for %s"), username);
1656         password = e_passwords_ask_password (_("Enter password"),
1657                                 "Exchange", key, prompt,
1658                                 E_PASSWORDS_REMEMBER_FOREVER|E_PASSWORDS_SECRET,
1659                                 &remember, parent);
1660         g_free (prompt);
1661         if (!password) {
1662                 g_free (key);
1663                 g_free (username);
1664                 *result = E2K_AUTOCONFIG_CANCELLED;
1665                 return valid;
1666         }
1667
1668         valid = validate (owa_url, username, password, exchange_params, result);
1669         if (valid) {
1670                 /* generate the proper key once the host name 
1671                  * is read and remember password temporarily, 
1672                  * so that at the end of * account creation, 
1673                  * user will not be prompted, for password will
1674                  * not be asked again. 
1675                  */
1676                 *remember_password = remember;
1677                 g_free (key);
1678                 if (exchange_params->is_ntlm)
1679                         key = g_strdup_printf ("exchange://%s;auth=NTLM@%s/", 
1680                                                        username, exchange_params->host);
1681                 else
1682                         key = g_strdup_printf ("exchange://%s@%s/", username, exchange_params->host);
1683                 e_passwords_add_password (key, password);
1684                 e_passwords_remember_password ("Exchange", key);
1685         }
1686         else {
1687                 if (try == 0) {
1688                         /* Check for name as e-mail id and try once again 
1689                          * extracing username from e-mail id. 
1690                          */
1691                         usernames = g_strsplit (*user, "@", 2);
1692                         if (usernames && usernames[0] && usernames[1]) {
1693                                 username = g_strdup (usernames[0]);
1694                                 g_strfreev (usernames);
1695                                 try ++;
1696                                 memset(*user, 0, strlen(*user));
1697                                 g_free (*user);
1698                                 *user = g_strdup (username);
1699                                 g_free (username);
1700                                 goto try_auth_again;
1701                         }
1702                 }
1703                 /* if validation failed*/
1704                 e_passwords_forget_password ("Exchange", key);
1705         }
1706
1707         g_free (key);
1708         g_free (password);
1709         g_free (username);
1710         return valid;
1711 }