Update for glib 2.31 GMutex API changes
[platform/upstream/glib-networking.git] / tls / gnutls / gtlsbackend-gnutls.c
1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright 2010 Red Hat, Inc
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, see
17  * <http://www.gnu.org/licenses/>.
18  */
19
20 #include "config.h"
21 #include "glib.h"
22
23 #include <errno.h>
24
25 #include <gnutls/gnutls.h>
26 #include <gcrypt.h>
27 #ifndef G_OS_WIN32
28 #include <pthread.h>
29 #endif
30
31 #include "gtlsbackend-gnutls.h"
32 #include "gtlscertificate-gnutls.h"
33 #include "gtlsclientconnection-gnutls.h"
34 #include "gtlsfiledatabase-gnutls.h"
35 #include "gtlsserverconnection-gnutls.h"
36
37 struct _GTlsBackendGnutlsPrivate
38 {
39   GMutex mutex;
40   GTlsDatabase *default_database;
41 };
42
43 static void g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface);
44
45 G_DEFINE_DYNAMIC_TYPE_EXTENDED (GTlsBackendGnutls, g_tls_backend_gnutls, G_TYPE_OBJECT, 0,
46                                 G_IMPLEMENT_INTERFACE_DYNAMIC (G_TYPE_TLS_BACKEND,
47                                                                g_tls_backend_gnutls_interface_init);)
48
49 #if defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) && !defined(G_OS_WIN32)
50 GCRY_THREAD_OPTION_PTHREAD_IMPL;
51 #endif
52
53 #ifdef G_OS_WIN32
54
55 static int
56 gtls_gcry_win32_mutex_init (void **priv)
57 {
58         int err = 0;
59         CRITICAL_SECTION *lock = (CRITICAL_SECTION*)malloc (sizeof (CRITICAL_SECTION));
60
61         if (!lock)
62                 err = ENOMEM;
63         if (!err) {
64                 InitializeCriticalSection (lock);
65                 *priv = lock;
66         }
67         return err;
68 }
69
70 static int
71 gtls_gcry_win32_mutex_destroy (void **lock)
72 {
73         DeleteCriticalSection ((CRITICAL_SECTION*)*lock);
74         free (*lock);
75         return 0;
76 }
77
78 static int
79 gtls_gcry_win32_mutex_lock (void **lock)
80 {
81         EnterCriticalSection ((CRITICAL_SECTION*)*lock);
82         return 0;
83 }
84
85 static int
86 gtls_gcry_win32_mutex_unlock (void **lock)
87 {
88         LeaveCriticalSection ((CRITICAL_SECTION*)*lock);
89         return 0;
90 }
91
92
93 static struct gcry_thread_cbs gtls_gcry_threads_win32 = {                \
94         (GCRY_THREAD_OPTION_USER | (GCRY_THREAD_OPTION_VERSION << 8)),   \
95         NULL, gtls_gcry_win32_mutex_init, gtls_gcry_win32_mutex_destroy, \
96         gtls_gcry_win32_mutex_lock, gtls_gcry_win32_mutex_unlock,        \
97         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
98
99 #endif
100
101 #ifdef GTLS_GNUTLS_DEBUG
102 static void
103 gtls_log_func (int level, const char *msg)
104 {
105   g_print ("GTLS: %s", msg);
106 }
107 #endif
108
109 static gpointer
110 gtls_gnutls_init (gpointer data)
111 {
112 #if defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) && !defined(G_OS_WIN32)
113   gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
114 #elif defined(G_OS_WIN32)
115   gcry_control (GCRYCTL_SET_THREAD_CBS, &gtls_gcry_threads_win32);
116 #endif
117   gnutls_global_init ();
118
119 #ifdef GTLS_GNUTLS_DEBUG
120   gnutls_global_set_log_function (gtls_log_func);
121   gnutls_global_set_log_level (9);
122 #endif
123
124   /* Leak the module to keep it from being unloaded. */
125   g_type_plugin_use (g_type_get_plugin (G_TYPE_TLS_BACKEND_GNUTLS));
126   return NULL;
127 }
128
129 static GOnce gnutls_inited = G_ONCE_INIT;
130
131 static void
132 g_tls_backend_gnutls_init (GTlsBackendGnutls *backend)
133 {
134   /* Once we call gtls_gnutls_init(), we can't allow the module to be
135    * unloaded, since that would break the pointers to the mutex
136    * functions we set for gcrypt. So we initialize it from here rather
137    * than at class init time so that it doesn't happen unless the app
138    * is actually using TLS (as opposed to just calling
139    * g_io_modules_scan_all_in_directory()).
140    */
141   g_once (&gnutls_inited, gtls_gnutls_init, NULL);
142
143   backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (backend, G_TYPE_TLS_BACKEND_GNUTLS, GTlsBackendGnutlsPrivate);
144   g_mutex_init (&backend->priv->mutex);
145 }
146
147 static void
148 g_tls_backend_gnutls_finalize (GObject *object)
149 {
150   GTlsBackendGnutls *backend = G_TLS_BACKEND_GNUTLS (object);
151
152   if (backend->priv->default_database)
153     g_object_unref (backend->priv->default_database);
154   g_mutex_clear (&backend->priv->mutex);
155
156   G_OBJECT_CLASS (g_tls_backend_gnutls_parent_class)->finalize (object);
157 }
158
159 static void
160 g_tls_backend_gnutls_class_init (GTlsBackendGnutlsClass *backend_class)
161 {
162   GObjectClass *gobject_class = G_OBJECT_CLASS (backend_class);
163   gobject_class->finalize = g_tls_backend_gnutls_finalize;
164   g_type_class_add_private (backend_class, sizeof (GTlsBackendGnutlsPrivate));
165 }
166
167 static void
168 g_tls_backend_gnutls_class_finalize (GTlsBackendGnutlsClass *backend_class)
169 {
170 }
171
172 static GTlsDatabase*
173 g_tls_backend_gnutls_get_default_database (GTlsBackend *backend)
174 {
175   GTlsBackendGnutls *self = G_TLS_BACKEND_GNUTLS (backend);
176   const gchar *anchor_file = NULL;
177   GTlsDatabase *result;
178   GError *error = NULL;
179
180   g_mutex_lock (&self->priv->mutex);
181
182   if (self->priv->default_database)
183     {
184       result = g_object_ref (self->priv->default_database);
185     }
186   else
187     {
188 #ifdef GTLS_SYSTEM_CA_FILE
189       anchor_file = GTLS_SYSTEM_CA_FILE;
190 #endif
191       result = g_tls_file_database_new (anchor_file, &error);
192       if (error)
193         {
194           g_warning ("couldn't load TLS file database: %s",
195                      error->message);
196           g_clear_error (&error);
197         }
198       else
199         {
200           self->priv->default_database = g_object_ref (result);
201         }
202     }
203
204   g_mutex_unlock (&self->priv->mutex);
205
206   return result;
207 }
208
209 static void
210 g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface)
211 {
212   iface->get_certificate_type       = g_tls_certificate_gnutls_get_type;
213   iface->get_client_connection_type = g_tls_client_connection_gnutls_get_type;
214   iface->get_server_connection_type = g_tls_server_connection_gnutls_get_type;
215   iface->get_file_database_type =     g_tls_file_database_gnutls_get_type;
216   iface->get_default_database =       g_tls_backend_gnutls_get_default_database;
217 }
218
219 /* Session cache support; all the details are sort of arbitrary. Note
220  * that having session_cache_cleanup() be a little bit slow isn't the
221  * end of the world, since it will still be faster than the network
222  * is. (NSS uses a linked list for its cache...)
223  */
224
225 G_LOCK_DEFINE_STATIC (session_cache_lock);
226 GHashTable *session_cache;
227
228 #define SESSION_CACHE_MAX_SIZE 50
229 #define SESSION_CACHE_MAX_AGE (60 * 60) /* one hour */
230
231 typedef struct {
232   gchar      *session_id;
233   GByteArray *session_data;
234   time_t      last_used;
235 } GTlsBackendGnutlsCacheData;
236
237 static void
238 session_cache_cleanup (void)
239 {
240   GHashTableIter iter;
241   gpointer key, value;
242   GTlsBackendGnutlsCacheData *cache_data;
243   time_t expired = time (NULL) - SESSION_CACHE_MAX_AGE;
244
245   g_hash_table_iter_init (&iter, session_cache);
246   while (g_hash_table_iter_next (&iter, &key, &value))
247     {
248       cache_data = value;
249       if (cache_data->last_used < expired)
250         g_hash_table_iter_remove (&iter);
251     }
252 }
253
254 static void
255 cache_data_free (gpointer data)
256 {
257   GTlsBackendGnutlsCacheData *cache_data = data;
258
259   g_free (cache_data->session_id);
260   g_byte_array_unref (cache_data->session_data);
261   g_slice_free (GTlsBackendGnutlsCacheData, cache_data);
262 }
263
264 void
265 g_tls_backend_gnutls_cache_session_data (const gchar *session_id,
266                                          guchar      *session_data,
267                                          gsize        session_data_length)
268 {
269   GTlsBackendGnutlsCacheData *cache_data;
270
271   G_LOCK (session_cache_lock);
272
273   if (!session_cache)
274     session_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
275                                            NULL, cache_data_free);
276
277   cache_data = g_hash_table_lookup (session_cache, session_id);
278   if (cache_data)
279     {
280       if (cache_data->session_data->len == session_data_length &&
281           memcmp (cache_data->session_data->data,
282                   session_data, session_data_length) == 0)
283         {
284           cache_data->last_used = time (NULL);
285           G_UNLOCK (session_cache_lock);
286           return;
287         }
288
289       g_byte_array_set_size (cache_data->session_data, 0);
290     }
291   else
292     {
293       if (g_hash_table_size (session_cache) >= SESSION_CACHE_MAX_SIZE)
294         session_cache_cleanup ();
295
296       cache_data = g_slice_new (GTlsBackendGnutlsCacheData);
297       cache_data->session_id = g_strdup (session_id);
298       cache_data->session_data = g_byte_array_sized_new (session_data_length);
299
300       g_hash_table_insert (session_cache, cache_data->session_id, cache_data);
301     }
302
303   g_byte_array_append (cache_data->session_data,
304                        session_data, session_data_length);
305   cache_data->last_used = time (NULL);
306   G_UNLOCK (session_cache_lock);
307 }
308
309 void
310 g_tls_backend_gnutls_uncache_session_data (const gchar *session_id)
311 {
312   G_LOCK (session_cache_lock);
313   if (session_cache)
314     g_hash_table_remove (session_cache, session_id);
315   G_UNLOCK (session_cache_lock);
316 }
317
318 GByteArray *
319 g_tls_backend_gnutls_lookup_session_data (const gchar *session_id)
320 {
321   GTlsBackendGnutlsCacheData *cache_data;
322   GByteArray *session_data = NULL;
323
324   G_LOCK (session_cache_lock);
325   if (session_cache)
326     {
327       cache_data = g_hash_table_lookup (session_cache, session_id);
328       if (cache_data)
329         {
330           cache_data->last_used = time (NULL);
331           session_data = g_byte_array_ref (cache_data->session_data);
332         }
333     }
334   G_UNLOCK (session_cache_lock);
335
336   return session_data;
337 }
338
339 void
340 g_tls_backend_gnutls_register (GIOModule *module)
341 {
342   g_tls_backend_gnutls_register_type (G_TYPE_MODULE (module));
343   g_io_extension_point_implement (G_TLS_BACKEND_EXTENSION_POINT_NAME,
344                                   g_tls_backend_gnutls_get_type(),
345                                   "gnutls",
346                                   0);
347 }