1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-service.c : Abstract class for an email service */
7 * Bertrand Guiheneuf <bertrand@helixcode.com>
9 * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com)
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.
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.
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
38 #include "e-util/e-msgport.h"
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"
49 static CamelObjectClass *parent_class = NULL;
51 /* Returns the class for a CamelService */
52 #define CSERV_CLASS(so) CAMEL_SERVICE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
54 static void construct (CamelService *service, CamelSession *session,
55 CamelProvider *provider, CamelURL *url,
57 static gboolean service_connect(CamelService *service, CamelException *ex);
58 static gboolean service_disconnect(CamelService *service, gboolean clean,
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);
67 camel_service_class_init (CamelServiceClass *camel_service_class)
69 parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE);
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;
81 camel_service_init (void *o, void *k)
83 CamelService *service = o;
85 service->priv = g_malloc0(sizeof(*service->priv));
87 service->priv->connect_lock = e_mutex_new(E_MUTEX_REC);
92 camel_service_finalize (CamelObject *object)
94 CamelService *camel_service = CAMEL_SERVICE (object);
96 if (camel_service->connected) {
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));
106 camel_exception_clear (&ex);
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));
114 #ifdef ENABLE_THREADS
115 e_mutex_destroy(camel_service->priv->connect_lock);
117 g_free(camel_service->priv);
123 camel_service_get_type (void)
125 static CamelType camel_service_type = CAMEL_INVALID_TYPE;
127 if (camel_service_type == CAMEL_INVALID_TYPE) {
129 camel_type_register (CAMEL_OBJECT_TYPE, "CamelService",
130 sizeof (CamelService),
131 sizeof (CamelServiceClass),
132 (CamelObjectClassInitFunc) camel_service_class_init,
134 (CamelObjectInitFunc) camel_service_init,
135 camel_service_finalize );
138 return camel_service_type;
143 construct (CamelService *service, CamelSession *session,
144 CamelProvider *provider, CamelURL *url, CamelException *ex)
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"),
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"),
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"),
174 service->provider = provider;
176 service->session = session;
177 camel_object_ref (CAMEL_OBJECT (session));
179 service->connected = FALSE;
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
190 * Constructs a CamelService initialized with the given parameters.
193 camel_service_construct (CamelService *service, CamelSession *session,
194 CamelProvider *provider, CamelURL *url,
197 g_return_if_fail (CAMEL_IS_SERVICE (service));
198 g_return_if_fail (CAMEL_IS_SESSION (session));
200 CSERV_CLASS (service)->construct (service, session, provider, url, ex);
205 service_connect (CamelService *service, CamelException *ex)
207 /* Things like the CamelMboxStore can validly
208 * not define a connect function.
214 * camel_service_connect:
215 * @service: CamelService object
216 * @ex: a CamelException
218 * Connect to the service using the parameters it was initialized
221 * Return value: whether or not the connection succeeded
225 camel_service_connect (CamelService *service, CamelException *ex)
227 gboolean ret = FALSE;
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);
233 CAMEL_SERVICE_LOCK(service, connect_lock);
235 if (service->connected) {
236 /* But we're still connected, so no exception
239 g_warning ("camel_service_connect: trying to connect to an already connected service");
241 } else if (CSERV_CLASS (service)->connect (service, ex)) {
242 service->connected = TRUE;
246 CAMEL_SERVICE_UNLOCK(service, connect_lock);
252 service_disconnect (CamelService *service, gboolean clean, CamelException *ex)
254 /*service->connect_level--;*/
256 /* We let people get away with not having a disconnect
257 * function -- CamelMboxStore, for example.
264 * camel_service_disconnect:
265 * @service: CamelService object
266 * @clean: whether or not to try to disconnect cleanly.
267 * @ex: a CamelException
269 * Disconnect from the service. If @clean is %FALSE, it should not
270 * try to do any synchronizing or other cleanup of the connection.
272 * Return value: whether or not the disconnection succeeded without
273 * errors. (Consult @ex if %FALSE.)
276 camel_service_disconnect (CamelService *service, gboolean clean,
281 CAMEL_SERVICE_LOCK(service, connect_lock);
283 if (service->connected) {
284 res = CSERV_CLASS (service)->disconnect (service, clean, ex);
285 service->connected = FALSE;
288 CAMEL_SERVICE_UNLOCK(service, connect_lock);
294 * camel_service_get_url:
295 * @service: a service
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.
301 * Return value: the url name
304 camel_service_get_url (CamelService *service)
306 return camel_url_to_string(service->url, FALSE);
311 get_name (CamelService *service, gboolean brief)
313 g_warning ("CamelService::get_name not implemented for `%s'",
314 camel_type_to_name (CAMEL_OBJECT_GET_TYPE (service)));
315 return g_strdup ("???");
319 * camel_service_get_name:
320 * @service: the service
321 * @brief: whether or not to use a briefer form
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.
328 * Return value: the description, which the caller must free.
331 camel_service_get_name (CamelService *service, gboolean brief)
333 g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
334 g_return_val_if_fail (service->url, NULL);
336 return CSERV_CLASS (service)->get_name (service, brief);
341 get_path (CamelService *service)
345 CamelURL *url = service->url;
346 CamelProvider *prov = service->provider;
348 /* A sort of ad-hoc default implementation that works for our
349 * current set of services.
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 : "");
360 g_string_sprintfa (gpath, ":%d", url->port);
362 g_string_sprintfa (gpath, "/%s%s",
363 url->user ? url->user : "",
364 CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_USER) ? "" : "@");
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 : "");
372 g_string_sprintfa (gpath, ":%d", url->port);
374 if (CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_PATH)) {
375 g_string_sprintfa (gpath, "%s%s",
376 *url->path == '/' ? "" : "/",
381 g_string_free (gpath, FALSE);
386 * camel_service_get_path:
387 * @service: the service
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
395 * Return value: the path, which the caller must free.
398 camel_service_get_path (CamelService *service)
400 g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
401 g_return_val_if_fail (service->url, NULL);
403 return CSERV_CLASS (service)->get_path (service);
408 * camel_service_get_session:
409 * @service: a service
411 * Returns the CamelSession associated with the service.
413 * Return value: the session
416 camel_service_get_session (CamelService *service)
418 return service->session;
422 * camel_service_get_provider:
423 * @service: a service
425 * Returns the CamelProvider associated with the service.
427 * Return value: the provider
430 camel_service_get_provider (CamelService *service)
432 return service->provider;
436 query_auth_types (CamelService *service, CamelException *ex)
442 * camel_service_query_auth_types:
443 * @service: a CamelService
444 * @ex: a CamelException
446 * This is used by the mail source wizard to get the list of
447 * authentication types supported by the protocol, and information
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.
454 camel_service_query_auth_types (CamelService *service, CamelException *ex)
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);
467 /* URL utility routines */
470 * camel_service_gethost:
471 * @service: a CamelService
472 * @ex: a CamelException
474 * This is a convenience function to do a gethostbyname on the host
475 * for the service's URL.
477 * Return value: a (statically-allocated) hostent.
480 camel_service_gethost (CamelService *service, CamelException *ex)
485 if (service->url->host)
486 hostname = service->url->host;
488 hostname = "localhost";
490 return camel_get_host_byname(hostname, ex);
494 #define STRUCT_OFFSET(type, field) ((gint) offsetof (type, field))
496 #define STRUCT_OFFSET(type, field) ((gint) ((gchar*) &((type *) 0)->field))
500 #ifdef ENABLE_THREADS
506 struct hostent hostbuf;
507 #ifndef GETHOSTBYNAME_R_FIVE_ARGS
517 struct _lookup_msg *info = data;
519 #ifdef GETHOSTBYNAME_R_FIVE_ARGS
520 while (gethostbyname_r(info->name, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->herr) && info->herr == ERANGE) {
522 while ((info->result = gethostbyname_r(info->name, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->hp, &info->herr)) == ERANGE) {
524 d(printf("gethostbyname fialed?\n"));
525 #ifdef ENABLE_THREADS
526 pthread_testcancel();
528 info->hostbuflen *= 2;
529 info->hostbufmem = g_realloc(info->hostbufmem, info->hostbuflen);
532 d(printf("gethostbyname ok?\n"));
534 #ifdef ENABLE_THREADS
535 e_msgport_reply((EMsg *)info);
540 struct hostent *camel_get_host_byname(const char *name, CamelException *ex)
542 #ifdef ENABLE_THREADS
543 int fdmax, fd, cancel_fd;
545 struct _lookup_msg *msg;
547 g_return_val_if_fail(name != NULL, NULL);
549 if (camel_operation_cancel_check(NULL)) {
550 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
554 camel_operation_start(NULL, _("Resolving: %s"), name);
556 msg = g_malloc0(sizeof(*msg));
557 msg->hostbuflen = 1024;
558 msg->hostbufmem = g_malloc(msg->hostbuflen);
561 #ifdef ENABLE_THREADS
562 cancel_fd = camel_operation_cancel_fd(NULL);
563 if (cancel_fd == -1) {
566 #ifdef ENABLE_THREADS
568 EMsgPort *reply_port;
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) {
576 FD_SET(cancel_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"));
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"));
589 struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port);
591 g_assert(reply == msg);
593 d(printf("waiting for child to exit\n"));
594 pthread_join(id, NULL);
595 d(printf("child done\n"));
597 e_msgport_destroy(reply_port);
601 camel_operation_end(NULL);
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);
608 camel_exception_setv(ex, 1, _("Host lookup failed: %s: unknown reason"), name);
610 g_free(msg->hostbufmem);
614 return &msg->hostbuf;
618 void camel_free_host(struct hostent *h)
620 struct _lookup_msg *msg;
622 g_return_if_fail(h != NULL);
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));
628 g_free(msg->hostbufmem);