source: lib/ssl_gnutls.c @ aaaed5e

Last change on this file since aaaed5e was c1ed6527, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-10-02T13:19:13Z

No more double free()/crashes when trying to set up an SSL connection to
a non-SSL server, and better handling of TLS connection setup by initializing
the TLS session from a callback function (which guarantees a valid return
value from ssl_starttls() before any error callback could be called).

  • Property mode set to 100644
File size: 5.7 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
38struct scd
39{
40        ssl_input_function func;
41        gpointer data;
42        int fd;
43        gboolean established;
44        int inpa;
45       
46        gnutls_session session;
47        gnutls_certificate_credentials xcred;
48};
49
50static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond );
51static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond );
52static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond );
53
54
55void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data )
56{
57        struct scd *conn = g_new0( struct scd, 1 );
58       
59        conn->fd = proxy_connect( host, port, ssl_connected, conn );
60        conn->func = func;
61        conn->data = data;
62        conn->inpa = -1;
63       
64        if( conn->fd < 0 )
65        {
66                g_free( conn );
67                return NULL;
68        }
69       
70        return conn;
71}
72
73void *ssl_starttls( int fd, ssl_input_function func, gpointer data )
74{
75        struct scd *conn = g_new0( struct scd, 1 );
76       
77        conn->fd = fd;
78        conn->func = func;
79        conn->data = data;
80        conn->inpa = -1;
81       
82        /* This function should be called via a (short) timeout instead of
83           directly from here, because these SSL calls are *supposed* to be
84           *completely* asynchronous and not ready yet when this function
85           (or *_connect, for examle) returns. Also, errors are reported via
86           the callback function, not via this function's return value.
87           
88           In short, doing things like this makes the rest of the code a lot
89           simpler. */
90       
91        b_timeout_add( 1, ssl_starttls_real, conn );
92       
93        return conn;
94}
95
96static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond )
97{
98        struct scd *conn = data;
99       
100        return ssl_connected( conn, conn->fd, GAIM_INPUT_WRITE );
101}
102
103static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )
104{
105        struct scd *conn = data;
106       
107        if( source == -1 )
108        {
109                conn->func( conn->data, NULL, cond );
110               
111                g_free( conn );
112               
113                return FALSE;
114        }
115       
116        if( !initialized )
117        {
118                gnutls_global_init();
119                initialized = TRUE;
120                atexit( gnutls_global_deinit );
121        }
122       
123        gnutls_certificate_allocate_credentials( &conn->xcred );
124        gnutls_init( &conn->session, GNUTLS_CLIENT );
125        gnutls_set_default_priority( conn->session );
126        gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, conn->xcred );
127       
128        sock_make_nonblocking( conn->fd );
129        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) conn->fd );
130       
131        return ssl_handshake( data, source, cond );
132}
133
134static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )
135{
136        struct scd *conn = data;
137        int st;
138       
139        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
140        {
141                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
142                {
143                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
144                                                  ssl_handshake, data );
145                }
146                else
147                {
148                        conn->func( conn->data, NULL, cond );
149                       
150                        gnutls_deinit( conn->session );
151                        gnutls_certificate_free_credentials( conn->xcred );
152                        closesocket( conn->fd );
153                       
154                        g_free( conn );
155                }
156        }
157        else
158        {
159                /* For now we can't handle non-blocking perfectly everywhere... */
160                sock_make_blocking( conn->fd );
161               
162                conn->established = TRUE;
163                conn->func( conn->data, conn, cond );
164        }
165       
166        return FALSE;
167}
168
169int ssl_read( void *conn, char *buf, int len )
170{
171        int st;
172       
173        if( !((struct scd*)conn)->established )
174        {
175                ssl_errno = SSL_NOHANDSHAKE;
176                return( -1 );
177        }
178       
179        st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
180       
181        ssl_errno = SSL_OK;
182        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
183                ssl_errno = SSL_AGAIN;
184       
185        return st;
186}
187
188int ssl_write( void *conn, const char *buf, int len )
189{
190        int st;
191       
192        if( !((struct scd*)conn)->established )
193        {
194                ssl_errno = SSL_NOHANDSHAKE;
195                return( -1 );
196        }
197       
198        st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
199       
200        ssl_errno = SSL_OK;
201        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
202                ssl_errno = SSL_AGAIN;
203       
204        return st;
205}
206
207void ssl_disconnect( void *conn_ )
208{
209        struct scd *conn = conn_;
210       
211        if( conn->inpa != -1 )
212                b_event_remove( conn->inpa );
213       
214        if( conn->established )
215                gnutls_bye( conn->session, GNUTLS_SHUT_WR );
216       
217        closesocket( conn->fd );
218       
219        gnutls_deinit( conn->session );
220        gnutls_certificate_free_credentials( conn->xcred );
221        g_free( conn );
222}
223
224int ssl_getfd( void *conn )
225{
226        return( ((struct scd*)conn)->fd );
227}
228
229b_input_condition ssl_getdirection( void *conn )
230{
231        return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
232                GAIM_INPUT_WRITE : GAIM_INPUT_READ );
233}
Note: See TracBrowser for help on using the repository browser.