comparison extern/libircclient/src/ssl.c @ 0:1158cffe5a5e

Initial import
author David Demelier <markand@malikania.fr>
date Mon, 08 Feb 2016 16:43:14 +0100
parents
children f67540addf5a
comparison
equal deleted inserted replaced
-1:000000000000 0:1158cffe5a5e
1 /*
2 * Copyright (C) 2004-2012 George Yunaev gyunaev@ulduzsoft.com
3 *
4 * This library is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or (at your
7 * option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12 * License for more details.
13 */
14
15
16 #if defined (ENABLE_SSL)
17
18 // Nonzero if OpenSSL has been initialized
19 static SSL_CTX * ssl_context = 0;
20
21 #if defined (_WIN32)
22 #include <windows.h>
23 // This array will store all of the mutexes available to OpenSSL
24 static CRITICAL_SECTION * mutex_buf = 0;
25
26 // OpenSSL callback to utilize static locks
27 static void cb_openssl_locking_function( int mode, int n, const char * file, int line )
28 {
29 if ( mode & CRYPTO_LOCK)
30 EnterCriticalSection( &mutex_buf[n] );
31 else
32 LeaveCriticalSection( &mutex_buf[n] );
33 }
34
35 // OpenSSL callback to get the thread ID
36 static unsigned long cb_openssl_id_function(void)
37 {
38 return ((unsigned long) GetCurrentThreadId() );
39 }
40
41 static int alloc_mutexes( unsigned int total )
42 {
43 unsigned int i;
44
45 // Enable thread safety in OpenSSL
46 mutex_buf = (CRITICAL_SECTION*) malloc( total * sizeof(CRITICAL_SECTION) );
47
48 if ( !mutex_buf )
49 return -1;
50
51 for ( i = 0; i < total; i++)
52 InitializeCriticalSection( &(mutex_buf[i]) );
53
54 return 0;
55 }
56
57
58 #else
59
60 // This array will store all of the mutexes available to OpenSSL
61 static pthread_mutex_t * mutex_buf = 0;
62
63 // OpenSSL callback to utilize static locks
64 static void cb_openssl_locking_function( int mode, int n, const char * file, int line )
65 {
66 (void)file;
67 (void)line;
68
69 if ( mode & CRYPTO_LOCK)
70 pthread_mutex_lock( &mutex_buf[n] );
71 else
72 pthread_mutex_unlock( &mutex_buf[n] );
73 }
74
75 // OpenSSL callback to get the thread ID
76 static unsigned long cb_openssl_id_function()
77 {
78 return ((unsigned long) pthread_self() );
79 }
80
81 static int alloc_mutexes( unsigned int total )
82 {
83 unsigned i;
84
85 // Enable thread safety in OpenSSL
86 mutex_buf = (pthread_mutex_t*) malloc( total * sizeof(pthread_mutex_t) );
87
88 if ( !mutex_buf )
89 return -1;
90
91 for ( i = 0; i < total; i++)
92 pthread_mutex_init( &(mutex_buf[i]), 0 );
93
94 return 0;
95 }
96
97 #endif
98
99 static int ssl_init_context( irc_session_t * session )
100 {
101 // Load the strings and init the library
102 SSL_load_error_strings();
103
104 // Enable thread safety in OpenSSL
105 if ( alloc_mutexes( CRYPTO_num_locks() ) )
106 return LIBIRC_ERR_NOMEM;
107
108 // Register our callbacks
109 CRYPTO_set_id_callback( cb_openssl_id_function );
110 CRYPTO_set_locking_callback( cb_openssl_locking_function );
111
112 // Init it
113 if ( !SSL_library_init() )
114 return LIBIRC_ERR_SSL_INIT_FAILED;
115
116 if ( RAND_status() == 0 )
117 return LIBIRC_ERR_SSL_INIT_FAILED;
118
119 // Create an SSL context; currently a single context is used for all connections
120 ssl_context = SSL_CTX_new( SSLv23_method() );
121
122 if ( !ssl_context )
123 return LIBIRC_ERR_SSL_INIT_FAILED;
124
125 // Disable SSLv2 as it is unsecure
126 if ( (SSL_CTX_set_options( ssl_context, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2) == 0 )
127 return LIBIRC_ERR_SSL_INIT_FAILED;
128
129 // Enable only strong ciphers
130 if ( SSL_CTX_set_cipher_list( ssl_context, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" ) != 1 )
131 return LIBIRC_ERR_SSL_INIT_FAILED;
132
133 // Set the verification
134 if ( session->options & LIBIRC_OPTION_SSL_NO_VERIFY )
135 SSL_CTX_set_verify( ssl_context, SSL_VERIFY_NONE, 0 );
136 else
137 SSL_CTX_set_verify( ssl_context, SSL_VERIFY_PEER, 0 );
138
139 // Disable session caching
140 SSL_CTX_set_session_cache_mode( ssl_context, SSL_SESS_CACHE_OFF );
141
142 // Enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER so we can move the buffer during sending
143 SSL_CTX_set_mode( ssl_context, SSL_CTX_get_mode(ssl_context) | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE );
144
145 return 0;
146 }
147
148
149 #if defined (_WIN32)
150 #define SSLINIT_LOCK_MUTEX(a) WaitForSingleObject( a, INFINITE )
151 #define SSLINIT_UNLOCK_MUTEX(a) ReleaseMutex( a )
152 #else
153 #define SSLINIT_LOCK_MUTEX(a) pthread_mutex_lock( &a )
154 #define SSLINIT_UNLOCK_MUTEX(a) pthread_mutex_unlock( &a )
155 #endif
156
157 // Initializes the SSL context. Must be called after the socket is created.
158 static int ssl_init( irc_session_t * session )
159 {
160 static int ssl_context_initialized = 0;
161
162 #if defined (_WIN32)
163 static HANDLE initmutex = 0;
164
165 // First time run? Create the mutex
166 if ( initmutex == 0 )
167 {
168 HANDLE m = CreateMutex( 0, FALSE, 0 );
169
170 // Now we check if the mutex has already been created by another thread performing the init concurrently.
171 // If it was, we close our mutex and use the original one. This could be done synchronously by using the
172 // InterlockedCompareExchangePointer function.
173 if ( InterlockedCompareExchangePointer( &m, m, 0 ) != 0 )
174 CloseHandle( m );
175 }
176 #else
177 static pthread_mutex_t initmutex = PTHREAD_MUTEX_INITIALIZER;
178 #endif
179
180 // This initialization needs to be performed only once. The problem is that it is called from
181 // irc_connect() and this function may be called simultaneously from different threads. So we have
182 // to use mutex on Linux because it allows static mutex initialization. Windows doesn't, so here
183 // we do the sabre dance around it.
184 SSLINIT_LOCK_MUTEX( initmutex );
185
186 if ( ssl_context_initialized == 0 )
187 {
188 int res = ssl_init_context( session );
189
190 if ( res )
191 {
192 SSLINIT_UNLOCK_MUTEX( initmutex );
193 return res;
194 }
195
196 ssl_context_initialized = 1;
197 }
198
199 SSLINIT_UNLOCK_MUTEX( initmutex );
200
201 // Get the SSL context
202 session->ssl = SSL_new( ssl_context );
203
204 if ( !session->ssl )
205 return LIBIRC_ERR_SSL_INIT_FAILED;
206
207 // Let OpenSSL use our socket
208 if ( SSL_set_fd( session->ssl, session->sock) != 1 )
209 return LIBIRC_ERR_SSL_INIT_FAILED;
210
211 // Since we're connecting on our own, tell openssl about it
212 SSL_set_connect_state( session->ssl );
213
214 return 0;
215 }
216
217 static void ssl_handle_error( irc_session_t * session, int ssl_error )
218 {
219 if ( ERR_GET_LIB(ssl_error) == ERR_LIB_SSL )
220 {
221 if ( ERR_GET_REASON(ssl_error) == SSL_R_CERTIFICATE_VERIFY_FAILED )
222 {
223 session->lasterror = LIBIRC_ERR_SSL_CERT_VERIFY_FAILED;
224 return;
225 }
226
227 if ( ERR_GET_REASON(ssl_error) == SSL_R_UNKNOWN_PROTOCOL )
228 {
229 session->lasterror = LIBIRC_ERR_CONNECT_SSL_FAILED;
230 return;
231 }
232 }
233
234 #if defined (ENABLE_DEBUG)
235 if ( IS_DEBUG_ENABLED(session) )
236 fprintf (stderr, "[DEBUG] SSL error: %s\n\t(%d, %d)\n",
237 ERR_error_string( ssl_error, NULL), ERR_GET_LIB( ssl_error), ERR_GET_REASON(ssl_error) );
238 #endif
239 }
240
241 static int ssl_recv( irc_session_t * session )
242 {
243 int count;
244 unsigned int amount = (sizeof (session->incoming_buf) - 1) - session->incoming_offset;
245
246 ERR_clear_error();
247
248 // Read up to m_bufferLength bytes
249 count = SSL_read( session->ssl, session->incoming_buf + session->incoming_offset, amount );
250
251 if ( count > 0 )
252 return count;
253 else if ( count == 0 )
254 return -1; // remote connection closed
255 else
256 {
257 int ssl_error = SSL_get_error( session->ssl, count );
258
259 // Handle SSL error since not all of them are actually errors
260 switch ( ssl_error )
261 {
262 case SSL_ERROR_WANT_READ:
263 // This is not really an error. We received something, but
264 // OpenSSL gave nothing to us because all it read was
265 // internal data. Repeat the same read.
266 return 0;
267
268 case SSL_ERROR_WANT_WRITE:
269 // This is not really an error. We received something, but
270 // now OpenSSL needs to send the data before returning any
271 // data to us (like negotiations). This means we'd need
272 // to wait for WRITE event, but call SSL_read() again.
273 session->flags |= SESSIONFL_SSL_READ_WANTS_WRITE;
274 return 0;
275 }
276
277 // This is an SSL error, handle it
278 ssl_handle_error( session, ERR_get_error() );
279 }
280
281 return -1;
282 }
283
284
285 static int ssl_send( irc_session_t * session )
286 {
287 int count;
288 ERR_clear_error();
289
290 count = SSL_write( session->ssl, session->outgoing_buf, session->outgoing_offset );
291
292 if ( count > 0 )
293 return count;
294 else if ( count == 0 )
295 return -1;
296 else
297 {
298 int ssl_error = SSL_get_error( session->ssl, count );
299
300 switch ( ssl_error )
301 {
302 case SSL_ERROR_WANT_READ:
303 // This is not really an error. We sent some internal OpenSSL data,
304 // but now it needs to read more data before it can send anything.
305 // Thus we wait for READ event, but will call SSL_write() again.
306 session->flags |= SESSIONFL_SSL_WRITE_WANTS_READ;
307 return 0;
308
309 case SSL_ERROR_WANT_WRITE:
310 // This is not really an error. We sent some data, but now OpenSSL
311 // wants to send some internal data before sending ours.
312 // Repeat the same write.
313 return 0;
314 }
315
316 // This is an SSL error, handle it
317 ssl_handle_error( session, ERR_get_error() );
318 }
319
320 return -1;
321 }
322
323 #endif
324
325
326 // Handles both SSL and non-SSL reads.
327 // Returns -1 in case there is an error and socket should be closed/connection terminated
328 // Returns 0 in case there is a temporary error and the call should be retried (SSL_WANTS_WRITE case)
329 // Returns a positive number if we actually read something
330 static int session_socket_read( irc_session_t * session )
331 {
332 int length;
333
334 #if defined (ENABLE_SSL)
335 if ( session->ssl )
336 {
337 // Yes, I know this is tricky
338 if ( session->flags & SESSIONFL_SSL_READ_WANTS_WRITE )
339 {
340 session->flags &= ~SESSIONFL_SSL_READ_WANTS_WRITE;
341 ssl_send( session );
342 return 0;
343 }
344
345 return ssl_recv( session );
346 }
347 #endif
348
349 length = socket_recv( &session->sock,
350 session->incoming_buf + session->incoming_offset,
351 (sizeof (session->incoming_buf) - 1) - session->incoming_offset );
352
353 // There is no "retry" errors for regular sockets
354 if ( length <= 0 )
355 return -1;
356
357 return length;
358 }
359
360 // Handles both SSL and non-SSL writes.
361 // Returns -1 in case there is an error and socket should be closed/connection terminated
362 // Returns 0 in case there is a temporary error and the call should be retried (SSL_WANTS_WRITE case)
363 // Returns a positive number if we actually sent something
364 static int session_socket_write( irc_session_t * session )
365 {
366 int length;
367
368 #if defined (ENABLE_SSL)
369 if ( session->ssl )
370 {
371 // Yep
372 if ( session->flags & SESSIONFL_SSL_WRITE_WANTS_READ )
373 {
374 session->flags &= ~SESSIONFL_SSL_WRITE_WANTS_READ;
375 ssl_recv( session );
376 return 0;
377 }
378
379 return ssl_send( session );
380 }
381 #endif
382
383 length = socket_send (&session->sock, session->outgoing_buf, session->outgoing_offset);
384
385 // There is no "retry" errors for regular sockets
386 if ( length <= 0 )
387 return -1;
388
389 return length;
390 }