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