]> git.sesse.net Git - vlc/blob - modules/misc/gnutls.c
Use free() instead of LocaleFree() for utf8_readdir() result
[vlc] / modules / misc / gnutls.c
1 /*****************************************************************************
2  * gnutls.c
3  *****************************************************************************
4  * Copyright (C) 2004-2006 Rémi Denis-Courmont
5  * $Id$
6  *
7  * Authors: Rémi Denis-Courmont <rem # videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #include <vlc/vlc.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <time.h>
32
33 #include <sys/types.h>
34 #include <errno.h>
35 #ifdef HAVE_DIRENT_H
36 # include <dirent.h>
37 #endif
38 #ifdef HAVE_SYS_STAT_H
39 # include <sys/stat.h>
40 # ifdef HAVE_UNISTD_H
41 #  include <unistd.h>
42 # endif
43 #endif
44
45
46 #include "vlc_tls.h"
47 #include "charset.h"
48
49 #include <gcrypt.h>
50 #include <gnutls/gnutls.h>
51 #include <gnutls/x509.h>
52
53 #define DH_BITS             1024
54 #define CACHE_EXPIRATION    3600
55 #define CACHE_SIZE          64
56
57 /*****************************************************************************
58  * Module descriptor
59  *****************************************************************************/
60 static int  Open ( vlc_object_t * );
61 static void Close( vlc_object_t * );
62
63 #define DH_BITS_TEXT N_("Diffie-Hellman prime bits")
64 #define DH_BITS_LONGTEXT N_( \
65     "This allows you to modify the Diffie-Hellman prime's number of bits, " \
66     "used for TLS or SSL-based server-side encryption. This is generally " \
67     "not needed." )
68
69 #define CACHE_EXPIRATION_TEXT N_("Expiration time for resumed TLS sessions")
70 #define CACHE_EXPIRATION_LONGTEXT N_( \
71     "It is possible to cache the resumed TLS sessions. This is the expiration "\
72     "time of the sessions stored in this cache, in seconds." )
73
74 #define CACHE_SIZE_TEXT N_("Number of resumed TLS sessions")
75 #define CACHE_SIZE_LONGTEXT N_( \
76     "This is the maximum number of resumed TLS sessions that " \
77     "the cache will hold." )
78
79 #define CHECK_CERT_TEXT N_("Check TLS/SSL server certificate validity")
80 #define CHECK_CERT_LONGTEXT N_( \
81     "This ensures that the server certificate is valid " \
82     "(i.e. signed by an approved Certification Authority)." )
83
84 #define CHECK_HOSTNAME_TEXT N_("Check TLS/SSL server hostname in certificate")
85 #define CHECK_HOSTNAME_LONGTEXT N_( \
86     "This ensures that the server hostname in certificate matches the " \
87     "requested host name." )
88
89 vlc_module_begin();
90     set_shortname( "GnuTLS" );
91     set_description( _("GnuTLS TLS encryption layer") );
92     set_capability( "tls", 1 );
93     set_callbacks( Open, Close );
94     set_category( CAT_ADVANCED );
95     set_subcategory( SUBCAT_ADVANCED_MISC );
96
97     add_bool( "tls-check-cert", VLC_TRUE, NULL, CHECK_CERT_TEXT,
98               CHECK_CERT_LONGTEXT, VLC_FALSE );
99     add_bool( "tls-check-hostname", VLC_TRUE, NULL, CHECK_HOSTNAME_TEXT,
100               CHECK_HOSTNAME_LONGTEXT, VLC_FALSE );
101
102     add_integer( "gnutls-dh-bits", DH_BITS, NULL, DH_BITS_TEXT,
103                  DH_BITS_LONGTEXT, VLC_TRUE );
104     add_integer( "gnutls-cache-expiration", CACHE_EXPIRATION, NULL,
105                  CACHE_EXPIRATION_TEXT, CACHE_EXPIRATION_LONGTEXT, VLC_TRUE );
106     add_integer( "gnutls-cache-size", CACHE_SIZE, NULL, CACHE_SIZE_TEXT,
107                  CACHE_SIZE_LONGTEXT, VLC_TRUE );
108 vlc_module_end();
109
110
111 #define MAX_SESSION_ID    32
112 #define MAX_SESSION_DATA  1024
113
114 typedef struct saved_session_t
115 {
116     char id[MAX_SESSION_ID];
117     char data[MAX_SESSION_DATA];
118
119     unsigned i_idlen;
120     unsigned i_datalen;
121 } saved_session_t;
122
123
124 typedef struct tls_server_sys_t
125 {
126     gnutls_certificate_credentials  x509_cred;
127     gnutls_dh_params                dh_params;
128
129     struct saved_session_t          *p_cache;
130     struct saved_session_t          *p_store;
131     int                             i_cache_size;
132     vlc_mutex_t                     cache_lock;
133
134     int                             (*pf_handshake2)( tls_session_t * );
135 } tls_server_sys_t;
136
137
138 typedef struct tls_session_sys_t
139 {
140     gnutls_session  session;
141     char            *psz_hostname;
142     vlc_bool_t      b_handshaked;
143 } tls_session_sys_t;
144
145
146 typedef struct tls_client_sys_t
147 {
148     struct tls_session_sys_t       session;
149     gnutls_certificate_credentials x509_cred;
150 } tls_client_sys_t;
151
152
153 static int
154 _get_Int( vlc_object_t *p_this, const char *var )
155 {
156     vlc_value_t value;
157
158     if( var_Get( p_this, var, &value ) != VLC_SUCCESS )
159     {
160         var_Create( p_this, var, VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
161         var_Get( p_this, var, &value );
162     }
163
164     return value.i_int;
165 }
166
167 static int
168 _get_Bool( vlc_object_t *p_this, const char *var )
169 {
170     vlc_value_t value;
171
172     if( var_Get( p_this, var, &value ) != VLC_SUCCESS )
173     {
174         var_Create( p_this, var, VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
175         var_Get( p_this, var, &value );
176     }
177
178     return value.b_bool;
179 }
180
181 #define get_Int( a, b ) _get_Int( (vlc_object_t *)(a), (b) )
182 #define get_Bool( a, b ) _get_Bool( (vlc_object_t *)(a), (b) )
183
184
185 /**
186  * Sends data through a TLS session.
187  */
188 static int
189 gnutls_Send( void *p_session, const void *buf, int i_length )
190 {
191     int val;
192     tls_session_sys_t *p_sys;
193
194     p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);
195
196     val = gnutls_record_send( p_sys->session, buf, i_length );
197     /* TODO: handle fatal error */
198     return val < 0 ? -1 : val;
199 }
200
201
202 /**
203  * Receives data through a TLS session.
204  */
205 static int
206 gnutls_Recv( void *p_session, void *buf, int i_length )
207 {
208     int val;
209     tls_session_sys_t *p_sys;
210
211     p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);
212
213     val = gnutls_record_recv( p_sys->session, buf, i_length );
214     /* TODO: handle fatal error */
215     return val < 0 ? -1 : val;
216 }
217
218
219 /*****************************************************************************
220  * tls_Session(Continue)?Handshake:
221  *****************************************************************************
222  * Establishes TLS session with a peer through socket <fd>.
223  * Returns -1 on error (you need not and must not call tls_SessionClose)
224  * 0 on succesful handshake completion, 1 if more would-be blocking recv is
225  * needed, 2 if more would-be blocking send is required.
226  *****************************************************************************/
227 static int
228 gnutls_ContinueHandshake( tls_session_t *p_session)
229 {
230     tls_session_sys_t *p_sys;
231     int val;
232
233     p_sys = (tls_session_sys_t *)(p_session->p_sys);
234
235      /* TODO: handle fatal error */
236 #ifdef WIN32
237     WSASetLastError( 0 );
238 #endif
239     val = gnutls_handshake( p_sys->session );
240     if( ( val == GNUTLS_E_AGAIN ) || ( val == GNUTLS_E_INTERRUPTED ) )
241         return 1 + gnutls_record_get_direction( p_sys->session );
242
243     if( val < 0 )
244     {
245 #ifdef WIN32
246         msg_Dbg( p_session, "Winsock error %d", WSAGetLastError( ) );
247 #endif
248         msg_Err( p_session, "TLS handshake failed: %s",
249                  gnutls_strerror( val ) );
250         p_session->pf_close( p_session );
251         return -1;
252     }
253
254     p_sys->b_handshaked = VLC_TRUE;
255     return 0;
256 }
257
258
259 typedef struct
260 {
261     int flag;
262     const char *msg;
263 } error_msg_t;
264
265 static const error_msg_t cert_errors[] =
266 {
267     { GNUTLS_CERT_INVALID,
268         "Certificate could not be verified" },
269     { GNUTLS_CERT_REVOKED,
270         "Certificate was revoked" },
271     { GNUTLS_CERT_SIGNER_NOT_FOUND,
272         "Certificate's signer was not found" },
273     { GNUTLS_CERT_SIGNER_NOT_CA,
274         "Certificate's signer is not a CA" },
275     { GNUTLS_CERT_INSECURE_ALGORITHM,
276         "Insecure certificate signature algorithm" },
277     { 0, NULL }
278 };
279
280
281 static int
282 gnutls_HandshakeAndValidate( tls_session_t *session )
283 {
284     int val = gnutls_ContinueHandshake( session );
285     if( val )
286         return val;
287
288     tls_session_sys_t *p_sys = (tls_session_sys_t *)(session->p_sys);
289
290     /* certificates chain verification */
291     unsigned status;
292     val = gnutls_certificate_verify_peers2( p_sys->session, &status );
293
294     if( val )
295     {
296         msg_Err( session, "Certificate verification failed: %s",
297                  gnutls_strerror( val ) );
298         goto error;
299     }
300
301     if( status )
302     {
303         msg_Err( session, "TLS session: access denied" );
304         for( const error_msg_t *e = cert_errors; e->flag; e++ )
305         {
306             if( status & e->flag )
307             {
308                 msg_Err( session, e->msg );
309                 status &= ~e->flag;
310             }
311         }
312
313         if( status )
314             msg_Err( session,
315                      "unknown certificate error (you found a bug in VLC)" );
316
317         goto error;
318     }
319
320     /* certificate (host)name verification */
321     const gnutls_datum *data = gnutls_certificate_get_peers( p_sys->session,
322                                                              &(size_t){ 0 } );
323     if( data == NULL )
324     {
325         msg_Err( session, "Peer certificate not available" );
326         goto error;
327     }
328
329     gnutls_x509_crt cert;
330     val = gnutls_x509_crt_init( &cert );
331     if( val )
332     {
333         msg_Err( session, "x509 fatal error: %s", gnutls_strerror( val ) );
334         goto error;
335     }
336
337     val = gnutls_x509_crt_import( cert, data, GNUTLS_X509_FMT_DER );
338     if( val )
339     {
340         msg_Err( session, "Certificate import error: %s",
341                  gnutls_strerror( val ) );
342         gnutls_x509_crt_deinit( cert );
343         goto crt_error;
344     }
345
346     if( p_sys->psz_hostname != NULL )
347     {
348         if ( !gnutls_x509_crt_check_hostname( cert, p_sys->psz_hostname ) )
349         {
350             msg_Err( session, "Certificate does not match \"%s\"",
351                      p_sys->psz_hostname );
352             goto crt_error;
353         }
354     }
355     else
356         msg_Warn( session, "Certificate and hostname were not verified" );
357
358     if( gnutls_x509_crt_get_expiration_time( cert ) < time( NULL ) )
359     {
360         msg_Err( session, "Certificate expired" );
361         goto crt_error;
362     }
363
364     if( gnutls_x509_crt_get_activation_time( cert ) > time ( NULL ) )
365     {
366         msg_Err( session, "Certificate not yet valid" );
367         goto crt_error;
368     }
369
370     gnutls_x509_crt_deinit( cert );
371     msg_Dbg( session, "TLS/x509 certificate verified" );
372     return 0;
373
374 crt_error:
375     gnutls_x509_crt_deinit( cert );
376 error:
377     session->pf_close( session );
378     return -1;
379 }
380
381 /**
382  * Starts negociation of a TLS session.
383  *
384  * @param fd stream socket already connected with the peer.
385  * @param psz_hostname if not NULL, hostname to mention as a Server Name.
386  *
387  * @return -1 on error (you need not and must not call tls_SessionClose),
388  * 0 on succesful handshake completion, 1 if more would-be blocking recv is
389  * needed, 2 if more would-be blocking send is required.
390  */
391 static int
392 gnutls_BeginHandshake( tls_session_t *p_session, int fd,
393                        const char *psz_hostname )
394 {
395     tls_session_sys_t *p_sys;
396
397     p_sys = (tls_session_sys_t *)(p_session->p_sys);
398
399     gnutls_transport_set_ptr (p_sys->session, (gnutls_transport_ptr)(intptr_t)fd);
400
401     if( psz_hostname != NULL )
402     {
403         gnutls_server_name_set( p_sys->session, GNUTLS_NAME_DNS, psz_hostname,
404                                 strlen( psz_hostname ) );
405         if( get_Bool( p_session, "tls-check-hostname" ) )
406         {
407             p_sys->psz_hostname = strdup( psz_hostname );
408             if( p_sys->psz_hostname == NULL )
409             {
410                 p_session->pf_close( p_session );
411                 return -1;
412             }
413         }
414     }
415
416     return p_session->pf_handshake2( p_session );
417 }
418
419 /**
420  * Terminates TLS session and releases session data.
421  * You still have to close the socket yourself.
422  */
423 static void
424 gnutls_SessionClose( tls_session_t *p_session )
425 {
426     tls_session_sys_t *p_sys;
427
428     p_sys = (tls_session_sys_t *)(p_session->p_sys);
429
430     if( p_sys->b_handshaked == VLC_TRUE )
431         gnutls_bye( p_sys->session, GNUTLS_SHUT_WR );
432     gnutls_deinit( p_sys->session );
433
434     if( p_sys->psz_hostname != NULL )
435         free( p_sys->psz_hostname );
436
437     vlc_object_detach( p_session );
438     vlc_object_destroy( p_session );
439
440     free( p_sys );
441 }
442
443 static void
444 gnutls_ClientDelete( tls_session_t *p_session )
445 {
446     /* On the client-side, credentials are re-allocated per session */
447     gnutls_certificate_credentials x509_cred =
448                         ((tls_client_sys_t *)(p_session->p_sys))->x509_cred;
449
450     gnutls_SessionClose( p_session );
451
452     /* credentials must be free'd *after* gnutls_deinit() */
453     gnutls_certificate_free_credentials( x509_cred );
454 }
455
456
457 static int
458 gnutls_Addx509File( vlc_object_t *p_this,
459                     gnutls_certificate_credentials cred,
460                     const char *psz_path, vlc_bool_t b_priv );
461
462 static int
463 gnutls_Addx509Directory( vlc_object_t *p_this,
464                          gnutls_certificate_credentials cred,
465                          const char *psz_dirname,
466                          vlc_bool_t b_priv )
467 {
468     DIR* dir;
469     const char *psz_dirent;
470
471     if( *psz_dirname == '\0' )
472         psz_dirname = ".";
473
474     dir = utf8_opendir( psz_dirname );
475     if( dir == NULL )
476     {
477         msg_Warn( p_this, "cannot open directory (%s): %s", psz_dirname,
478                   strerror( errno ) );
479         return VLC_EGENERIC;
480     }
481 #ifdef S_ISLNK
482     else
483     {
484         struct stat st1, st2;
485         int fd = dirfd( dir );
486
487         /*
488          * Gets stats for the directory path, checks that it is not a
489          * symbolic link (to avoid possibly infinite recursion), and verifies
490          * that the inode is still the same, to avoid TOCTOU race condition.
491          */
492         if( ( fd == -1)
493          || fstat( fd, &st1 ) || utf8_lstat( psz_dirname, &st2 )
494          || S_ISLNK( st2.st_mode ) || ( st1.st_ino != st2.st_ino ) )
495         {
496             closedir( dir );
497             return VLC_EGENERIC;
498         }
499     }
500 #endif
501
502     while( ( psz_dirent = utf8_readdir( dir ) ) != NULL )
503     {
504         char *psz_filename;
505         int check;
506
507         if( (psz_dirent == NULL)
508          || ( strcmp( ".", psz_dirent ) == 0 )
509          || ( strcmp( "..", psz_dirent ) == 0 ) )
510             continue;
511
512         check = asprintf( &psz_filename, "%s/%s", psz_dirname, psz_dirent );
513         free( psz_dirent );
514         if( check == -1 )
515             continue;
516
517         gnutls_Addx509File( p_this, cred, psz_filename, b_priv );
518         free( psz_filename );
519     }
520
521     closedir( dir );
522     return VLC_SUCCESS;
523 }
524
525
526 static int
527 gnutls_Addx509File( vlc_object_t *p_this,
528                     gnutls_certificate_credentials cred,
529                     const char *psz_path, vlc_bool_t b_priv )
530 {
531     struct stat st;
532
533     if( utf8_stat( psz_path, &st ) == 0 )
534     {
535         if( S_ISREG( st.st_mode ) )
536         {
537             char *psz_localname = ToLocale( psz_path );
538             int i = b_priv
539                     ? gnutls_certificate_set_x509_key_file( cred,
540                     psz_localname,  psz_localname, GNUTLS_X509_FMT_PEM )
541                 : gnutls_certificate_set_x509_trust_file( cred,
542                         psz_localname, GNUTLS_X509_FMT_PEM );
543             LocaleFree( psz_localname );
544
545             if( i < 0 )
546             {
547                 msg_Warn( p_this, "cannot add x509 credentials (%s): %s",
548                           psz_path, gnutls_strerror( i ) );
549                 return VLC_EGENERIC;
550             }
551             else
552             {
553                 msg_Dbg( p_this, "added x509 credentials (%s)",
554                          psz_path );
555                 return VLC_SUCCESS;
556             }
557         }
558         else if( S_ISDIR( st.st_mode ) )
559         {
560             msg_Dbg( p_this,
561                      "looking recursively for x509 credentials in %s",
562                      psz_path );
563             return gnutls_Addx509Directory( p_this, cred, psz_path, b_priv);
564         }
565     }
566     else
567         msg_Warn( p_this, "cannot add x509 credentials (%s): %s",
568                   psz_path, strerror( errno ) );
569     return VLC_EGENERIC;
570 }
571
572
573 /**
574  * Initializes a client-side TLS session.
575  */
576 static tls_session_t *
577 gnutls_ClientCreate( tls_t *p_tls )
578 {
579     tls_session_t *p_session = NULL;
580     tls_client_sys_t *p_sys = NULL;
581     int i_val;
582     const int cert_type_priority[3] =
583     {
584         GNUTLS_CRT_X509,
585         0
586     };
587
588     p_sys = (tls_client_sys_t *)malloc( sizeof(struct tls_client_sys_t) );
589     if( p_sys == NULL )
590         return NULL;
591
592     p_session = (struct tls_session_t *)vlc_object_create ( p_tls, sizeof(struct tls_session_t) );
593     if( p_session == NULL )
594     {
595         free( p_sys );
596         return NULL;
597     }
598
599     p_session->p_sys = p_sys;
600     p_session->sock.p_sys = p_session;
601     p_session->sock.pf_send = gnutls_Send;
602     p_session->sock.pf_recv = gnutls_Recv;
603     p_session->pf_handshake = gnutls_BeginHandshake;
604     p_session->pf_close = gnutls_ClientDelete;
605
606     p_sys->session.b_handshaked = VLC_FALSE;
607     p_sys->session.psz_hostname = NULL;
608
609     vlc_object_attach( p_session, p_tls );
610
611     i_val = gnutls_certificate_allocate_credentials( &p_sys->x509_cred );
612     if( i_val != 0 )
613     {
614         msg_Err( p_tls, "cannot allocate X509 credentials: %s",
615                  gnutls_strerror( i_val ) );
616         goto error;
617     }
618
619     if( get_Bool( p_tls, "tls-check-cert" ) )
620     {
621         char *psz_path;
622
623         if( asprintf( &psz_path, "%s/"CONFIG_DIR"/ssl/certs",
624                       p_tls->p_libvlc->psz_homedir ) != -1 )
625         {
626             gnutls_Addx509Directory( (vlc_object_t *)p_session,
627                                      p_sys->x509_cred, psz_path, VLC_FALSE );
628             free( psz_path );
629         }
630
631         if( asprintf( &psz_path, "%s/ca-certificates.crt",
632             config_GetDataDir ( (vlc_object_t *)p_session) ) != -1 )
633         {
634             gnutls_Addx509File( (vlc_object_t *)p_session,
635                                 p_sys->x509_cred, psz_path, VLC_FALSE );
636             free( psz_path );
637         }
638         p_session->pf_handshake2 = gnutls_HandshakeAndValidate;
639     }
640     else
641         p_session->pf_handshake2 = gnutls_ContinueHandshake;
642
643     {
644         char *psz_path;
645
646         if( asprintf( &psz_path, "%s/"CONFIG_DIR"/ssl/private",
647                       p_tls->p_libvlc->psz_homedir ) == -1 )
648         {
649             gnutls_certificate_free_credentials( p_sys->x509_cred );
650             goto error;
651         }
652
653         gnutls_Addx509Directory( (vlc_object_t *)p_session, p_sys->x509_cred,
654                                  psz_path, VLC_TRUE );
655
656         free( psz_path );
657     }
658
659     i_val = gnutls_init( &p_sys->session.session, GNUTLS_CLIENT );
660     if( i_val != 0 )
661     {
662         msg_Err( p_tls, "cannot initialize TLS session: %s",
663                  gnutls_strerror( i_val ) );
664         gnutls_certificate_free_credentials( p_sys->x509_cred );
665         goto error;
666     }
667
668     i_val = gnutls_set_default_priority( p_sys->session.session );
669     if( i_val < 0 )
670     {
671         msg_Err( p_tls, "cannot set ciphers priorities: %s",
672                  gnutls_strerror( i_val ) );
673         gnutls_deinit( p_sys->session.session );
674         gnutls_certificate_free_credentials( p_sys->x509_cred );
675         goto error;
676     }
677
678     i_val = gnutls_certificate_type_set_priority( p_sys->session.session,
679                                                   cert_type_priority );
680     if( i_val < 0 )
681     {
682         msg_Err( p_tls, "cannot set certificate type priorities: %s",
683                  gnutls_strerror( i_val ) );
684         gnutls_deinit( p_sys->session.session );
685         gnutls_certificate_free_credentials( p_sys->x509_cred );
686         goto error;
687     }
688
689     i_val = gnutls_credentials_set( p_sys->session.session,
690                                     GNUTLS_CRD_CERTIFICATE,
691                                     p_sys->x509_cred );
692     if( i_val < 0 )
693     {
694         msg_Err( p_tls, "cannot set TLS session credentials: %s",
695                  gnutls_strerror( i_val ) );
696         gnutls_deinit( p_sys->session.session );
697         gnutls_certificate_free_credentials( p_sys->x509_cred );
698         goto error;
699     }
700
701     return p_session;
702
703 error:
704     vlc_object_detach( p_session );
705     vlc_object_destroy( p_session );
706     free( p_sys );
707
708     return NULL;
709 }
710
711
712 /**
713  * TLS session resumption callbacks (server-side)
714  */
715 static int cb_store( void *p_server, gnutls_datum key, gnutls_datum data )
716 {
717     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
718
719     if( ( p_sys->i_cache_size == 0 )
720      || ( key.size > MAX_SESSION_ID )
721      || ( data.size > MAX_SESSION_DATA ) )
722         return -1;
723
724     vlc_mutex_lock( &p_sys->cache_lock );
725
726     memcpy( p_sys->p_store->id, key.data, key.size);
727     memcpy( p_sys->p_store->data, data.data, data.size );
728     p_sys->p_store->i_idlen = key.size;
729     p_sys->p_store->i_datalen = data.size;
730
731     p_sys->p_store++;
732     if( ( p_sys->p_store - p_sys->p_cache ) == p_sys->i_cache_size )
733         p_sys->p_store = p_sys->p_cache;
734
735     vlc_mutex_unlock( &p_sys->cache_lock );
736
737     return 0;
738 }
739
740
741 static const gnutls_datum err_datum = { NULL, 0 };
742
743 static gnutls_datum cb_fetch( void *p_server, gnutls_datum key )
744 {
745     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
746     saved_session_t *p_session, *p_end;
747
748     p_session = p_sys->p_cache;
749     p_end = p_session + p_sys->i_cache_size;
750
751     vlc_mutex_lock( &p_sys->cache_lock );
752
753     while( p_session < p_end )
754     {
755         if( ( p_session->i_idlen == key.size )
756          && !memcmp( p_session->id, key.data, key.size ) )
757         {
758             gnutls_datum data;
759
760             data.size = p_session->i_datalen;
761
762             data.data = gnutls_malloc( data.size );
763             if( data.data == NULL )
764             {
765                 vlc_mutex_unlock( &p_sys->cache_lock );
766                 return err_datum;
767             }
768
769             memcpy( data.data, p_session->data, data.size );
770             vlc_mutex_unlock( &p_sys->cache_lock );
771             return data;
772         }
773         p_session++;
774     }
775
776     vlc_mutex_unlock( &p_sys->cache_lock );
777
778     return err_datum;
779 }
780
781
782 static int cb_delete( void *p_server, gnutls_datum key )
783 {
784     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
785     saved_session_t *p_session, *p_end;
786
787     p_session = p_sys->p_cache;
788     p_end = p_session + p_sys->i_cache_size;
789
790     vlc_mutex_lock( &p_sys->cache_lock );
791
792     while( p_session < p_end )
793     {
794         if( ( p_session->i_idlen == key.size )
795          && !memcmp( p_session->id, key.data, key.size ) )
796         {
797             p_session->i_datalen = p_session->i_idlen = 0;
798             vlc_mutex_unlock( &p_sys->cache_lock );
799             return 0;
800         }
801         p_session++;
802     }
803
804     vlc_mutex_unlock( &p_sys->cache_lock );
805
806     return -1;
807 }
808
809
810 /**
811  * Initializes a server-side TLS session.
812  */
813 static tls_session_t *
814 gnutls_ServerSessionPrepare( tls_server_t *p_server )
815 {
816     tls_session_t *p_session;
817     tls_server_sys_t *p_server_sys;
818     gnutls_session session;
819     int i_val;
820
821     p_session = vlc_object_create( p_server, sizeof (struct tls_session_t) );
822     if( p_session == NULL )
823         return NULL;
824
825     p_session->p_sys = malloc( sizeof(struct tls_session_sys_t) );
826     if( p_session->p_sys == NULL )
827     {
828         vlc_object_destroy( p_session );
829         return NULL;
830     }
831
832     vlc_object_attach( p_session, p_server );
833
834     p_server_sys = (tls_server_sys_t *)p_server->p_sys;
835     p_session->sock.p_sys = p_session;
836     p_session->sock.pf_send = gnutls_Send;
837     p_session->sock.pf_recv = gnutls_Recv;
838     p_session->pf_handshake = gnutls_BeginHandshake;
839     p_session->pf_handshake2 = p_server_sys->pf_handshake2;
840     p_session->pf_close = gnutls_SessionClose;
841
842     ((tls_session_sys_t *)p_session->p_sys)->b_handshaked = VLC_FALSE;
843     ((tls_session_sys_t *)p_session->p_sys)->psz_hostname = NULL;
844
845     i_val = gnutls_init( &session, GNUTLS_SERVER );
846     if( i_val != 0 )
847     {
848         msg_Err( p_server, "cannot initialize TLS session: %s",
849                  gnutls_strerror( i_val ) );
850         goto error;
851     }
852
853     ((tls_session_sys_t *)p_session->p_sys)->session = session;
854
855     i_val = gnutls_set_default_priority( session );
856     if( i_val < 0 )
857     {
858         msg_Err( p_server, "cannot set ciphers priorities: %s",
859                  gnutls_strerror( i_val ) );
860         gnutls_deinit( session );
861         goto error;
862     }
863
864     i_val = gnutls_credentials_set( session, GNUTLS_CRD_CERTIFICATE,
865                                     p_server_sys->x509_cred );
866     if( i_val < 0 )
867     {
868         msg_Err( p_server, "cannot set TLS session credentials: %s",
869                  gnutls_strerror( i_val ) );
870         gnutls_deinit( session );
871         goto error;
872     }
873
874     if( p_session->pf_handshake2 == gnutls_HandshakeAndValidate )
875         gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUIRE );
876
877     gnutls_dh_set_prime_bits( session, get_Int( p_server, "gnutls-dh-bits" ) );
878
879     /* Session resumption support */
880     gnutls_db_set_cache_expiration( session, get_Int( p_server,
881                                     "gnutls-cache-expiration" ) );
882     gnutls_db_set_retrieve_function( session, cb_fetch );
883     gnutls_db_set_remove_function( session, cb_delete );
884     gnutls_db_set_store_function( session, cb_store );
885     gnutls_db_set_ptr( session, p_server );
886
887     return p_session;
888
889 error:
890     free( p_session->p_sys );
891     vlc_object_detach( p_session );
892     vlc_object_destroy( p_session );
893     return NULL;
894 }
895
896
897 /**
898  * Releases data allocated with tls_ServerCreate().
899  */
900 static void
901 gnutls_ServerDelete( tls_server_t *p_server )
902 {
903     tls_server_sys_t *p_sys;
904     p_sys = (tls_server_sys_t *)p_server->p_sys;
905
906     vlc_mutex_destroy( &p_sys->cache_lock );
907     free( p_sys->p_cache );
908
909     vlc_object_detach( p_server );
910     vlc_object_destroy( p_server );
911
912     /* all sessions depending on the server are now deinitialized */
913     gnutls_certificate_free_credentials( p_sys->x509_cred );
914     gnutls_dh_params_deinit( p_sys->dh_params );
915     free( p_sys );
916 }
917
918
919 /**
920  * Adds one or more certificate authorities.
921  *
922  * @param psz_ca_path (Unicode) path to an x509 certificates list.
923  *
924  * @return -1 on error, 0 on success.
925  *****************************************************************************/
926 static int
927 gnutls_ServerAddCA( tls_server_t *p_server, const char *psz_ca_path )
928 {
929     tls_server_sys_t *p_sys;
930     char *psz_local_path;
931     int val;
932
933     p_sys = (tls_server_sys_t *)(p_server->p_sys);
934
935     psz_local_path = ToLocale( psz_ca_path );
936     val = gnutls_certificate_set_x509_trust_file( p_sys->x509_cred,
937                                                   psz_local_path,
938                                                   GNUTLS_X509_FMT_PEM );
939     LocaleFree( psz_local_path );
940     if( val < 0 )
941     {
942         msg_Err( p_server, "cannot add trusted CA (%s): %s", psz_ca_path,
943                  gnutls_strerror( val ) );
944         return VLC_EGENERIC;
945     }
946     msg_Dbg( p_server, " %d trusted CA added (%s)", val, psz_ca_path );
947
948     /* enables peer's certificate verification */
949     p_sys->pf_handshake2 = gnutls_HandshakeAndValidate;
950
951     return VLC_SUCCESS;
952 }
953
954
955 /**
956  * Adds a certificates revocation list to be sent to TLS clients.
957  *
958  * @param psz_crl_path (Unicode) path of the CRL file.
959  *
960  * @return -1 on error, 0 on success.
961  */
962 static int
963 gnutls_ServerAddCRL( tls_server_t *p_server, const char *psz_crl_path )
964 {
965     int val;
966     char *psz_local_path = ToLocale( psz_crl_path );
967
968     val = gnutls_certificate_set_x509_crl_file( ((tls_server_sys_t *)
969                                                 (p_server->p_sys))->x509_cred,
970                                                 psz_local_path,
971                                                 GNUTLS_X509_FMT_PEM );
972     LocaleFree( psz_crl_path );
973     if( val < 0 )
974     {
975         msg_Err( p_server, "cannot add CRL (%s): %s", psz_crl_path,
976                  gnutls_strerror( val ) );
977         return VLC_EGENERIC;
978     }
979     msg_Dbg( p_server, "%d CRL added (%s)", val, psz_crl_path );
980     return VLC_SUCCESS;
981 }
982
983
984 /**
985  * Allocates a whole server's TLS credentials.
986  *
987  * @return NULL on error.
988  */
989 static tls_server_t *
990 gnutls_ServerCreate( tls_t *p_tls, const char *psz_cert_path,
991                      const char *psz_key_path )
992 {
993     tls_server_t *p_server;
994     tls_server_sys_t *p_sys;
995     char *psz_local_key, *psz_local_cert;
996     int val;
997
998     msg_Dbg( p_tls, "creating TLS server" );
999
1000     p_sys = (tls_server_sys_t *)malloc( sizeof(struct tls_server_sys_t) );
1001     if( p_sys == NULL )
1002         return NULL;
1003
1004     p_sys->i_cache_size = get_Int( p_tls, "gnutls-cache-size" );
1005     p_sys->p_cache = (struct saved_session_t *)calloc( p_sys->i_cache_size,
1006                                            sizeof( struct saved_session_t ) );
1007     if( p_sys->p_cache == NULL )
1008     {
1009         free( p_sys );
1010         return NULL;
1011     }
1012     p_sys->p_store = p_sys->p_cache;
1013
1014     p_server = vlc_object_create( p_tls, sizeof(struct tls_server_t) );
1015     if( p_server == NULL )
1016     {
1017         free( p_sys->p_cache );
1018         free( p_sys );
1019         return NULL;
1020     }
1021
1022     vlc_object_attach( p_server, p_tls );
1023
1024     p_server->p_sys = p_sys;
1025     p_server->pf_delete = gnutls_ServerDelete;
1026     p_server->pf_add_CA = gnutls_ServerAddCA;
1027     p_server->pf_add_CRL = gnutls_ServerAddCRL;
1028     p_server->pf_session_prepare = gnutls_ServerSessionPrepare;
1029
1030     /* No certificate validation by default */
1031     p_sys->pf_handshake2 = gnutls_ContinueHandshake;
1032
1033     vlc_mutex_init( p_server, &p_sys->cache_lock );
1034
1035     /* Sets server's credentials */
1036     val = gnutls_certificate_allocate_credentials( &p_sys->x509_cred );
1037     if( val != 0 )
1038     {
1039         msg_Err( p_server, "cannot allocate X509 credentials: %s",
1040                  gnutls_strerror( val ) );
1041         goto error;
1042     }
1043
1044     psz_local_cert = ToLocale( psz_cert_path );
1045     psz_local_key = ToLocale( psz_key_path );
1046     val = gnutls_certificate_set_x509_key_file( p_sys->x509_cred,
1047                                                 psz_local_cert, psz_local_key,
1048                                                 GNUTLS_X509_FMT_PEM );
1049     LocaleFree( psz_cert_path );
1050     LocaleFree( psz_key_path );
1051     if( val < 0 )
1052     {
1053         msg_Err( p_server, "cannot set certificate chain or private key: %s",
1054                  gnutls_strerror( val ) );
1055         gnutls_certificate_free_credentials( p_sys->x509_cred );
1056         goto error;
1057     }
1058
1059     /* FIXME:
1060      * - regenerate these regularly
1061      * - support other ciper suites
1062      */
1063     val = gnutls_dh_params_init( &p_sys->dh_params );
1064     if( val >= 0 )
1065     {
1066         msg_Dbg( p_server, "computing Diffie Hellman ciphers parameters" );
1067         val = gnutls_dh_params_generate2( p_sys->dh_params,
1068                                           get_Int( p_tls, "gnutls-dh-bits" ) );
1069     }
1070     if( val < 0 )
1071     {
1072         msg_Err( p_server, "cannot initialize DH cipher suites: %s",
1073                  gnutls_strerror( val ) );
1074         gnutls_certificate_free_credentials( p_sys->x509_cred );
1075         goto error;
1076     }
1077     msg_Dbg( p_server, "ciphers parameters computed" );
1078
1079     gnutls_certificate_set_dh_params( p_sys->x509_cred, p_sys->dh_params);
1080
1081     return p_server;
1082
1083 error:
1084     vlc_mutex_destroy( &p_sys->cache_lock );
1085     vlc_object_detach( p_server );
1086     vlc_object_destroy( p_server );
1087     free( p_sys );
1088     return NULL;
1089 }
1090
1091
1092 #ifdef LIBVLC_USE_PTHREAD
1093 GCRY_THREAD_OPTION_PTHREAD_IMPL;
1094 # define gcry_threads_vlc gcry_threads_pthread
1095 #else
1096 /**
1097  * gcrypt thread option VLC implementation
1098  */
1099
1100 # define NEED_THREAD_CONTEXT 1
1101 static vlc_object_t *__p_gcry_data;
1102
1103 static int gcry_vlc_mutex_init( void **p_sys )
1104 {
1105     int i_val;
1106     vlc_mutex_t *p_lock = (vlc_mutex_t *)malloc( sizeof( vlc_mutex_t ) );
1107
1108     if( p_lock == NULL)
1109         return ENOMEM;
1110
1111     i_val = vlc_mutex_init( __p_gcry_data, p_lock );
1112     if( i_val )
1113         free( p_lock );
1114     else
1115         *p_sys = p_lock;
1116     return i_val;
1117 }
1118
1119 static int gcry_vlc_mutex_destroy( void **p_sys )
1120 {
1121     int i_val;
1122     vlc_mutex_t *p_lock = (vlc_mutex_t *)*p_sys;
1123
1124     i_val = vlc_mutex_destroy( p_lock );
1125     free( p_lock );
1126     return i_val;
1127 }
1128
1129 static int gcry_vlc_mutex_lock( void **p_sys )
1130 {
1131     return vlc_mutex_lock( (vlc_mutex_t *)*p_sys );
1132 }
1133
1134 static int gcry_vlc_mutex_unlock( void **lock )
1135 {
1136     return vlc_mutex_unlock( (vlc_mutex_t *)*lock );
1137 }
1138
1139 static struct gcry_thread_cbs gcry_threads_vlc =
1140 {
1141     GCRY_THREAD_OPTION_USER,
1142     NULL,
1143     gcry_vlc_mutex_init,
1144     gcry_vlc_mutex_destroy,
1145     gcry_vlc_mutex_lock,
1146     gcry_vlc_mutex_unlock
1147 };
1148 #endif
1149
1150
1151 /*****************************************************************************
1152  * Module initialization
1153  *****************************************************************************/
1154 static int
1155 Open( vlc_object_t *p_this )
1156 {
1157     tls_t *p_tls = (tls_t *)p_this;
1158
1159     vlc_value_t lock, count;
1160
1161     var_Create( p_this->p_libvlc_global, "gnutls_mutex", VLC_VAR_MUTEX );
1162     var_Get( p_this->p_libvlc_global, "gnutls_mutex", &lock );
1163     vlc_mutex_lock( lock.p_address );
1164
1165     /* Initialize GnuTLS only once */
1166     var_Create( p_this->p_libvlc_global, "gnutls_count", VLC_VAR_INTEGER );
1167     var_Get( p_this->p_libvlc_global, "gnutls_count", &count);
1168
1169     if( count.i_int == 0)
1170     {
1171 #ifdef NEED_THREAD_CONTEXT
1172         __p_gcry_data = VLC_OBJECT( p_this->p_libvlc );
1173 #endif
1174
1175         gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_vlc);
1176         if( gnutls_global_init( ) )
1177         {
1178             msg_Warn( p_this, "cannot initialize GnuTLS" );
1179             vlc_mutex_unlock( lock.p_address );
1180             return VLC_EGENERIC;
1181         }
1182
1183         const char *psz_version = gnutls_check_version( "1.2.9" );
1184         if( psz_version == NULL )
1185         {
1186             gnutls_global_deinit( );
1187             vlc_mutex_unlock( lock.p_address );
1188             msg_Err( p_this, "unsupported GnuTLS version" );
1189             return VLC_EGENERIC;
1190         }
1191         msg_Dbg( p_this, "GnuTLS v%s initialized", psz_version );
1192     }
1193
1194     count.i_int++;
1195     var_Set( p_this->p_libvlc_global, "gnutls_count", count);
1196     vlc_mutex_unlock( lock.p_address );
1197
1198     p_tls->pf_server_create = gnutls_ServerCreate;
1199     p_tls->pf_client_create = gnutls_ClientCreate;
1200     return VLC_SUCCESS;
1201 }
1202
1203
1204 /*****************************************************************************
1205  * Module deinitialization
1206  *****************************************************************************/
1207 static void
1208 Close( vlc_object_t *p_this )
1209 {
1210     /*tls_t *p_tls = (tls_t *)p_this;
1211     tls_sys_t *p_sys = (tls_sys_t *)(p_this->p_sys);*/
1212
1213     vlc_value_t lock, count;
1214
1215     var_Create( p_this->p_libvlc_global, "gnutls_mutex", VLC_VAR_MUTEX );
1216     var_Get( p_this->p_libvlc_global, "gnutls_mutex", &lock );
1217     vlc_mutex_lock( lock.p_address );
1218
1219     var_Create( p_this->p_libvlc_global, "gnutls_count", VLC_VAR_INTEGER );
1220     var_Get( p_this->p_libvlc_global, "gnutls_count", &count);
1221     count.i_int--;
1222     var_Set( p_this->p_libvlc_global, "gnutls_count", count);
1223
1224     if( count.i_int == 0 )
1225     {
1226         gnutls_global_deinit( );
1227         msg_Dbg( p_this, "GnuTLS deinitialized" );
1228     }
1229
1230     vlc_mutex_unlock( lock.p_address );
1231 }