source: lib/ssl_gnutls.c @ 0d9d53e

Last change on this file since 0d9d53e was 8a2221a7, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-03-23T14:29:19Z

Fixed stalling issue with OpenSSL and Jabber (#368).

  • Property mode set to 100644
File size: 6.0 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2004 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* SSL module - GnuTLS version                                          */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#include <gnutls/gnutls.h>
27#include <fcntl.h>
28#include <unistd.h>
29#include "proxy.h"
30#include "ssl_client.h"
31#include "sock.h"
32#include "stdlib.h"
33
34int ssl_errno = 0;
35
36static gboolean initialized = FALSE;
37
38#include <limits.h>
39
40#if defined(ULONG_MAX) && ULONG_MAX > 4294967295UL
41#define GNUTLS_STUPID_CAST (long)
42#else
43#define GNUTLS_STUPID_CAST (int)
44#endif
45
46struct scd
47{
48        ssl_input_function func;
49        gpointer data;
50        int fd;
51        gboolean established;
52        int inpa;
53       
54        gnutls_session session;
55        gnutls_certificate_credentials xcred;
56};
57
58static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond );
59static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond );
60static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond );
61
62
63void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data )
64{
65        struct scd *conn = g_new0( struct scd, 1 );
66       
67        conn->fd = proxy_connect( host, port, ssl_connected, conn );
68        conn->func = func;
69        conn->data = data;
70        conn->inpa = -1;
71       
72        if( conn->fd < 0 )
73        {
74                g_free( conn );
75                return NULL;
76        }
77       
78        return conn;
79}
80
81void *ssl_starttls( int fd, ssl_input_function func, gpointer data )
82{
83        struct scd *conn = g_new0( struct scd, 1 );
84       
85        conn->fd = fd;
86        conn->func = func;
87        conn->data = data;
88        conn->inpa = -1;
89       
90        /* This function should be called via a (short) timeout instead of
91           directly from here, because these SSL calls are *supposed* to be
92           *completely* asynchronous and not ready yet when this function
93           (or *_connect, for examle) returns. Also, errors are reported via
94           the callback function, not via this function's return value.
95           
96           In short, doing things like this makes the rest of the code a lot
97           simpler. */
98       
99        b_timeout_add( 1, ssl_starttls_real, conn );
100       
101        return conn;
102}
103
104static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond )
105{
106        struct scd *conn = data;
107       
108        return ssl_connected( conn, conn->fd, GAIM_INPUT_WRITE );
109}
110
111static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )
112{
113        struct scd *conn = data;
114       
115        if( source == -1 )
116        {
117                conn->func( conn->data, NULL, cond );
118                g_free( conn );
119                return FALSE;
120        }
121       
122        if( !initialized )
123        {
124                gnutls_global_init();
125                initialized = TRUE;
126                atexit( gnutls_global_deinit );
127        }
128       
129        gnutls_certificate_allocate_credentials( &conn->xcred );
130        gnutls_init( &conn->session, GNUTLS_CLIENT );
131        gnutls_set_default_priority( conn->session );
132        gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, conn->xcred );
133       
134        sock_make_nonblocking( conn->fd );
135        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd );
136       
137        return ssl_handshake( data, source, cond );
138}
139
140static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )
141{
142        struct scd *conn = data;
143        int st;
144       
145        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
146        {
147                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
148                {
149                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
150                                                  ssl_handshake, data );
151                }
152                else
153                {
154                        conn->func( conn->data, NULL, cond );
155                       
156                        gnutls_deinit( conn->session );
157                        gnutls_certificate_free_credentials( conn->xcred );
158                        closesocket( conn->fd );
159                       
160                        g_free( conn );
161                }
162        }
163        else
164        {
165                /* For now we can't handle non-blocking perfectly everywhere... */
166                sock_make_blocking( conn->fd );
167               
168                conn->established = TRUE;
169                conn->func( conn->data, conn, cond );
170        }
171       
172        return FALSE;
173}
174
175int ssl_read( void *conn, char *buf, int len )
176{
177        int st;
178       
179        if( !((struct scd*)conn)->established )
180        {
181                ssl_errno = SSL_NOHANDSHAKE;
182                return( -1 );
183        }
184       
185        st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
186       
187        ssl_errno = SSL_OK;
188        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
189                ssl_errno = SSL_AGAIN;
190       
191        return st;
192}
193
194int ssl_write( void *conn, const char *buf, int len )
195{
196        int st;
197       
198        if( !((struct scd*)conn)->established )
199        {
200                ssl_errno = SSL_NOHANDSHAKE;
201                return( -1 );
202        }
203       
204        st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
205       
206        ssl_errno = SSL_OK;
207        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
208                ssl_errno = SSL_AGAIN;
209       
210        return st;
211}
212
213/* See ssl_openssl.c for an explanation. */
214int ssl_pending( void *conn )
215{
216        return 0;
217}
218
219void ssl_disconnect( void *conn_ )
220{
221        struct scd *conn = conn_;
222       
223        if( conn->inpa != -1 )
224                b_event_remove( conn->inpa );
225       
226        if( conn->established )
227                gnutls_bye( conn->session, GNUTLS_SHUT_WR );
228       
229        closesocket( conn->fd );
230       
231        if( conn->session )
232                gnutls_deinit( conn->session );
233        if( conn->xcred )
234                gnutls_certificate_free_credentials( conn->xcred );
235        g_free( conn );
236}
237
238int ssl_getfd( void *conn )
239{
240        return( ((struct scd*)conn)->fd );
241}
242
243b_input_condition ssl_getdirection( void *conn )
244{
245        return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
246                GAIM_INPUT_WRITE : GAIM_INPUT_READ );
247}
Note: See TracBrowser for help on using the repository browser.