Bug #606181 - Accepting bad SSL certificate applies to any hostname
[platform/upstream/evolution-data-server.git] / camel / providers / imapx / camel-imapx-conn-manager.c
1 /*-*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-conn-manager.h
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * Authors: Chenthill Palanisamy <pchenthill@novell.com>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of version 2 of the GNU Lesser General Public
10  * License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21
22 #include "camel-imapx-conn-manager.h"
23 #include "camel-imapx-settings.h"
24 #include "camel-imapx-store.h"
25 #include "camel-imapx-utils.h"
26
27 #define c(...) camel_imapx_debug(conman, __VA_ARGS__)
28
29 #define CON_READ_LOCK(x) \
30         (g_static_rw_lock_reader_lock (&(x)->priv->rw_lock))
31 #define CON_READ_UNLOCK(x) \
32         (g_static_rw_lock_reader_unlock (&(x)->priv->rw_lock))
33 #define CON_WRITE_LOCK(x) \
34         (g_static_rw_lock_writer_lock (&(x)->priv->rw_lock))
35 #define CON_WRITE_UNLOCK(x) \
36         (g_static_rw_lock_writer_unlock (&(x)->priv->rw_lock))
37
38 #define CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE(obj) \
39         (G_TYPE_INSTANCE_GET_PRIVATE \
40         ((obj), CAMEL_TYPE_IMAPX_CONN_MANAGER, CamelIMAPXConnManagerPrivate))
41
42 typedef struct _ConnectionInfo ConnectionInfo;
43
44 struct _CamelIMAPXConnManagerPrivate {
45         /* XXX Might be easier for this to be a hash table,
46          *     with CamelIMAPXServer pointers as the keys. */
47         GList *connections;
48         gpointer store;  /* weak pointer */
49         GStaticRWLock rw_lock;
50 };
51
52 struct _ConnectionInfo {
53         GMutex *lock;
54         CamelIMAPXServer *is;
55         GHashTable *folder_names;
56         gchar *selected_folder;
57         volatile gint ref_count;
58 };
59
60 enum {
61         PROP_0,
62         PROP_STORE
63 };
64
65 G_DEFINE_TYPE (
66         CamelIMAPXConnManager,
67         camel_imapx_conn_manager,
68         CAMEL_TYPE_OBJECT)
69
70 static ConnectionInfo *
71 connection_info_new (CamelIMAPXServer *is)
72 {
73         ConnectionInfo *cinfo;
74         GHashTable *folder_names;
75
76         folder_names = g_hash_table_new_full (
77                 (GHashFunc) g_str_hash,
78                 (GEqualFunc) g_str_equal,
79                 (GDestroyNotify) g_free,
80                 (GDestroyNotify) NULL);
81
82         cinfo = g_slice_new0 (ConnectionInfo);
83         cinfo->lock = g_mutex_new ();
84         cinfo->is = g_object_ref (is);
85         cinfo->folder_names = folder_names;
86         cinfo->ref_count = 1;
87
88         return cinfo;
89 }
90
91 static ConnectionInfo *
92 connection_info_ref (ConnectionInfo *cinfo)
93 {
94         g_return_val_if_fail (cinfo != NULL, NULL);
95         g_return_val_if_fail (cinfo->ref_count > 0, NULL);
96
97         g_atomic_int_inc (&cinfo->ref_count);
98
99         return cinfo;
100 }
101
102 static void
103 connection_info_unref (ConnectionInfo *cinfo)
104 {
105         g_return_if_fail (cinfo != NULL);
106         g_return_if_fail (cinfo->ref_count > 0);
107
108         if (g_atomic_int_dec_and_test (&cinfo->ref_count)) {
109                 camel_imapx_server_connect (cinfo->is, NULL, NULL);
110
111                 g_mutex_free (cinfo->lock);
112                 g_object_unref (cinfo->is);
113                 g_hash_table_destroy (cinfo->folder_names);
114                 g_free (cinfo->selected_folder);
115
116                 g_slice_free (ConnectionInfo, cinfo);
117         }
118 }
119
120 static gboolean
121 connection_info_is_available (ConnectionInfo *cinfo)
122 {
123         gboolean available;
124
125         g_return_val_if_fail (cinfo != NULL, FALSE);
126
127         g_mutex_lock (cinfo->lock);
128
129         /* Available means it's not tracking any folder names. */
130         available = (g_hash_table_size (cinfo->folder_names) == 0);
131
132         g_mutex_unlock (cinfo->lock);
133
134         return available;
135 }
136
137 static gboolean
138 connection_info_has_folder_name (ConnectionInfo *cinfo,
139                                  const gchar *folder_name)
140 {
141         gpointer value;
142
143         g_return_val_if_fail (cinfo != NULL, FALSE);
144
145         if (folder_name == NULL)
146                 return FALSE;
147
148         g_mutex_lock (cinfo->lock);
149
150         value = g_hash_table_lookup (cinfo->folder_names, folder_name);
151
152         g_mutex_unlock (cinfo->lock);
153
154         return (value != NULL);
155 }
156
157 static void
158 connection_info_insert_folder_name (ConnectionInfo *cinfo,
159                                     const gchar *folder_name)
160 {
161         g_return_if_fail (cinfo != NULL);
162         g_return_if_fail (folder_name != NULL);
163
164         g_mutex_lock (cinfo->lock);
165
166         g_hash_table_insert (
167                 cinfo->folder_names,
168                 g_strdup (folder_name),
169                 GINT_TO_POINTER (1));
170
171         g_mutex_unlock (cinfo->lock);
172 }
173
174 static void
175 connection_info_remove_folder_name (ConnectionInfo *cinfo,
176                                     const gchar *folder_name)
177 {
178         g_return_if_fail (cinfo != NULL);
179         g_return_if_fail (folder_name != NULL);
180
181         g_mutex_lock (cinfo->lock);
182
183         g_hash_table_remove (cinfo->folder_names, folder_name);
184
185         g_mutex_unlock (cinfo->lock);
186 }
187
188 static gchar *
189 connection_info_dup_selected_folder (ConnectionInfo *cinfo)
190 {
191         gchar *selected_folder;
192
193         g_return_val_if_fail (cinfo != NULL, NULL);
194
195         g_mutex_lock (cinfo->lock);
196
197         selected_folder = g_strdup (cinfo->selected_folder);
198
199         g_mutex_unlock (cinfo->lock);
200
201         return selected_folder;
202 }
203
204 static void
205 connection_info_set_selected_folder (ConnectionInfo *cinfo,
206                                      const gchar *selected_folder)
207 {
208         g_return_if_fail (cinfo != NULL);
209
210         g_mutex_lock (cinfo->lock);
211
212         g_free (cinfo->selected_folder);
213         cinfo->selected_folder = g_strdup (selected_folder);
214
215         g_mutex_unlock (cinfo->lock);
216 }
217
218 static GList *
219 imapx_conn_manager_list_info (CamelIMAPXConnManager *con_man)
220 {
221         GList *list;
222
223         g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
224
225         CON_READ_LOCK (con_man);
226
227         list = g_list_copy (con_man->priv->connections);
228         g_list_foreach (list, (GFunc) connection_info_ref, NULL);
229
230         CON_READ_UNLOCK (con_man);
231
232         return list;
233 }
234
235 static ConnectionInfo *
236 imapx_conn_manager_lookup_info (CamelIMAPXConnManager *con_man,
237                                 CamelIMAPXServer *is)
238 {
239         ConnectionInfo *cinfo = NULL;
240         GList *list, *link;
241
242         g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
243         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
244
245         CON_READ_LOCK (con_man);
246
247         list = con_man->priv->connections;
248
249         for (link = list; link != NULL; link = g_list_next (link)) {
250                 ConnectionInfo *candidate = link->data;
251
252                 if (candidate->is == is) {
253                         cinfo = connection_info_ref (candidate);
254                         break;
255                 }
256         }
257
258         CON_READ_UNLOCK (con_man);
259
260         return cinfo;
261 }
262
263 static gboolean
264 imapx_conn_manager_remove_info (CamelIMAPXConnManager *con_man,
265                                 ConnectionInfo *cinfo)
266 {
267         GList *list, *link;
268         gboolean removed = FALSE;
269
270         g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), FALSE);
271         g_return_val_if_fail (cinfo != NULL, FALSE);
272
273         CON_WRITE_LOCK (con_man);
274
275         list = con_man->priv->connections;
276         link = g_list_find (list, cinfo);
277
278         if (link != NULL) {
279                 list = g_list_remove_link (list, link);
280                 connection_info_unref (cinfo);
281                 removed = TRUE;
282         }
283
284         con_man->priv->connections = list;
285
286         CON_WRITE_UNLOCK (con_man);
287
288         return removed;
289 }
290
291 static void
292 imapx_conn_manager_set_store (CamelIMAPXConnManager *con_man,
293                               CamelStore *store)
294 {
295         g_return_if_fail (CAMEL_IS_STORE (store));
296         g_return_if_fail (con_man->priv->store == NULL);
297
298         con_man->priv->store = store;
299
300         g_object_add_weak_pointer (
301                 G_OBJECT (store), &con_man->priv->store);
302 }
303
304 static void
305 imapx_conn_manager_set_property (GObject *object,
306                                  guint property_id,
307                                  const GValue *value,
308                                  GParamSpec *pspec)
309 {
310         switch (property_id) {
311                 case PROP_STORE:
312                         imapx_conn_manager_set_store (
313                                 CAMEL_IMAPX_CONN_MANAGER (object),
314                                 g_value_get_object (value));
315                         return;
316         }
317
318         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
319 }
320
321 static void
322 imapx_conn_manager_get_property (GObject *object,
323                                  guint property_id,
324                                  GValue *value,
325                                  GParamSpec *pspec)
326 {
327         switch (property_id) {
328                 case PROP_STORE:
329                         g_value_set_object (
330                                 value,
331                                 camel_imapx_conn_manager_get_store (
332                                 CAMEL_IMAPX_CONN_MANAGER (object)));
333                         return;
334         }
335
336         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
337 }
338
339 static void
340 imapx_conn_manager_dispose (GObject *object)
341 {
342         CamelIMAPXConnManagerPrivate *priv;
343
344         priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (object);
345
346         g_list_free_full (
347                 priv->connections,
348                 (GDestroyNotify) connection_info_unref);
349         priv->connections = NULL;
350
351         if (priv->store != NULL) {
352                 g_object_remove_weak_pointer (
353                         G_OBJECT (priv->store), &priv->store);
354                 priv->store = NULL;
355         }
356
357         /* Chain up to parent's dispose() method. */
358         G_OBJECT_CLASS (camel_imapx_conn_manager_parent_class)->dispose (object);
359 }
360
361 static void
362 imapx_conn_manager_finalize (GObject *object)
363 {
364         CamelIMAPXConnManagerPrivate *priv;
365
366         priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (object);
367
368         g_static_rw_lock_free (&priv->rw_lock);
369
370         /* Chain up to parent's finalize() method. */
371         G_OBJECT_CLASS (camel_imapx_conn_manager_parent_class)->finalize (object);
372 }
373
374 static void
375 camel_imapx_conn_manager_class_init (CamelIMAPXConnManagerClass *class)
376 {
377         GObjectClass *object_class;
378
379         g_type_class_add_private (class, sizeof (CamelIMAPXConnManagerPrivate));
380
381         object_class = G_OBJECT_CLASS (class);
382         object_class->set_property = imapx_conn_manager_set_property;
383         object_class->get_property = imapx_conn_manager_get_property;
384         object_class->dispose = imapx_conn_manager_dispose;
385         object_class->finalize = imapx_conn_manager_finalize;
386
387         g_object_class_install_property (
388                 object_class,
389                 PROP_STORE,
390                 g_param_spec_object (
391                         "store",
392                         "Store",
393                         "The CamelStore to which we belong",
394                         CAMEL_TYPE_STORE,
395                         G_PARAM_READWRITE |
396                         G_PARAM_CONSTRUCT_ONLY |
397                         G_PARAM_STATIC_STRINGS));
398 }
399
400 static void
401 camel_imapx_conn_manager_init (CamelIMAPXConnManager *con_man)
402 {
403         con_man->priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (con_man);
404
405         g_static_rw_lock_init (&con_man->priv->rw_lock);
406 }
407
408 /* Static functions go here */
409
410 /* TODO destroy unused connections in a time-out loop */
411 static void
412 imapx_conn_shutdown (CamelIMAPXServer *is,
413                      CamelIMAPXConnManager *con_man)
414 {
415         ConnectionInfo *cinfo;
416
417         /* Returns a new ConnectionInfo reference. */
418         cinfo = imapx_conn_manager_lookup_info (con_man, is);
419
420         if (cinfo != NULL) {
421                 imapx_conn_manager_remove_info (con_man, cinfo);
422                 connection_info_unref (cinfo);
423         }
424 }
425
426 static void
427 imapx_conn_update_select (CamelIMAPXServer *is,
428                           const gchar *selected_folder,
429                           CamelIMAPXConnManager *con_man)
430 {
431         ConnectionInfo *cinfo;
432         gchar *old_selected_folder;
433
434         /* Returns a new ConnectionInfo reference. */
435         cinfo = imapx_conn_manager_lookup_info (con_man, is);
436
437         if (cinfo == NULL)
438                 return;
439
440         old_selected_folder = connection_info_dup_selected_folder (cinfo);
441
442         if (old_selected_folder != NULL) {
443                 IMAPXJobQueueInfo *jinfo;
444
445                 jinfo = camel_imapx_server_get_job_queue_info (is);
446                 if (!g_hash_table_lookup (jinfo->folders, old_selected_folder)) {
447                         connection_info_remove_folder_name (cinfo, old_selected_folder);
448                         c(is->tagprefix, "Removed folder %s from connection folder list - select changed \n", old_selected_folder);
449                 }
450                 camel_imapx_destroy_job_queue_info (jinfo);
451
452                 g_free (old_selected_folder);
453         }
454
455         connection_info_set_selected_folder (cinfo, selected_folder);
456
457         connection_info_unref (cinfo);
458 }
459
460 /* This should find a connection if the slots are full, returns NULL if there are slots available for a new connection for a folder */
461 static CamelIMAPXServer *
462 imapx_find_connection_unlocked (CamelIMAPXConnManager *con_man,
463                                 const gchar *folder_name)
464 {
465         CamelService *service;
466         CamelSettings *settings;
467         CamelIMAPXServer *is = NULL;
468         ConnectionInfo *cinfo = NULL;
469         GList *list, *link;
470         guint concurrent_connections;
471         guint min_jobs = G_MAXUINT;
472
473         /* Caller must be holding CON_WRITE_LOCK. */
474
475         service = CAMEL_SERVICE (con_man->priv->store);
476         settings = camel_service_get_settings (service);
477
478         concurrent_connections =
479                 camel_imapx_settings_get_concurrent_connections (
480                 CAMEL_IMAPX_SETTINGS (settings));
481
482         /* XXX Have a dedicated connection for INBOX ? */
483
484         list = con_man->priv->connections;
485
486         /* If a folder was not given, find the least-busy connection. */
487         if (folder_name == NULL)
488                 goto least_busy;
489
490         /* First try to find a connection already handling this folder. */
491         for (link = list; link != NULL; link = g_list_next (link)) {
492                 ConnectionInfo *candidate = link->data;
493
494                 if (connection_info_has_folder_name (candidate, folder_name)) {
495                         cinfo = connection_info_ref (candidate);
496                         goto exit;
497                 }
498         }
499
500         /* Next try to find a connection not handling any folders. */
501         for (link = list; link != NULL; link = g_list_next (link)) {
502                 ConnectionInfo *candidate = link->data;
503
504                 if (connection_info_is_available (candidate)) {
505                         cinfo = connection_info_ref (candidate);
506                         goto exit;
507                 }
508         }
509
510 least_busy:
511         /* Pick the connection with the least number of jobs in progress. */
512         for (link = list; link != NULL; link = g_list_next (link)) {
513                 ConnectionInfo *candidate = link->data;
514                 IMAPXJobQueueInfo *jinfo = NULL;
515
516                 jinfo = camel_imapx_server_get_job_queue_info (candidate->is);
517
518                 if (cinfo == NULL) {
519                         cinfo = connection_info_ref (candidate);
520                         min_jobs = jinfo->queue_len;
521
522                 } else if (jinfo->queue_len < min_jobs) {
523                         connection_info_unref (cinfo);
524                         cinfo = connection_info_ref (candidate);
525                         min_jobs = jinfo->queue_len;
526                 }
527
528                 camel_imapx_destroy_job_queue_info (jinfo);
529         }
530
531 exit:
532         if (cinfo != NULL && folder_name != NULL)
533                 connection_info_insert_folder_name (cinfo, folder_name);
534
535         if (cinfo != NULL) {
536                 is = g_object_ref (cinfo->is);
537                 connection_info_unref (cinfo);
538         }
539
540         if (camel_debug_flag (conman))
541                 g_assert (!(concurrent_connections == g_list_length (con_man->priv->connections) && is == NULL));
542
543         return is;
544 }
545
546 static CamelIMAPXServer *
547 imapx_create_new_connection_unlocked (CamelIMAPXConnManager *con_man,
548                                       const gchar *folder_name,
549                                       GCancellable *cancellable,
550                                       GError **error)
551 {
552         CamelIMAPXServer *is = NULL;
553         CamelIMAPXStore *imapx_store;
554         CamelStore *store = con_man->priv->store;
555         CamelService *service;
556         ConnectionInfo *cinfo = NULL;
557         gboolean success;
558
559         /* Caller must be holding CON_WRITE_LOCK. */
560
561         service = CAMEL_SERVICE (store);
562
563         imapx_store = CAMEL_IMAPX_STORE (store);
564
565         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
566
567         /* Check if we got cancelled while we were waiting. */
568         if (g_cancellable_set_error_if_cancelled (cancellable, error))
569                 goto exit;
570
571         is = camel_imapx_server_new (store);
572
573         /* XXX As part of the connect operation the CamelIMAPXServer will
574          *     have to call camel_session_authenticate_sync(), but it has
575          *     no way to pass itself through in that call so the service
576          *     knows which CamelIMAPXServer is trying to authenticate.
577          *
578          *     IMAPX is the only provider that does multiple connections
579          *     like this, so I didn't want to pollute the CamelSession and
580          *     CamelService authentication APIs with an extra argument.
581          *     Instead we do this little hack so the service knows which
582          *     CamelIMAPXServer to act on in its authenticate_sync() method.
583          *
584          *     Because we're holding the CAMEL_SERVICE_REC_CONNECT_LOCK
585          *     we should not have multiple IMAPX connections trying to
586          *     authenticate at once, so this should be thread-safe.
587          */
588         imapx_store->authenticating_server = g_object_ref (is);
589         success = camel_imapx_server_connect (is, cancellable, error);
590         g_object_unref (imapx_store->authenticating_server);
591         imapx_store->authenticating_server = NULL;
592
593         if (!success) {
594                 g_object_unref (is);
595                 is = NULL;
596                 goto exit;
597         }
598
599         g_signal_connect (
600                 is, "shutdown",
601                 G_CALLBACK (imapx_conn_shutdown), con_man);
602         g_signal_connect (
603                 is, "select_changed",
604                 G_CALLBACK (imapx_conn_update_select), con_man);
605
606         cinfo = connection_info_new (is);
607
608         if (folder_name != NULL)
609                 connection_info_insert_folder_name (cinfo, folder_name);
610
611         /* Takes ownership of the ConnectionInfo. */
612         con_man->priv->connections = g_list_prepend (
613                 con_man->priv->connections, cinfo);
614
615         c(is->tagprefix, "Created new connection for %s and total connections %d \n", folder_name, g_list_length (con_man->priv->connections));
616
617 exit:
618         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
619
620         return is;
621 }
622
623 /****************************/
624
625 CamelIMAPXConnManager *
626 camel_imapx_conn_manager_new (CamelStore *store)
627 {
628         g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
629
630         return g_object_new (
631                 CAMEL_TYPE_IMAPX_CONN_MANAGER, "store", store, NULL);
632 }
633
634 CamelStore *
635 camel_imapx_conn_manager_get_store (CamelIMAPXConnManager *con_man)
636 {
637         g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
638
639         return CAMEL_STORE (con_man->priv->store);
640 }
641
642 CamelIMAPXServer *
643 camel_imapx_conn_manager_get_connection (CamelIMAPXConnManager *con_man,
644                                          const gchar *folder_name,
645                                          GCancellable *cancellable,
646                                          GError **error)
647 {
648         CamelIMAPXServer *is = NULL;
649
650         g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
651
652         /* Hold the writer lock while we requisition a CamelIMAPXServer
653          * to prevent other threads from adding or removing connections. */
654         CON_WRITE_LOCK (con_man);
655
656         /* Check if we got cancelled while waiting for the lock. */
657         if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
658                 is = imapx_find_connection_unlocked (con_man, folder_name);
659                 if (is == NULL)
660                         is = imapx_create_new_connection_unlocked (
661                                 con_man, folder_name, cancellable, error);
662         }
663
664         CON_WRITE_UNLOCK (con_man);
665
666         return is;
667 }
668
669 GList *
670 camel_imapx_conn_manager_get_connections (CamelIMAPXConnManager *con_man)
671 {
672         GList *list, *link;
673
674         g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man), NULL);
675
676         list = imapx_conn_manager_list_info (con_man);
677
678         /* Swap ConnectionInfo for CamelIMAPXServer in each link. */
679         for (link = list; link != NULL; link = g_list_next (link)) {
680                 ConnectionInfo *cinfo = link->data;
681                 link->data = g_object_ref (cinfo->is);
682                 connection_info_unref (cinfo);
683         }
684
685         return list;
686 }
687
688 /* Used for handling operations that fails to execute and that needs to removed from folder list */
689 void
690 camel_imapx_conn_manager_update_con_info (CamelIMAPXConnManager *con_man,
691                                           CamelIMAPXServer *is,
692                                           const gchar *folder_name)
693 {
694         ConnectionInfo *cinfo;
695         IMAPXJobQueueInfo *jinfo;
696
697         g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man));
698
699         /* Returns a new ConnectionInfo reference. */
700         cinfo = imapx_conn_manager_lookup_info (con_man, is);
701
702         if (cinfo == NULL)
703                 return;
704
705         jinfo = camel_imapx_server_get_job_queue_info (cinfo->is);
706         if (!g_hash_table_lookup (jinfo->folders, folder_name)) {
707                 connection_info_remove_folder_name (cinfo, folder_name);
708                 c(is->tagprefix, "Removed folder %s from connection folder list - op done \n", folder_name);
709         }
710         camel_imapx_destroy_job_queue_info (jinfo);
711
712         connection_info_unref (cinfo);
713 }
714
715 void
716 camel_imapx_conn_manager_close_connections (CamelIMAPXConnManager *con_man)
717 {
718         g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (con_man));
719
720         CON_WRITE_LOCK (con_man);
721
722         g_list_free_full (
723                 con_man->priv->connections,
724                 (GDestroyNotify) connection_info_unref);
725         con_man->priv->connections = NULL;
726
727         CON_WRITE_UNLOCK (con_man);
728 }
729