Mercurial > irccd
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 } |