Add some extra HTTP body context debug
[framework/connectivity/connman.git] / gweb / giognutls.c
1 /*
2  *
3  *  Web service library with GLib integration
4  *
5  *  Copyright (C) 2009-2010  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <unistd.h>
30
31 #include <gnutls/gnutls.h>
32
33 #include "giognutls.h"
34
35 //#define DBG(fmt, arg...)  printf("%s: " fmt "\n" , __func__ , ## arg)
36 #define DBG(fmt, arg...)
37
38 typedef struct _GIOGnuTLSChannel GIOGnuTLSChannel;
39 typedef struct _GIOGnuTLSWatch GIOGnuTLSWatch;
40
41 struct _GIOGnuTLSChannel {
42         GIOChannel channel;
43         GIOChannel *transport;
44         gnutls_certificate_credentials_t cred;
45         gnutls_session session;
46         gboolean established;
47         gboolean again;
48 };
49
50 struct _GIOGnuTLSWatch {
51         GSource source;
52         GPollFD pollfd;
53         GIOChannel *channel;
54         GIOCondition condition;
55 };
56
57 static volatile gint global_init_done = 0;
58
59 static inline void g_io_gnutls_global_init(void)
60 {
61         if (g_atomic_int_compare_and_exchange(&global_init_done, 0, 1) == TRUE)
62                 gnutls_global_init();
63 }
64
65 static GIOStatus check_handshake(GIOChannel *channel, GError **err)
66 {
67         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
68         int result;
69
70         DBG("channel %p", channel);
71
72         if (gnutls_channel->established == TRUE)
73                 return G_IO_STATUS_NORMAL;
74
75 again:
76         result = gnutls_handshake(gnutls_channel->session);
77
78         if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) {
79                 GIOFlags flags = g_io_channel_get_flags(channel);
80
81                 if (gnutls_channel->again == TRUE)
82                         return G_IO_STATUS_AGAIN;
83
84                 if (flags & G_IO_FLAG_NONBLOCK)
85                         return G_IO_STATUS_AGAIN;
86
87                 goto again;
88         }
89
90         if (result < 0) {
91                 g_set_error(err, G_IO_CHANNEL_ERROR,
92                                 G_IO_CHANNEL_ERROR_FAILED, "Handshake failed");
93                 return G_IO_STATUS_ERROR;
94         }
95
96         gnutls_channel->established = TRUE;
97
98         DBG("handshake done");
99
100         return G_IO_STATUS_NORMAL;
101 }
102
103 static GIOStatus g_io_gnutls_read(GIOChannel *channel, gchar *buf,
104                                 gsize count, gsize *bytes_read, GError **err)
105 {
106         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
107         GIOStatus status;
108         ssize_t result;
109
110         DBG("channel %p count %zu", channel, count);
111
112         *bytes_read = 0;
113
114 again:
115         status = check_handshake(channel, err);
116         if (status != G_IO_STATUS_NORMAL)
117                 return status;
118
119         result = gnutls_record_recv(gnutls_channel->session, buf, count);
120
121         DBG("result %zd", result);
122
123         if (result == GNUTLS_E_REHANDSHAKE) {
124                 gnutls_channel->established = FALSE;
125                 goto again;
126         }
127
128         if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) {
129                 GIOFlags flags = g_io_channel_get_flags(channel);
130
131                 if (gnutls_channel->again == TRUE)
132                         return G_IO_STATUS_AGAIN;
133
134                 if (flags & G_IO_FLAG_NONBLOCK)
135                         return G_IO_STATUS_AGAIN;
136
137                 goto again;
138         }
139
140         if (result == GNUTLS_E_UNEXPECTED_PACKET_LENGTH)
141                 return G_IO_STATUS_EOF;
142
143         if (result < 0) {
144                 g_set_error(err, G_IO_CHANNEL_ERROR,
145                                 G_IO_CHANNEL_ERROR_FAILED, "Stream corrupted");
146                 return G_IO_STATUS_ERROR;
147         }
148
149         *bytes_read = result;
150
151         return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF;
152 }
153
154 static GIOStatus g_io_gnutls_write(GIOChannel *channel, const gchar *buf,
155                                 gsize count, gsize *bytes_written, GError **err)
156 {
157         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
158         GIOStatus status;
159         ssize_t result;
160
161         DBG("channel %p count %zu", channel, count);
162
163         *bytes_written = 0;
164
165 again:
166         status = check_handshake(channel, err);
167         if (status != G_IO_STATUS_NORMAL)
168                 return status;
169
170         result = gnutls_record_send(gnutls_channel->session, buf, count);
171
172         DBG("result %zd", result);
173
174         if (result == GNUTLS_E_REHANDSHAKE) {
175                 gnutls_channel->established = FALSE;
176                 goto again;
177         }
178
179         if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) {
180                 GIOFlags flags = g_io_channel_get_flags(channel);
181
182                 if (gnutls_channel->again == TRUE)
183                         return G_IO_STATUS_AGAIN;
184
185                 if (flags & G_IO_FLAG_NONBLOCK)
186                         return G_IO_STATUS_AGAIN;
187
188                 goto again;
189         }
190
191         if (result < 0) {
192                 g_set_error(err, G_IO_CHANNEL_ERROR,
193                                 G_IO_CHANNEL_ERROR_FAILED, "Stream corrupted");
194                 return G_IO_STATUS_ERROR;
195         }
196
197         *bytes_written = result;
198
199         return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF;
200 }
201
202 static GIOStatus g_io_gnutls_seek(GIOChannel *channel, gint64 offset,
203                                                 GSeekType type, GError **err)
204 {
205         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
206         GIOChannel *transport = gnutls_channel->transport;
207
208         DBG("channel %p", channel);
209
210         return transport->funcs->io_seek(transport, offset, type, err);
211 }
212
213 static GIOStatus g_io_gnutls_close(GIOChannel *channel, GError **err)
214 {
215         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
216         GIOChannel *transport = gnutls_channel->transport;
217
218         DBG("channel %p", channel);
219
220         if (gnutls_channel->established == TRUE)
221                 gnutls_bye(gnutls_channel->session, GNUTLS_SHUT_RDWR);
222
223         return transport->funcs->io_close(transport, err);
224 }
225
226 static void g_io_gnutls_free(GIOChannel *channel)
227 {
228         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
229
230         DBG("channel %p", channel);
231
232         g_io_channel_unref(gnutls_channel->transport);
233
234         gnutls_deinit(gnutls_channel->session);
235
236         gnutls_certificate_free_credentials(gnutls_channel->cred);
237
238         g_free(gnutls_channel);
239 }
240
241 static GIOStatus g_io_gnutls_set_flags(GIOChannel *channel,
242                                                 GIOFlags flags, GError **err)
243 {
244         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
245         GIOChannel *transport = gnutls_channel->transport;
246
247         DBG("channel %p flags %u", channel, flags);
248
249         return transport->funcs->io_set_flags(transport, flags, err);
250 }
251
252 static GIOFlags g_io_gnutls_get_flags(GIOChannel *channel)
253 {
254         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
255         GIOChannel *transport = gnutls_channel->transport;
256
257         DBG("channel %p", channel);
258
259         return transport->funcs->io_get_flags(transport);
260 }
261
262 static gboolean g_io_gnutls_prepare(GSource *source, gint *timeout)
263 {
264         DBG("source %p", source);
265
266         *timeout = -1;
267
268         return FALSE;
269 }
270
271 static gboolean g_io_gnutls_check(GSource *source)
272 {
273         GIOGnuTLSWatch *watch = (GIOGnuTLSWatch *) source;
274         GIOCondition condition = watch->pollfd.revents;
275
276         DBG("source %p condition %u", source, condition);
277
278         if (condition & watch->condition)
279                 return TRUE;
280
281         return FALSE;
282 }
283
284 static gboolean g_io_gnutls_dispatch(GSource *source, GSourceFunc callback,
285                                                         gpointer user_data)
286 {
287         GIOGnuTLSWatch *watch = (GIOGnuTLSWatch *) source;
288         GIOFunc func = (GIOFunc) callback;
289         GIOCondition condition = watch->pollfd.revents;
290
291         DBG("source %p condition %u", source, condition);
292
293         if (func == NULL)
294                 return FALSE;
295
296         return func(watch->channel, condition & watch->condition, user_data);
297 }
298
299 static void g_io_gnutls_finalize(GSource *source)
300 {
301         GIOGnuTLSWatch *watch = (GIOGnuTLSWatch *) source;
302
303         DBG("source %p", source);
304
305         g_io_channel_unref(watch->channel);
306 }
307
308 static GSourceFuncs gnutls_watch_funcs = {
309         g_io_gnutls_prepare,
310         g_io_gnutls_check,
311         g_io_gnutls_dispatch,
312         g_io_gnutls_finalize,
313 };
314
315 static GSource *g_io_gnutls_create_watch(GIOChannel *channel,
316                                                 GIOCondition condition)
317 {
318         GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel;
319         GIOGnuTLSWatch *watch;
320         GSource *source;
321
322         DBG("channel %p condition %u", channel, condition);
323
324         source = g_source_new(&gnutls_watch_funcs, sizeof(GIOGnuTLSWatch));
325
326         watch = (GIOGnuTLSWatch *) source;
327
328         watch->channel = channel;
329         g_io_channel_ref(channel);
330
331         watch->condition = condition;
332
333         watch->pollfd.fd = g_io_channel_unix_get_fd(gnutls_channel->transport);
334         watch->pollfd.events = condition;
335
336         g_source_add_poll(source, &watch->pollfd);
337
338         return source;
339 }
340
341 static GIOFuncs gnutls_channel_funcs = {
342         g_io_gnutls_read,
343         g_io_gnutls_write,
344         g_io_gnutls_seek,
345         g_io_gnutls_close,
346         g_io_gnutls_create_watch,
347         g_io_gnutls_free,
348         g_io_gnutls_set_flags,
349         g_io_gnutls_get_flags,
350 };
351
352 static ssize_t g_io_gnutls_push_func(gnutls_transport_ptr_t transport_data,
353                                                 const void *buf, size_t count)
354 {
355         GIOGnuTLSChannel *gnutls_channel = transport_data;
356         ssize_t result;
357         int fd;
358
359         DBG("transport %p count %zu", gnutls_channel->transport, count);
360
361         fd = g_io_channel_unix_get_fd(gnutls_channel->transport);
362
363         result = write(fd, buf, count);
364
365         if (result < 0 && errno == EAGAIN)
366                 gnutls_channel->again = TRUE;
367         else
368                 gnutls_channel->again = FALSE;
369
370         DBG("result %zd", result);
371
372         return result;
373 }
374
375 static ssize_t g_io_gnutls_pull_func(gnutls_transport_ptr_t transport_data,
376                                                 void *buf, size_t count)
377 {
378         GIOGnuTLSChannel *gnutls_channel = transport_data;
379         ssize_t result;
380         int fd;
381
382         DBG("transport %p count %zu", gnutls_channel->transport, count);
383
384         fd = g_io_channel_unix_get_fd(gnutls_channel->transport);
385
386         result = read(fd, buf, count);
387
388         if (result < 0 && errno == EAGAIN)
389                 gnutls_channel->again = TRUE;
390         else
391                 gnutls_channel->again = FALSE;
392
393         DBG("result %zd", result);
394
395         return result;
396 }
397
398 GIOChannel *g_io_channel_gnutls_new(int fd)
399 {
400         GIOGnuTLSChannel *gnutls_channel;
401         GIOChannel *channel;
402         int err;
403
404         DBG("");
405
406         gnutls_channel = g_new(GIOGnuTLSChannel, 1);
407
408         channel = (GIOChannel *) gnutls_channel;
409
410         g_io_channel_init(channel);
411         channel->funcs = &gnutls_channel_funcs;
412
413         gnutls_channel->transport = g_io_channel_unix_new(fd);
414
415         g_io_channel_set_encoding(gnutls_channel->transport, NULL, NULL);
416         g_io_channel_set_buffered(gnutls_channel->transport, FALSE);
417
418         channel->is_seekable = FALSE;
419         channel->is_readable = TRUE;
420         channel->is_writeable = TRUE;
421
422         channel->do_encode = FALSE;
423
424         g_io_gnutls_global_init();
425
426         err = gnutls_init(&gnutls_channel->session, GNUTLS_CLIENT);
427         if (err < 0) {
428                 g_free(gnutls_channel);
429                 return NULL;
430         }
431
432         gnutls_transport_set_ptr(gnutls_channel->session, gnutls_channel);
433         gnutls_transport_set_push_function(gnutls_channel->session,
434                                                 g_io_gnutls_push_func);
435         gnutls_transport_set_pull_function(gnutls_channel->session,
436                                                 g_io_gnutls_pull_func);
437         gnutls_transport_set_lowat(gnutls_channel->session, 0);
438
439         gnutls_priority_set_direct(gnutls_channel->session,
440                                 "NORMAL:!VERS-TLS1.1:!VERS-TLS1.0", NULL);
441
442         gnutls_certificate_allocate_credentials(&gnutls_channel->cred);
443         gnutls_credentials_set(gnutls_channel->session,
444                                 GNUTLS_CRD_CERTIFICATE, gnutls_channel->cred);
445
446         DBG("channel %p transport %p", channel, gnutls_channel->transport);
447
448         return channel;
449 }