if we got cancelled, the message is floating, so free it. (struct
[platform/upstream/evolution-data-server.git] / camel / camel-service.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-service.c : Abstract class for an email service */
3
4 /*
5  *
6  * Author :
7  *  Bertrand Guiheneuf <bertrand@helixcode.com>
8  *
9  * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com)
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of version 2 of the GNU General Public
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23  * USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <ctype.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34
35 #ifdef ENABLE_THREADS
36 #include <pthread.h>
37 #include "e-util/e-msgport.h"
38 #endif
39
40 #include "e-util/e-host-utils.h"
41
42 #include "camel-service.h"
43 #include "camel-session.h"
44 #include "camel-exception.h"
45 #include "camel-operation.h"
46 #include "camel-private.h"
47
48 #define d(x)
49 #define w(x)
50
51 static CamelObjectClass *parent_class = NULL;
52
53 /* Returns the class for a CamelService */
54 #define CSERV_CLASS(so) CAMEL_SERVICE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
55
56 static void construct (CamelService *service, CamelSession *session,
57                        CamelProvider *provider, CamelURL *url,
58                        CamelException *ex);
59 static gboolean service_connect(CamelService *service, CamelException *ex);
60 static gboolean service_disconnect(CamelService *service, gboolean clean,
61                                    CamelException *ex);
62 static void cancel_connect (CamelService *service);
63 static GList *query_auth_types (CamelService *service, CamelException *ex);
64 static char *get_name (CamelService *service, gboolean brief);
65 static char *get_path (CamelService *service);
66
67 static int service_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
68 static int service_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
69
70
71 static void
72 camel_service_class_init (CamelServiceClass *camel_service_class)
73 {
74         CamelObjectClass *object_class = CAMEL_OBJECT_CLASS (camel_service_class);
75         
76         parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE);
77         
78         /* virtual method overloading */
79         object_class->setv = service_setv;
80         object_class->getv = service_getv;
81         
82         /* virtual method definition */
83         camel_service_class->construct = construct;
84         camel_service_class->connect = service_connect;
85         camel_service_class->disconnect = service_disconnect;
86         camel_service_class->cancel_connect = cancel_connect;
87         camel_service_class->query_auth_types = query_auth_types;
88         camel_service_class->get_name = get_name;
89         camel_service_class->get_path = get_path;
90 }
91
92 static void
93 camel_service_init (void *o, void *k)
94 {
95         CamelService *service = o;
96
97         service->priv = g_malloc0(sizeof(*service->priv));
98 #ifdef ENABLE_THREADS
99         service->priv->connect_lock = e_mutex_new(E_MUTEX_REC);
100         service->priv->connect_op_lock = e_mutex_new(E_MUTEX_SIMPLE);
101 #endif
102 }
103
104 static void
105 camel_service_finalize (CamelObject *object)
106 {
107         CamelService *service = CAMEL_SERVICE (object);
108         
109         if (service->status == CAMEL_SERVICE_CONNECTED) {
110                 CamelException ex;
111                 
112                 camel_exception_init (&ex);
113                 CSERV_CLASS (service)->disconnect (service, TRUE, &ex);
114                 if (camel_exception_is_set (&ex)) {
115                         w(g_warning ("camel_service_finalize: silent disconnect failure: %s",
116                                      camel_exception_get_description (&ex)));
117                 }
118                 camel_exception_clear (&ex);
119         }
120         
121         if (service->url)
122                 camel_url_free (service->url);
123         if (service->session)
124                 camel_object_unref (CAMEL_OBJECT (service->session));
125         
126 #ifdef ENABLE_THREADS
127         e_mutex_destroy (service->priv->connect_lock);
128         e_mutex_destroy (service->priv->connect_op_lock);
129 #endif
130         g_free (service->priv);
131 }
132
133
134
135 CamelType
136 camel_service_get_type (void)
137 {
138         static CamelType type = CAMEL_INVALID_TYPE;
139         
140         if (type == CAMEL_INVALID_TYPE) {
141                 type =
142                         camel_type_register (CAMEL_OBJECT_TYPE,
143                                              "CamelService",
144                                              sizeof (CamelService),
145                                              sizeof (CamelServiceClass),
146                                              (CamelObjectClassInitFunc) camel_service_class_init,
147                                              NULL,
148                                              (CamelObjectInitFunc) camel_service_init,
149                                              camel_service_finalize );
150         }
151         
152         return type;
153 }
154
155
156 static int
157 service_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
158 {
159         CamelService *service = (CamelService *) object;
160         CamelURL *url = service->url;
161         gboolean reconnect = FALSE;
162         guint32 tag;
163         int i;
164         
165         for (i = 0; i < args->argc; i++) {
166                 tag = args->argv[i].tag;
167                 
168                 /* make sure this arg wasn't already handled */
169                 if (tag & CAMEL_ARG_IGNORE)
170                         continue;
171                 
172                 /* make sure this is an arg we're supposed to handle */
173                 if ((tag & CAMEL_ARG_TAG) <= CAMEL_SERVICE_ARG_FIRST ||
174                     (tag & CAMEL_ARG_TAG) >= CAMEL_SERVICE_ARG_FIRST + 100)
175                         continue;
176                 
177                 if (tag == CAMEL_SERVICE_USERNAME) {
178                         /* set the username */
179                         if (strcmp (url->user, args->argv[i].ca_str) != 0) {
180                                 camel_url_set_user (url, args->argv[i].ca_str);
181                                 reconnect = TRUE;
182                         }
183                 } else if (tag == CAMEL_SERVICE_AUTH) {
184                         /* set the auth mechanism */
185                         if (strcmp (url->authmech, args->argv[i].ca_str) != 0) {
186                                 camel_url_set_authmech (url, args->argv[i].ca_str);
187                                 reconnect = TRUE;
188                         }
189                 } else if (tag == CAMEL_SERVICE_HOSTNAME) {
190                         /* set the hostname */
191                         if (strcmp (url->host, args->argv[i].ca_str) != 0) {
192                                 camel_url_set_host (url, args->argv[i].ca_str);
193                                 reconnect = TRUE;
194                         }
195                 } else if (tag == CAMEL_SERVICE_PORT) {
196                         /* set the port */
197                         if (url->port != args->argv[i].ca_int) {
198                                 camel_url_set_port (url, args->argv[i].ca_int);
199                                 reconnect = TRUE;
200                         }
201                 } else if (tag == CAMEL_SERVICE_PATH) {
202                         /* set the path */
203                         if (strcmp (url->path, args->argv[i].ca_str) != 0) {
204                                 camel_url_set_host (url, args->argv[i].ca_str);
205                                 reconnect = TRUE;
206                         }
207                 } else {
208                         /* error? */
209                         continue;
210                 }
211                 
212                 /* let our parent know that we've handled this arg */
213                 camel_argv_ignore (args, i);
214         }
215         
216         /* FIXME: what if we are in the process of connecting? */
217         if (reconnect && service->status == CAMEL_SERVICE_CONNECTED) {
218                 /* reconnect the service using the new URL */
219                 if (camel_service_disconnect (service, TRUE, ex))
220                         camel_service_connect (service, ex);
221         }
222         
223         return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
224 }
225
226 static int
227 service_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
228 {
229         CamelService *service = (CamelService *) object;
230         CamelURL *url = service->url;
231         guint32 tag;
232         int i;
233         
234         for (i = 0; i < args->argc; i++) {
235                 tag = args->argv[i].tag;
236                 
237                 /* make sure this is an arg we're supposed to handle */
238                 if ((tag & CAMEL_ARG_TAG) <= CAMEL_SERVICE_ARG_FIRST ||
239                     (tag & CAMEL_ARG_TAG) >= CAMEL_SERVICE_ARG_FIRST + 100)
240                         continue;
241                 
242                 switch (tag) {
243                 case CAMEL_SERVICE_USERNAME:
244                         /* get the username */
245                         *args->argv[i].ca_str = url->user;
246                         break;
247                 case CAMEL_SERVICE_AUTH:
248                         /* get the auth mechanism */
249                         *args->argv[i].ca_str = url->authmech;
250                         break;
251                 case CAMEL_SERVICE_HOSTNAME:
252                         /* get the hostname */
253                         *args->argv[i].ca_str = url->host;
254                         break;
255                 case CAMEL_SERVICE_PORT:
256                         /* get the port */
257                         *args->argv[i].ca_int = url->port;
258                         break;
259                 case CAMEL_SERVICE_PATH:
260                         /* get the path */
261                         *args->argv[i].ca_str = url->path;
262                         break;
263                 default:
264                         /* error? */
265                         break;
266                 }
267         }
268         
269         return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
270 }
271
272 static void
273 construct (CamelService *service, CamelSession *session,
274            CamelProvider *provider, CamelURL *url, CamelException *ex)
275 {
276         char *url_string;
277         
278         if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER) &&
279             (url->user == NULL || url->user[0] == '\0')) {
280                 url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
281                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
282                                       _("URL '%s' needs a username component"),
283                                       url_string);
284                 g_free (url_string);
285                 return;
286         } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST) &&
287                    (url->host == NULL || url->host[0] == '\0')) {
288                 url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
289                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
290                                       _("URL '%s' needs a host component"),
291                                       url_string);
292                 g_free (url_string);
293                 return;
294         } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH) &&
295                    (url->path == NULL || url->path[0] == '\0')) {
296                 url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
297                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
298                                       _("URL '%s' needs a path component"),
299                                       url_string);
300                 g_free (url_string);
301                 return;
302         }
303         
304         service->provider = provider;
305         service->url = url;
306         service->session = session;
307         camel_object_ref (CAMEL_OBJECT (session));
308         
309         service->status = CAMEL_SERVICE_DISCONNECTED;
310 }
311
312 /**
313  * camel_service_construct:
314  * @service: the CamelService
315  * @session: the session for the service
316  * @provider: the service's provider
317  * @url: the default URL for the service (may be NULL)
318  * @ex: a CamelException
319  *
320  * Constructs a CamelService initialized with the given parameters.
321  **/
322 void
323 camel_service_construct (CamelService *service, CamelSession *session,
324                          CamelProvider *provider, CamelURL *url,
325                          CamelException *ex)
326 {
327         g_return_if_fail (CAMEL_IS_SERVICE (service));
328         g_return_if_fail (CAMEL_IS_SESSION (session));
329         
330         CSERV_CLASS (service)->construct (service, session, provider, url, ex);
331 }
332
333
334 static gboolean
335 service_connect (CamelService *service, CamelException *ex)
336 {
337         /* Things like the CamelMboxStore can validly
338          * not define a connect function.
339          */
340          return TRUE;
341 }
342
343 /**
344  * camel_service_connect:
345  * @service: CamelService object
346  * @ex: a CamelException
347  *
348  * Connect to the service using the parameters it was initialized
349  * with.
350  *
351  * Return value: whether or not the connection succeeded
352  **/
353
354 gboolean
355 camel_service_connect (CamelService *service, CamelException *ex)
356 {
357         gboolean ret = FALSE;
358         gboolean unreg = FALSE;
359         
360         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
361         g_return_val_if_fail (service->session != NULL, FALSE);
362         g_return_val_if_fail (service->url != NULL, FALSE);
363         
364         CAMEL_SERVICE_LOCK (service, connect_lock);
365         
366         if (service->status == CAMEL_SERVICE_CONNECTED) {
367                 CAMEL_SERVICE_UNLOCK (service, connect_lock);
368                 return TRUE;
369         }
370
371         /* Register a separate operation for connecting, so that
372          * the offline code can cancel it.
373          */
374         CAMEL_SERVICE_LOCK (service, connect_op_lock);
375         service->connect_op = camel_operation_registered ();
376         if (!service->connect_op) {
377                 service->connect_op = camel_operation_new (NULL, NULL);
378                 camel_operation_register (service->connect_op);
379                 unreg = TRUE;
380         }
381         CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
382
383         service->status = CAMEL_SERVICE_CONNECTING;
384         ret = CSERV_CLASS (service)->connect (service, ex);
385         service->status = ret ? CAMEL_SERVICE_CONNECTED : CAMEL_SERVICE_DISCONNECTED;
386
387         CAMEL_SERVICE_LOCK (service, connect_op_lock);
388         if (service->connect_op) {
389                 if (unreg)
390                         camel_operation_unregister (service->connect_op);
391                 
392                 camel_operation_unref (service->connect_op);
393                 service->connect_op = NULL;
394         }
395         CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
396
397         CAMEL_SERVICE_UNLOCK (service, connect_lock);
398         
399         return ret;
400 }
401
402 static gboolean
403 service_disconnect (CamelService *service, gboolean clean, CamelException *ex)
404 {
405         /*service->connect_level--;*/
406
407         /* We let people get away with not having a disconnect
408          * function -- CamelMboxStore, for example. 
409          */
410         
411         return TRUE;
412 }
413
414 /**
415  * camel_service_disconnect:
416  * @service: CamelService object
417  * @clean: whether or not to try to disconnect cleanly.
418  * @ex: a CamelException
419  *
420  * Disconnect from the service. If @clean is %FALSE, it should not
421  * try to do any synchronizing or other cleanup of the connection.
422  *
423  * Return value: whether or not the disconnection succeeded without
424  * errors. (Consult @ex if %FALSE.)
425  **/
426 gboolean
427 camel_service_disconnect (CamelService *service, gboolean clean,
428                           CamelException *ex)
429 {
430         gboolean res = TRUE;
431         int unreg = FALSE;
432
433         CAMEL_SERVICE_LOCK (service, connect_lock);
434         
435         if (service->status != CAMEL_SERVICE_DISCONNECTED
436             && service->status != CAMEL_SERVICE_DISCONNECTING) {
437                 CAMEL_SERVICE_LOCK (service, connect_op_lock);
438                 service->connect_op = camel_operation_registered ();
439                 if (!service->connect_op) {
440                         service->connect_op = camel_operation_new (NULL, NULL);
441                         camel_operation_register (service->connect_op);
442                         unreg = TRUE;
443                 }
444                 CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
445
446                 service->status = CAMEL_SERVICE_DISCONNECTING;
447                 res = CSERV_CLASS (service)->disconnect (service, clean, ex);
448                 service->status = CAMEL_SERVICE_DISCONNECTED;
449
450                 CAMEL_SERVICE_LOCK (service, connect_op_lock);
451                 if (unreg)
452                         camel_operation_unregister (service->connect_op);
453
454                 camel_operation_unref (service->connect_op);
455                 service->connect_op = NULL;
456                 CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
457         }
458         
459         CAMEL_SERVICE_UNLOCK (service, connect_lock);
460         
461         return res;
462 }
463
464 static void
465 cancel_connect (CamelService *service)
466 {
467         camel_operation_cancel (service->connect_op);
468 }
469
470 /**
471  * camel_service_cancel_connect:
472  * @service: a service
473  *
474  * If @service is currently attempting to connect to or disconnect
475  * from a server, this causes it to stop and fail. Otherwise it is a
476  * no-op.
477  **/
478 void
479 camel_service_cancel_connect (CamelService *service)
480 {
481         CAMEL_SERVICE_LOCK (service, connect_op_lock);
482         if (service->connect_op)
483                 CSERV_CLASS (service)->cancel_connect (service);
484         CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
485 }
486
487 /**
488  * camel_service_get_url:
489  * @service: a service
490  *
491  * Returns the URL representing a service. The returned URL must be
492  * freed when it is no longer needed. For security reasons, this
493  * routine does not return the password.
494  *
495  * Return value: the url name
496  **/
497 char *
498 camel_service_get_url (CamelService *service)
499 {
500         return camel_url_to_string (service->url, CAMEL_URL_HIDE_PASSWORD);
501 }
502
503
504 static char *
505 get_name (CamelService *service, gboolean brief)
506 {
507         w(g_warning ("CamelService::get_name not implemented for `%s'",
508                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (service))));
509         return g_strdup ("???");
510 }               
511
512 /**
513  * camel_service_get_name:
514  * @service: the service
515  * @brief: whether or not to use a briefer form
516  *
517  * This gets the name of the service in a "friendly" (suitable for
518  * humans) form. If @brief is %TRUE, this should be a brief description
519  * such as for use in the folder tree. If @brief is %FALSE, it should
520  * be a more complete and mostly unambiguous description.
521  *
522  * Return value: the description, which the caller must free.
523  **/
524 char *
525 camel_service_get_name (CamelService *service, gboolean brief)
526 {
527         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
528         g_return_val_if_fail (service->url, NULL);
529         
530         return CSERV_CLASS (service)->get_name (service, brief);
531 }
532
533
534 static char *
535 get_path (CamelService *service)
536 {
537         CamelProvider *prov = service->provider;
538         CamelURL *url = service->url;
539         GString *gpath;
540         char *path;
541         
542         /* A sort of ad-hoc default implementation that works for our
543          * current set of services.
544          */
545         
546         gpath = g_string_new (service->provider->protocol);
547         if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_USER)) {
548                 if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
549                         g_string_append_printf (gpath, "/%s@%s",
550                                                 url->user ? url->user : "",
551                                                 url->host ? url->host : "");
552                         
553                         if (url->port)
554                                 g_string_append_printf (gpath, ":%d", url->port);
555                 } else {
556                         g_string_append_printf (gpath, "/%s%s", url->user ? url->user : "",
557                                                 CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_USER) ? "" : "@");
558                 }
559         } else if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
560                 g_string_append_printf (gpath, "/%s%s",
561                                         CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_HOST) ? "" : "@",
562                                         url->host ? url->host : "");
563                 
564                 if (url->port)
565                         g_string_append_printf (gpath, ":%d", url->port);
566         }
567         
568         if (CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_PATH))
569                 g_string_append_printf (gpath, "%s%s", *url->path == '/' ? "" : "/", url->path);
570         
571         path = gpath->str;
572         g_string_free (gpath, FALSE);
573         
574         return path;
575 }               
576
577 /**
578  * camel_service_get_path:
579  * @service: the service
580  *
581  * This gets a valid UNIX relative path describing the service, which
582  * is guaranteed to be different from the path returned for any
583  * different service. This path MUST start with the name of the
584  * provider, followed by a "/", but after that, it is up to the
585  * provider.
586  *
587  * Return value: the path, which the caller must free.
588  **/
589 char *
590 camel_service_get_path (CamelService *service)
591 {
592         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
593         g_return_val_if_fail (service->url, NULL);
594         
595         return CSERV_CLASS (service)->get_path (service);
596 }
597
598
599 /**
600  * camel_service_get_session:
601  * @service: a service
602  *
603  * Returns the CamelSession associated with the service.
604  *
605  * Return value: the session
606  **/
607 CamelSession *
608 camel_service_get_session (CamelService *service)
609 {
610         return service->session;
611 }
612
613 /**
614  * camel_service_get_provider:
615  * @service: a service
616  *
617  * Returns the CamelProvider associated with the service.
618  *
619  * Return value: the provider
620  **/
621 CamelProvider *
622 camel_service_get_provider (CamelService *service)
623 {
624         return service->provider;
625 }
626
627 static GList *
628 query_auth_types (CamelService *service, CamelException *ex)
629 {
630         return NULL;
631 }
632
633 /**
634  * camel_service_query_auth_types:
635  * @service: a CamelService
636  * @ex: a CamelException
637  *
638  * This is used by the mail source wizard to get the list of
639  * authentication types supported by the protocol, and information
640  * about them.
641  *
642  * Return value: a list of CamelServiceAuthType records. The caller
643  * must free the list with g_list_free() when it is done with it.
644  **/
645 GList *
646 camel_service_query_auth_types (CamelService *service, CamelException *ex)
647 {
648         GList *ret;
649         
650         /* note that we get the connect lock here, which means the callee
651            must not call the connect functions itself */
652         CAMEL_SERVICE_LOCK (service, connect_lock);
653         ret = CSERV_CLASS (service)->query_auth_types (service, ex);
654         CAMEL_SERVICE_UNLOCK (service, connect_lock);
655         
656         return ret;
657 }
658
659 /* URL utility routines */
660
661 /**
662  * camel_service_gethost:
663  * @service: a CamelService
664  * @ex: a CamelException
665  *
666  * This is a convenience function to do a gethostbyname on the host
667  * for the service's URL.
668  *
669  * Return value: a (statically-allocated) hostent.
670  **/
671 struct hostent *
672 camel_service_gethost (CamelService *service, CamelException *ex)
673 {
674         char *hostname;
675         
676         if (service->url->host)
677                 hostname = service->url->host;
678         else
679                 hostname = "localhost";
680         
681         return camel_gethostbyname (hostname, ex);
682 }
683
684 #ifdef offsetof
685 #define STRUCT_OFFSET(type, field)        ((gint) offsetof (type, field))
686 #else
687 #define STRUCT_OFFSET(type, field)        ((gint) ((gchar*) &((type *) 0)->field))
688 #endif
689
690 struct _lookup_msg {
691 #ifdef ENABLE_THREADS
692         EMsg msg;
693 #endif
694         unsigned int cancelled:1;
695         const char *name;
696         int len;
697         int type;
698         int result;
699         int herr;
700         struct hostent hostbuf;
701         int hostbuflen;
702         char *hostbufmem;
703 };
704
705 static void *
706 get_hostbyname(void *data)
707 {
708         struct _lookup_msg *info = data;
709
710         while ((info->result = e_gethostbyname_r(info->name, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->herr)) == ERANGE) {
711                 d(printf("gethostbyname fialed?\n"));
712 #ifdef ENABLE_THREADS
713                 pthread_testcancel();
714 #endif
715                 info->hostbuflen *= 2;
716                 info->hostbufmem = g_realloc(info->hostbufmem, info->hostbuflen);
717         }
718
719         d(printf("gethostbyname ok?\n"));
720
721 #ifdef ENABLE_THREADS
722         /* If we got cancelled, dont reply, just free it */
723         if (info->cancelled) {
724                 g_free(info->hostbufmem);
725                 g_free(info);
726         } else {
727                 e_msgport_reply((EMsg *)info);
728         }
729 #endif
730         return NULL;
731 }
732
733 struct hostent *
734 camel_gethostbyname (const char *name, CamelException *ex)
735 {
736 #ifdef ENABLE_THREADS
737         int fdmax, status, fd, cancel_fd;
738 #endif
739         struct _lookup_msg *msg;
740
741         g_return_val_if_fail(name != NULL, NULL);
742
743         if (camel_operation_cancel_check(NULL)) {
744                 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
745                 return NULL;
746         }
747
748         camel_operation_start_transient(NULL, _("Resolving: %s"), name);
749
750         msg = g_malloc0(sizeof(*msg));
751         msg->hostbuflen = 1024;
752         msg->hostbufmem = g_malloc(msg->hostbuflen);
753         msg->name = name;
754         msg->result = -1;
755
756 #ifdef ENABLE_THREADS
757         cancel_fd = camel_operation_cancel_fd(NULL);
758         if (cancel_fd == -1) {
759 #endif
760                 get_hostbyname(msg);
761 #ifdef ENABLE_THREADS
762         } else {
763                 EMsgPort *reply_port;
764                 pthread_t id;
765                 fd_set rdset;
766
767                 reply_port = msg->msg.reply_port = e_msgport_new();
768                 fd = e_msgport_fd(msg->msg.reply_port);
769                 if (pthread_create(&id, NULL, get_hostbyname, msg) == 0) {
770                         d(printf("waiting for name return/cancellation in main process\n"));
771                         do {
772                                 FD_ZERO(&rdset);
773                                 FD_SET(cancel_fd, &rdset);
774                                 FD_SET(fd, &rdset);
775                                 fdmax = MAX(fd, cancel_fd) + 1;
776                                 status = select(fdmax, &rdset, NULL, 0, NULL);
777                         } while (status == -1 && errno == EINTR);
778
779                         if (status == -1 || FD_ISSET(cancel_fd, &rdset)) {
780                                 if (status == -1)
781                                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Failure in name lookup: %s"), g_strerror(errno));
782                                 else
783                                         camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
784
785                                 /* We cancel so if the thread impl is decent it causes immediate exit.
786                                    We detach so we dont need to wait for it to exit if it isn't.
787                                    We check the reply port incase we had a reply in the mean time, which we free later */
788                                 d(printf("Cancelling lookup thread and leaving it\n"));
789                                 msg->cancelled = 1;
790                                 pthread_detach(id);
791                                 pthread_cancel(id);
792                                 msg = (struct _lookup_msg *)e_msgport_get(reply_port);
793                         } else {
794                                 struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port);
795
796                                 g_assert(reply == msg);
797                                 d(printf("waiting for child to exit\n"));
798                                 pthread_join(id, NULL);
799                                 d(printf("child done\n"));
800                         }
801                 } else {
802                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: cannot create thread: %s"), g_strerror(errno));
803                 }
804                 e_msgport_destroy(reply_port);
805         }
806 #endif
807
808         camel_operation_end(NULL);
809
810         if (!camel_exception_is_set(ex)) {
811                 if (msg->result == 0)
812                         return &msg->hostbuf;
813
814                 if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA)
815                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
816                                               _("Host lookup failed: %s: host not found"), name);
817                 else
818                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
819                                               _("Host lookup failed: %s: unknown reason"), name);
820         }
821
822         if (msg) {
823                 g_free(msg->hostbufmem);
824                 g_free(msg);
825         }
826
827         return NULL;
828 }
829
830 static void *
831 get_hostbyaddr (void *data)
832 {
833         struct _lookup_msg *info = data;
834
835         while ((info->result = e_gethostbyaddr_r (info->name, info->len, info->type, &info->hostbuf,
836                                                   info->hostbufmem, info->hostbuflen, &info->herr)) == ERANGE) {
837                 d(printf ("gethostbyaddr fialed?\n"));
838 #ifdef ENABLE_THREADS
839                 pthread_testcancel ();
840 #endif
841                 info->hostbuflen *= 2;
842                 info->hostbufmem = g_realloc (info->hostbufmem, info->hostbuflen);
843         }
844         
845         d(printf ("gethostbyaddr ok?\n"));
846         
847 #ifdef ENABLE_THREADS
848         if (info->cancelled) {
849                 g_free(info->hostbufmem);
850                 g_free(info);
851         } else {
852                 e_msgport_reply((EMsg *)info);
853         }
854 #endif
855         return NULL;
856 }
857
858
859 struct hostent *
860 camel_gethostbyaddr (const char *addr, int len, int type, CamelException *ex)
861 {
862 #ifdef ENABLE_THREADS
863         int fdmax, status, fd, cancel_fd;
864 #endif
865         struct _lookup_msg *msg;
866         
867         g_return_val_if_fail (addr != NULL, NULL);
868         
869         if (camel_operation_cancel_check (NULL)) {
870                 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
871                 return NULL;
872         }
873         
874         camel_operation_start_transient (NULL, _("Resolving address"));
875         
876         msg = g_malloc0 (sizeof (struct _lookup_msg));
877         msg->hostbuflen = 1024;
878         msg->hostbufmem = g_malloc (msg->hostbuflen);
879         msg->name = addr;
880         msg->len = len;
881         msg->type = type;
882         msg->result = -1;
883
884 #ifdef ENABLE_THREADS
885         cancel_fd = camel_operation_cancel_fd (NULL);
886         if (cancel_fd == -1) {
887 #endif
888                 get_hostbyaddr (msg);
889 #ifdef ENABLE_THREADS
890         } else {
891                 EMsgPort *reply_port;
892                 pthread_t id;
893                 fd_set rdset;
894                 
895                 reply_port = msg->msg.reply_port = e_msgport_new ();
896                 fd = e_msgport_fd (msg->msg.reply_port);
897                 if (pthread_create (&id, NULL, get_hostbyaddr, msg) == 0) {
898                         d(printf("waiting for name return/cancellation in main process\n"));
899                         do {
900                                 FD_ZERO(&rdset);
901                                 FD_SET(cancel_fd, &rdset);
902                                 FD_SET(fd, &rdset);
903                                 fdmax = MAX(fd, cancel_fd) + 1;
904                                 status = select (fdmax, &rdset, NULL, 0, NULL);
905                         } while (status == -1 && errno == EINTR);
906                         
907                         if (status == -1 || FD_ISSET(cancel_fd, &rdset)) {
908                                 if (status == -1)
909                                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Failure in name lookup: %s"), g_strerror(errno));
910                                 else
911                                         camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
912
913                                 /* We cancel so if the thread impl is decent it causes immediate exit.
914                                    We detach so we dont need to wait for it to exit if it isn't.
915                                    We check the reply port incase we had a reply in the mean time, which we free later */
916                                 d(printf("Cancelling lookup thread and leaving it\n"));
917                                 msg->cancelled = 1;
918                                 pthread_detach(id);
919                                 pthread_cancel(id);
920                                 msg = (struct _lookup_msg *)e_msgport_get(reply_port);
921                         } else {
922                                 struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port);
923
924                                 g_assert(reply == msg);
925                                 d(printf("waiting for child to exit\n"));
926                                 pthread_join(id, NULL);
927                                 d(printf("child done\n"));
928                         }
929                 }
930                 
931                 e_msgport_destroy (reply_port);
932         }
933 #endif
934         
935         camel_operation_end (NULL);
936
937         if (!camel_exception_is_set(ex)) {
938                 if (msg->result == 0)
939                         return &msg->hostbuf;
940
941                 if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA)
942                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
943                                               _("Host lookup failed: host not found"));
944                 else
945                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
946                                               _("Host lookup failed: unknown reason"));
947         }
948
949         if (msg) {
950                 g_free(msg->hostbufmem);
951                 g_free(msg);
952         }
953
954         return NULL;
955 }
956
957 void camel_free_host(struct hostent *h)
958 {
959         struct _lookup_msg *msg;
960
961         g_return_if_fail(h != NULL);
962
963         /* yeah this looks ugly but it is safe.  we passed out a reference to inside our structure, this maps it
964            to the base structure, so we can free everything right without having to keep track of it separately */
965         msg = (struct _lookup_msg *)(((char *)h) - STRUCT_OFFSET(struct _lookup_msg, hostbuf));
966
967         g_free(msg->hostbufmem);
968         g_free(msg);
969 }