Check if gethostbyname_r take five params
[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 Helix Code, Inc. (http://www.helixcode.com)
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License as
13  * published by the Free Software Foundation; either version 2 of the
14  * License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
24  * USA
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31 #include <ctype.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <errno.h>
35
36 #ifdef ENABLE_THREADS
37 #include <pthread.h>
38 #include "e-util/e-msgport.h"
39 #endif
40
41 #include "camel-service.h"
42 #include "camel-session.h"
43 #include "camel-exception.h"
44 #include "camel-operation.h"
45 #include "camel-private.h"
46
47 #define d(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 gboolean is_connected (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
66 static void
67 camel_service_class_init (CamelServiceClass *camel_service_class)
68 {
69         parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE);
70
71         /* virtual method definition */
72         camel_service_class->construct = construct;
73         camel_service_class->connect = service_connect;
74         camel_service_class->disconnect = service_disconnect;
75         camel_service_class->query_auth_types = query_auth_types;
76         camel_service_class->get_name = get_name;
77         camel_service_class->get_path = get_path;
78 }
79
80 static void
81 camel_service_init (void *o, void *k)
82 {
83         CamelService *service = o;
84
85         service->priv = g_malloc0(sizeof(*service->priv));
86 #ifdef ENABLE_THREADS
87         service->priv->connect_lock = e_mutex_new(E_MUTEX_REC);
88 #endif
89 }
90
91 static void
92 camel_service_finalize (CamelObject *object)
93 {
94         CamelService *camel_service = CAMEL_SERVICE (object);
95
96         if (camel_service->connected) {
97                 CamelException ex;
98
99                 /*g_warning ("camel_service_finalize: finalizing while still connected!");*/
100                 camel_exception_init (&ex);
101                 CSERV_CLASS (camel_service)->disconnect (camel_service, FALSE, &ex);
102                 if (camel_exception_is_set (&ex)) {
103                         g_warning ("camel_service_finalize: silent disconnect failure: %s",
104                                    camel_exception_get_description(&ex));
105                 }
106                 camel_exception_clear (&ex);
107         }
108
109         if (camel_service->url)
110                 camel_url_free (camel_service->url);
111         if (camel_service->session)
112                 camel_object_unref (CAMEL_OBJECT (camel_service->session));
113
114 #ifdef ENABLE_THREADS
115         e_mutex_destroy(camel_service->priv->connect_lock);
116 #endif
117         g_free(camel_service->priv);
118 }
119
120
121
122 CamelType
123 camel_service_get_type (void)
124 {
125         static CamelType camel_service_type = CAMEL_INVALID_TYPE;
126
127         if (camel_service_type == CAMEL_INVALID_TYPE) {
128                 camel_service_type =
129                         camel_type_register (CAMEL_OBJECT_TYPE, "CamelService",
130                                              sizeof (CamelService),
131                                              sizeof (CamelServiceClass),
132                                              (CamelObjectClassInitFunc) camel_service_class_init,
133                                              NULL,
134                                              (CamelObjectInitFunc) camel_service_init,
135                                              camel_service_finalize );
136         }
137         
138         return camel_service_type;
139 }
140
141
142 static void
143 construct (CamelService *service, CamelSession *session,
144            CamelProvider *provider, CamelURL *url, CamelException *ex)
145 {
146         char *url_string;
147
148         if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER) &&
149             (url->user == NULL || url->user[0] == '\0')) {
150                 url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
151                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
152                                       _("URL '%s' needs a username component"),
153                                       url_string);
154                 g_free (url_string);
155                 return;
156         } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST) &&
157                    (url->host == NULL || url->host[0] == '\0')) {
158                 url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
159                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
160                                       _("URL '%s' needs a host component"),
161                                       url_string);
162                 g_free (url_string);
163                 return;
164         } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH) &&
165                    (url->path == NULL || url->path[0] == '\0')) {
166                 url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
167                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
168                                       _("URL '%s' needs a path component"),
169                                       url_string);
170                 g_free (url_string);
171                 return;
172         }
173
174         service->provider = provider;
175         service->url = url;
176         service->session = session;
177         camel_object_ref (CAMEL_OBJECT (session));
178
179         service->connected = FALSE;
180 }
181
182 /**
183  * camel_service_construct:
184  * @service: the CamelService
185  * @session: the session for the service
186  * @provider: the service's provider
187  * @url: the default URL for the service (may be NULL)
188  * @ex: a CamelException
189  *
190  * Constructs a CamelService initialized with the given parameters.
191  **/
192 void
193 camel_service_construct (CamelService *service, CamelSession *session,
194                          CamelProvider *provider, CamelURL *url,
195                          CamelException *ex)
196 {
197         g_return_if_fail (CAMEL_IS_SERVICE (service));
198         g_return_if_fail (CAMEL_IS_SESSION (session));
199
200         CSERV_CLASS (service)->construct (service, session, provider, url, ex);
201 }
202
203
204 static gboolean
205 service_connect (CamelService *service, CamelException *ex)
206 {
207         /* Things like the CamelMboxStore can validly
208          * not define a connect function.
209          */
210          return TRUE;
211 }
212
213 /**
214  * camel_service_connect:
215  * @service: CamelService object
216  * @ex: a CamelException
217  *
218  * Connect to the service using the parameters it was initialized
219  * with.
220  *
221  * Return value: whether or not the connection succeeded
222  **/
223
224 gboolean
225 camel_service_connect (CamelService *service, CamelException *ex)
226 {
227         gboolean ret = FALSE;
228
229         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
230         g_return_val_if_fail (service->session != NULL, FALSE);
231         g_return_val_if_fail (service->url != NULL, FALSE);
232
233         CAMEL_SERVICE_LOCK(service, connect_lock);
234
235         if (service->connected) {
236                 /* But we're still connected, so no exception
237                  * and return true.
238                  */
239                 g_warning ("camel_service_connect: trying to connect to an already connected service");
240                 ret = TRUE;
241         } else if (CSERV_CLASS (service)->connect (service, ex)) {
242                 service->connected = TRUE;
243                 ret = TRUE;
244         }
245
246         CAMEL_SERVICE_UNLOCK(service, connect_lock);
247
248         return ret;
249 }
250
251 static gboolean
252 service_disconnect (CamelService *service, gboolean clean, CamelException *ex)
253 {
254         /*service->connect_level--;*/
255
256         /* We let people get away with not having a disconnect
257          * function -- CamelMboxStore, for example. 
258          */
259
260         return TRUE;
261 }
262
263 /**
264  * camel_service_disconnect:
265  * @service: CamelService object
266  * @clean: whether or not to try to disconnect cleanly.
267  * @ex: a CamelException
268  *
269  * Disconnect from the service. If @clean is %FALSE, it should not
270  * try to do any synchronizing or other cleanup of the connection.
271  *
272  * Return value: whether or not the disconnection succeeded without
273  * errors. (Consult @ex if %FALSE.)
274  **/
275 gboolean
276 camel_service_disconnect (CamelService *service, gboolean clean,
277                           CamelException *ex)
278 {
279         gboolean res = TRUE;
280
281         CAMEL_SERVICE_LOCK(service, connect_lock);
282
283         if (service->connected) {
284                 res = CSERV_CLASS (service)->disconnect (service, clean, ex);
285                 service->connected = FALSE;
286         }
287
288         CAMEL_SERVICE_UNLOCK(service, connect_lock);
289
290         return res;
291 }
292
293 /**
294  * camel_service_get_url:
295  * @service: a service
296  *
297  * Returns the URL representing a service. The returned URL must be
298  * freed when it is no longer needed. For security reasons, this
299  * routine does not return the password.
300  *
301  * Return value: the url name
302  **/
303 char *
304 camel_service_get_url (CamelService *service)
305 {
306         return camel_url_to_string(service->url, FALSE);
307 }
308
309
310 static char *
311 get_name (CamelService *service, gboolean brief)
312 {
313         g_warning ("CamelService::get_name not implemented for `%s'",
314                    camel_type_to_name (CAMEL_OBJECT_GET_TYPE (service)));
315         return g_strdup ("???");
316 }               
317
318 /**
319  * camel_service_get_name:
320  * @service: the service
321  * @brief: whether or not to use a briefer form
322  *
323  * This gets the name of the service in a "friendly" (suitable for
324  * humans) form. If @brief is %TRUE, this should be a brief description
325  * such as for use in the folder tree. If @brief is %FALSE, it should
326  * be a more complete and mostly unambiguous description.
327  *
328  * Return value: the description, which the caller must free.
329  **/
330 char *
331 camel_service_get_name (CamelService *service, gboolean brief)
332 {
333         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
334         g_return_val_if_fail (service->url, NULL);
335
336         return CSERV_CLASS (service)->get_name (service, brief);
337 }
338
339
340 static char *
341 get_path (CamelService *service)
342 {
343         GString *gpath;
344         char *path;
345         CamelURL *url = service->url;
346         CamelProvider *prov = service->provider;
347
348         /* A sort of ad-hoc default implementation that works for our
349          * current set of services.
350          */
351
352         gpath = g_string_new (service->provider->protocol);
353         if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_USER)) {
354                 if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
355                         g_string_sprintfa (gpath, "/%s@%s",
356                                            url->user ? url->user : "",
357                                            url->host ? url->host : "");
358                         
359                         if (url->port)
360                                 g_string_sprintfa (gpath, ":%d", url->port);
361                 } else {
362                         g_string_sprintfa (gpath, "/%s%s",
363                                            url->user ? url->user : "",
364                                            CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_USER) ? "" : "@");
365                 }
366         } else if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
367                 g_string_sprintfa (gpath, "/%s%s",
368                                    CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_HOST) ? "" : "@",
369                                    url->host ? url->host : "");
370                 
371                 if (url->port)
372                         g_string_sprintfa (gpath, ":%d", url->port);
373         }
374         if (CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_PATH)) {
375                 g_string_sprintfa (gpath, "%s%s",
376                                    *url->path == '/' ? "" : "/",
377                                    url->path);
378         }
379
380         path = gpath->str;
381         g_string_free (gpath, FALSE);
382         return path;
383 }               
384
385 /**
386  * camel_service_get_path:
387  * @service: the service
388  *
389  * This gets a valid UNIX relative path describing the service, which
390  * is guaranteed to be different from the path returned for any
391  * different service. This path MUST start with the name of the
392  * provider, followed by a "/", but after that, it is up to the
393  * provider.
394  *
395  * Return value: the path, which the caller must free.
396  **/
397 char *
398 camel_service_get_path (CamelService *service)
399 {
400         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
401         g_return_val_if_fail (service->url, NULL);
402
403         return CSERV_CLASS (service)->get_path (service);
404 }
405
406
407 /**
408  * camel_service_get_session:
409  * @service: a service
410  *
411  * Returns the CamelSession associated with the service.
412  *
413  * Return value: the session
414  **/
415 CamelSession *
416 camel_service_get_session (CamelService *service)
417 {
418         return service->session;
419 }
420
421 /**
422  * camel_service_get_provider:
423  * @service: a service
424  *
425  * Returns the CamelProvider associated with the service.
426  *
427  * Return value: the provider
428  **/
429 CamelProvider *
430 camel_service_get_provider (CamelService *service)
431 {
432         return service->provider;
433 }
434
435 static GList *
436 query_auth_types (CamelService *service, CamelException *ex)
437 {
438         return NULL;
439 }
440
441 /**
442  * camel_service_query_auth_types:
443  * @service: a CamelService
444  * @ex: a CamelException
445  *
446  * This is used by the mail source wizard to get the list of
447  * authentication types supported by the protocol, and information
448  * about them.
449  *
450  * Return value: a list of CamelServiceAuthType records. The caller
451  * must free the list with g_list_free() when it is done with it.
452  **/
453 GList *
454 camel_service_query_auth_types (CamelService *service, CamelException *ex)
455 {
456         GList *ret;
457
458         /* note that we get the connect lock here, which means the callee
459            must not call the connect functions itself */
460         CAMEL_SERVICE_LOCK(service, connect_lock);
461         ret = CSERV_CLASS (service)->query_auth_types (service, ex);
462         CAMEL_SERVICE_UNLOCK(service, connect_lock);
463
464         return ret;
465 }
466
467 /* URL utility routines */
468
469 /**
470  * camel_service_gethost:
471  * @service: a CamelService
472  * @ex: a CamelException
473  *
474  * This is a convenience function to do a gethostbyname on the host
475  * for the service's URL.
476  *
477  * Return value: a (statically-allocated) hostent.
478  **/
479 struct hostent *
480 camel_service_gethost (CamelService *service, CamelException *ex)
481 {
482         char *hostname;
483         struct hostent *h;
484
485         if (service->url->host)
486                 hostname = service->url->host;
487         else
488                 hostname = "localhost";
489
490         return camel_get_host_byname(hostname, ex);
491 }
492
493 #ifdef offsetof
494 #define STRUCT_OFFSET(type, field)        ((gint) offsetof (type, field))
495 #else
496 #define STRUCT_OFFSET(type, field)        ((gint) ((gchar*) &((type *) 0)->field))
497 #endif
498
499 struct _lookup_msg {
500 #ifdef ENABLE_THREADS
501         EMsg msg;
502 #endif
503         const char *name;
504         int result;
505         int herr;
506         struct hostent hostbuf;
507 #ifndef GETHOSTBYNAME_R_FIVE_ARGS
508         struct hostent *hp;
509 #endif
510         int hostbuflen;
511         char *hostbufmem;
512 };
513
514 static void *
515 get_host(void *data)
516 {
517         struct _lookup_msg *info = data;
518
519 #ifdef GETHOSTBYNAME_R_FIVE_ARGS
520         while (gethostbyname_r(info->name, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->herr) && info->herr == ERANGE) {
521 #else
522         while ((info->result = gethostbyname_r(info->name, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->hp, &info->herr)) == ERANGE) {
523 #endif
524                 d(printf("gethostbyname fialed?\n"));
525 #ifdef ENABLE_THREADS
526                 pthread_testcancel();
527 #endif
528                 info->hostbuflen *= 2;
529                 info->hostbufmem = g_realloc(info->hostbufmem, info->hostbuflen);
530         }
531
532         d(printf("gethostbyname ok?\n"));
533
534 #ifdef ENABLE_THREADS
535         e_msgport_reply((EMsg *)info);
536 #endif
537         return NULL;
538 }
539
540 struct hostent *camel_get_host_byname(const char *name, CamelException *ex)
541 {
542 #ifdef ENABLE_THREADS
543         int fdmax, fd, cancel_fd;
544 #endif
545         struct _lookup_msg *msg;
546
547         g_return_val_if_fail(name != NULL, NULL);
548
549         if (camel_operation_cancel_check(NULL)) {
550                 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
551                 return NULL;
552         }
553
554         camel_operation_start(NULL, _("Resolving: %s"), name);
555
556         msg = g_malloc0(sizeof(*msg));
557         msg->hostbuflen = 1024;
558         msg->hostbufmem = g_malloc(msg->hostbuflen);
559         msg->name = name;
560
561 #ifdef ENABLE_THREADS
562         cancel_fd = camel_operation_cancel_fd(NULL);
563         if (cancel_fd == -1) {
564 #endif
565                 get_host(msg);
566 #ifdef ENABLE_THREADS
567         } else {
568                 EMsgPort *reply_port;
569                 pthread_t id;
570                 fd_set rdset;
571
572                 reply_port = msg->msg.reply_port = e_msgport_new();
573                 fd = e_msgport_fd(msg->msg.reply_port);
574                 if (pthread_create(&id, NULL, get_host, msg) == 0) {
575                         FD_ZERO(&rdset);
576                         FD_SET(cancel_fd, &rdset);
577                         FD_SET(fd, &rdset);
578                         fdmax = MAX(fd, cancel_fd) + 1;
579                         d(printf("waiting for name return/cancellation in main process\n"));
580                         if (select(fdmax, &rdset, NULL, 0, NULL) == -1) {
581                                 camel_exception_setv(ex, 1, _("Failure in name lookup: %s"), strerror(errno));
582                                 d(printf("Cancelling lookup thread\n"));
583                                 pthread_cancel(id);
584                         } else if (FD_ISSET(cancel_fd, &rdset)) {
585                                 d(printf("Cancelling lookup thread\n"));
586                                 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
587                                 pthread_cancel(id);
588                         } else {
589                                 struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port);
590
591                                 g_assert(reply == msg);
592                         }
593                         d(printf("waiting for child to exit\n"));
594                         pthread_join(id, NULL);
595                         d(printf("child done\n"));
596                 }
597                 e_msgport_destroy(reply_port);
598         }
599 #endif
600
601         camel_operation_end(NULL);
602
603         if (msg->herr) {
604                 if (!camel_exception_is_set(ex)) {
605                         if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA)
606                                 camel_exception_setv(ex, 1, _("Host lookup failed: %s: host not found"), name);
607                         else
608                                 camel_exception_setv(ex, 1, _("Host lookup failed: %s: unknown reason"), name);
609                 }
610                 g_free(msg->hostbufmem);
611                 g_free(msg);
612                 return NULL;
613         } else {
614                 return &msg->hostbuf;
615         }
616 }
617
618 void camel_free_host(struct hostent *h)
619 {
620         struct _lookup_msg *msg;
621
622         g_return_if_fail(h != NULL);
623
624         /* yeah this looks ugly but it is safe.  we passed out a reference to inside our structure, this maps it
625            to the base structure, so we can free everything right without having to keep track of it separately */
626         msg = (struct _lookup_msg *)(((char *)h) - STRUCT_OFFSET(struct _lookup_msg, hostbuf));
627
628         g_free(msg->hostbufmem);
629         g_free(msg);
630 }