Commit 4dcdc8c4 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

Big cleanup of TLS interface

parent 378456a6
...@@ -344,10 +344,6 @@ typedef int (*httpd_handler_callback_t)( httpd_handler_sys_t *, httpd_handler_t ...@@ -344,10 +344,6 @@ typedef int (*httpd_handler_callback_t)( httpd_handler_sys_t *, httpd_handler_t
typedef struct httpd_redirect_t httpd_redirect_t; typedef struct httpd_redirect_t httpd_redirect_t;
typedef struct httpd_stream_t httpd_stream_t; typedef struct httpd_stream_t httpd_stream_t;
/* TLS support */
typedef struct tls_server_t tls_server_t;
typedef struct tls_session_t tls_session_t;
/* Hashing */ /* Hashing */
typedef struct md5_s md5_t; typedef struct md5_s md5_t;
......
...@@ -146,8 +146,8 @@ VLC_API int net_SetCSCov( int fd, int sendcov, int recvcov ); ...@@ -146,8 +146,8 @@ VLC_API int net_SetCSCov( int fd, int sendcov, int recvcov );
struct virtual_socket_t struct virtual_socket_t
{ {
void *p_sys; void *p_sys;
int (*pf_recv) ( void *, void *, int ); int (*pf_recv) ( void *, void *, size_t );
int (*pf_send) ( void *, const void *, int ); int (*pf_send) ( void *, const void *, size_t );
}; };
VLC_API ssize_t net_Read( vlc_object_t *p_this, int fd, const v_socket_t *, void *p_data, size_t i_data, bool b_retry ); VLC_API ssize_t net_Read( vlc_object_t *p_this, int fd, const v_socket_t *, void *p_data, size_t i_data, bool b_retry );
......
/***************************************************************************** /*****************************************************************************
* tls.c: Transport Layer Security API * vlc_tls.h: Transport Layer Security API
***************************************************************************** *****************************************************************************
* Copyright (C) 2004-2007 the VideoLAN team * Copyright (C) 2004-2011 Rémi Denis-Courmont
* $Id$ * Copyright (C) 2005-2006 the VideoLAN team
*
* Authors: Rémi Denis-Courmont <rem # videolan.org>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
...@@ -31,52 +29,56 @@ ...@@ -31,52 +29,56 @@
# include <vlc_network.h> # include <vlc_network.h>
typedef struct tls_server_sys_t tls_server_sys_t; typedef struct vlc_tls_sys vlc_tls_sys_t;
struct tls_server_t typedef struct vlc_tls
{ {
VLC_COMMON_MEMBERS VLC_COMMON_MEMBERS
module_t *p_module; union {
tls_server_sys_t *p_sys; module_t *module; /**< Plugin handle (client) */
void (*close) (struct vlc_tls *); /**< Close callback (server) */
} u;
vlc_tls_sys_t *sys;
int (*pf_add_CA) ( tls_server_t *, const char * ); struct virtual_socket_t sock;
int (*pf_add_CRL) ( tls_server_t *, const char * ); int (*handshake) (struct vlc_tls *);
} vlc_tls_t;
tls_session_t * (*pf_open) ( tls_server_t * ); VLC_API vlc_tls_t *vlc_tls_ClientCreate (vlc_object_t *, int fd,
void (*pf_close) ( tls_server_t *, tls_session_t * ); const char *hostname);
}; VLC_API void vlc_tls_ClientDelete (vlc_tls_t *);
typedef struct tls_session_sys_t tls_session_sys_t; /* NOTE: It is assumed that a->sock.p_sys = a */
# define tls_Send( a, b, c ) (((vlc_tls_t *)a)->sock.pf_send (a, b, c))
struct tls_session_t # define tls_Recv( a, b, c ) (((vlc_tls_t *)a)->sock.pf_recv (a, b, c))
{
VLC_COMMON_MEMBERS
module_t *p_module;
tls_session_sys_t *p_sys;
struct virtual_socket_t sock; typedef struct vlc_tls_creds_sys vlc_tls_creds_sys_t;
void (*pf_set_fd) ( tls_session_t *, int );
int (*pf_handshake) ( tls_session_t * );
};
/** TLS (server-side) credentials */
typedef struct vlc_tls_creds
{
VLC_COMMON_MEMBERS
tls_server_t *tls_ServerCreate (vlc_object_t *, const char *, const char *); module_t *module;
void tls_ServerDelete (tls_server_t *); vlc_tls_creds_sys_t *sys;
int tls_ServerAddCA (tls_server_t *srv, const char *path);
int tls_ServerAddCRL (tls_server_t *srv, const char *path);
tls_session_t *tls_ServerSessionCreate (tls_server_t *, int fd); int (*add_CA) (struct vlc_tls_creds *, const char *path);
int tls_ServerSessionHandshake (tls_session_t *); int (*add_CRL) (struct vlc_tls_creds *, const char *path);
void tls_ServerSessionDelete (tls_session_t *);
VLC_API tls_session_t * tls_ClientCreate( vlc_object_t *, int, const char * ); vlc_tls_t *(*open) (struct vlc_tls_creds *, int fd);
VLC_API void tls_ClientDelete( tls_session_t * ); } vlc_tls_creds_t;
/* NOTE: It is assumed that a->sock.p_sys = a */ vlc_tls_creds_t *vlc_tls_ServerCreate (vlc_object_t *,
# define tls_Send( a, b, c ) (((tls_session_t *)a)->sock.pf_send (a, b, c )) const char *cert, const char *key);
void vlc_tls_ServerDelete (vlc_tls_creds_t *);
int vlc_tls_ServerAddCA (vlc_tls_creds_t *srv, const char *path);
int vlc_tls_ServerAddCRL (vlc_tls_creds_t *srv, const char *path);
# define tls_Recv( a, b, c ) (((tls_session_t *)a)->sock.pf_recv (a, b, c )) vlc_tls_t *vlc_tls_ServerSessionCreate (vlc_tls_creds_t *, int fd);
int vlc_tls_ServerSessionHandshake (vlc_tls_t *);
void vlc_tls_ServerSessionDelete (vlc_tls_t *);
#endif #endif
...@@ -142,7 +142,7 @@ struct access_sys_t ...@@ -142,7 +142,7 @@ struct access_sys_t
{ {
int fd; int fd;
bool b_error; bool b_error;
tls_session_t *p_tls; vlc_tls_t *p_tls;
v_socket_t *p_vs; v_socket_t *p_vs;
/* From uri */ /* From uri */
...@@ -1195,7 +1195,7 @@ static int Connect( access_t *p_access, uint64_t i_tell ) ...@@ -1195,7 +1195,7 @@ static int Connect( access_t *p_access, uint64_t i_tell )
} }
/* TLS/SSL handshake */ /* TLS/SSL handshake */
p_sys->p_tls = tls_ClientCreate( VLC_OBJECT(p_access), p_sys->fd, p_sys->p_tls = vlc_tls_ClientCreate( VLC_OBJECT(p_access), p_sys->fd,
p_sys->url.psz_host ); p_sys->url.psz_host );
if( p_sys->p_tls == NULL ) if( p_sys->p_tls == NULL )
{ {
...@@ -1621,7 +1621,7 @@ static void Disconnect( access_t *p_access ) ...@@ -1621,7 +1621,7 @@ static void Disconnect( access_t *p_access )
if( p_sys->p_tls != NULL) if( p_sys->p_tls != NULL)
{ {
tls_ClientDelete( p_sys->p_tls ); vlc_tls_ClientDelete( p_sys->p_tls );
p_sys->p_tls = NULL; p_sys->p_tls = NULL;
p_sys->p_vs = NULL; p_sys->p_vs = NULL;
} }
......
This diff is collapsed.
...@@ -436,8 +436,8 @@ subpicture_Update ...@@ -436,8 +436,8 @@ subpicture_Update
subpicture_region_ChainDelete subpicture_region_ChainDelete
subpicture_region_Delete subpicture_region_Delete
subpicture_region_New subpicture_region_New
tls_ClientCreate vlc_tls_ClientCreate
tls_ClientDelete vlc_tls_ClientDelete
ToCharset ToCharset
ToLocale ToLocale
ToLocaleDup ToLocaleDup
......
...@@ -108,7 +108,7 @@ struct httpd_host_t ...@@ -108,7 +108,7 @@ struct httpd_host_t
httpd_client_t **client; httpd_client_t **client;
/* TLS data */ /* TLS data */
tls_server_t *p_tls; vlc_tls_creds_t *p_tls;
}; };
...@@ -180,7 +180,7 @@ struct httpd_client_t ...@@ -180,7 +180,7 @@ struct httpd_client_t
httpd_message_t answer; /* httpd -> client */ httpd_message_t answer; /* httpd -> client */
/* TLS data */ /* TLS data */
tls_session_t *p_tls; vlc_tls_t *p_tls;
}; };
...@@ -982,7 +982,7 @@ httpd_host_t *httpd_TLSHostNew( vlc_object_t *p_this, const char *psz_hostname, ...@@ -982,7 +982,7 @@ httpd_host_t *httpd_TLSHostNew( vlc_object_t *p_this, const char *psz_hostname,
{ {
httpd_t *httpd; httpd_t *httpd;
httpd_host_t *host; httpd_host_t *host;
tls_server_t *p_tls; vlc_tls_creds_t *p_tls;
char *psz_host; char *psz_host;
int i; int i;
...@@ -1043,20 +1043,20 @@ httpd_host_t *httpd_TLSHostNew( vlc_object_t *p_this, const char *psz_hostname, ...@@ -1043,20 +1043,20 @@ httpd_host_t *httpd_TLSHostNew( vlc_object_t *p_this, const char *psz_hostname,
/* determine TLS configuration */ /* determine TLS configuration */
if ( psz_cert != NULL ) if ( psz_cert != NULL )
{ {
p_tls = tls_ServerCreate( p_this, psz_cert, psz_key ); p_tls = vlc_tls_ServerCreate( p_this, psz_cert, psz_key );
if ( p_tls == NULL ) if ( p_tls == NULL )
{ {
msg_Err( p_this, "TLS initialization error" ); msg_Err( p_this, "TLS initialization error" );
goto error; goto error;
} }
if ( ( psz_ca != NULL) && tls_ServerAddCA( p_tls, psz_ca ) ) if ( ( psz_ca != NULL) && vlc_tls_ServerAddCA( p_tls, psz_ca ) )
{ {
msg_Err( p_this, "TLS CA error" ); msg_Err( p_this, "TLS CA error" );
goto error; goto error;
} }
if ( ( psz_crl != NULL) && tls_ServerAddCRL( p_tls, psz_crl ) ) if ( ( psz_crl != NULL) && vlc_tls_ServerAddCRL( p_tls, psz_crl ) )
{ {
msg_Err( p_this, "TLS CRL error" ); msg_Err( p_this, "TLS CRL error" );
goto error; goto error;
...@@ -1132,7 +1132,7 @@ error: ...@@ -1132,7 +1132,7 @@ error:
} }
if( p_tls != NULL ) if( p_tls != NULL )
tls_ServerDelete( p_tls ); vlc_tls_ServerDelete( p_tls );
return NULL; return NULL;
} }
...@@ -1184,7 +1184,7 @@ void httpd_HostDelete( httpd_host_t *host ) ...@@ -1184,7 +1184,7 @@ void httpd_HostDelete( httpd_host_t *host )
} }
if( host->p_tls != NULL) if( host->p_tls != NULL)
tls_ServerDelete( host->p_tls ); vlc_tls_ServerDelete( host->p_tls );
net_ListenClose( host->fds ); net_ListenClose( host->fds );
free( host->psz_hostname ); free( host->psz_hostname );
...@@ -1429,7 +1429,7 @@ static void httpd_ClientClean( httpd_client_t *cl ) ...@@ -1429,7 +1429,7 @@ static void httpd_ClientClean( httpd_client_t *cl )
if( cl->fd >= 0 ) if( cl->fd >= 0 )
{ {
if( cl->p_tls != NULL ) if( cl->p_tls != NULL )
tls_ServerSessionDelete( cl->p_tls ); vlc_tls_ServerSessionDelete( cl->p_tls );
net_Close( cl->fd ); net_Close( cl->fd );
cl->fd = -1; cl->fd = -1;
} }
...@@ -1441,7 +1441,7 @@ static void httpd_ClientClean( httpd_client_t *cl ) ...@@ -1441,7 +1441,7 @@ static void httpd_ClientClean( httpd_client_t *cl )
cl->p_buffer = NULL; cl->p_buffer = NULL;
} }
static httpd_client_t *httpd_ClientNew( int fd, tls_session_t *p_tls, mtime_t now ) static httpd_client_t *httpd_ClientNew( int fd, vlc_tls_t *p_tls, mtime_t now )
{ {
httpd_client_t *cl = malloc( sizeof( httpd_client_t ) ); httpd_client_t *cl = malloc( sizeof( httpd_client_t ) );
...@@ -1460,7 +1460,7 @@ static httpd_client_t *httpd_ClientNew( int fd, tls_session_t *p_tls, mtime_t no ...@@ -1460,7 +1460,7 @@ static httpd_client_t *httpd_ClientNew( int fd, tls_session_t *p_tls, mtime_t no
static static
ssize_t httpd_NetRecv (httpd_client_t *cl, uint8_t *p, size_t i_len) ssize_t httpd_NetRecv (httpd_client_t *cl, uint8_t *p, size_t i_len)
{ {
tls_session_t *p_tls; vlc_tls_t *p_tls;
ssize_t val; ssize_t val;
p_tls = cl->p_tls; p_tls = cl->p_tls;
...@@ -1474,7 +1474,7 @@ ssize_t httpd_NetRecv (httpd_client_t *cl, uint8_t *p, size_t i_len) ...@@ -1474,7 +1474,7 @@ ssize_t httpd_NetRecv (httpd_client_t *cl, uint8_t *p, size_t i_len)
static static
ssize_t httpd_NetSend (httpd_client_t *cl, const uint8_t *p, size_t i_len) ssize_t httpd_NetSend (httpd_client_t *cl, const uint8_t *p, size_t i_len)
{ {
tls_session_t *p_tls; vlc_tls_t *p_tls;
ssize_t val; ssize_t val;
p_tls = cl->p_tls; p_tls = cl->p_tls;
...@@ -2015,7 +2015,7 @@ static void httpd_ClientSend( httpd_client_t *cl ) ...@@ -2015,7 +2015,7 @@ static void httpd_ClientSend( httpd_client_t *cl )
static void httpd_ClientTlsHsIn( httpd_client_t *cl ) static void httpd_ClientTlsHsIn( httpd_client_t *cl )
{ {
switch( tls_ServerSessionHandshake( cl->p_tls ) ) switch( vlc_tls_ServerSessionHandshake( cl->p_tls ) )
{ {
case 0: case 0:
cl->i_state = HTTPD_CLIENT_RECEIVING; cl->i_state = HTTPD_CLIENT_RECEIVING;
...@@ -2033,7 +2033,7 @@ static void httpd_ClientTlsHsIn( httpd_client_t *cl ) ...@@ -2033,7 +2033,7 @@ static void httpd_ClientTlsHsIn( httpd_client_t *cl )
static void httpd_ClientTlsHsOut( httpd_client_t *cl ) static void httpd_ClientTlsHsOut( httpd_client_t *cl )
{ {
switch( tls_ServerSessionHandshake( cl->p_tls ) ) switch( vlc_tls_ServerSessionHandshake( cl->p_tls ) )
{ {
case 0: case 0:
cl->i_state = HTTPD_CLIENT_RECEIVING; cl->i_state = HTTPD_CLIENT_RECEIVING;
...@@ -2533,12 +2533,12 @@ static void* httpd_HostThread( void *data ) ...@@ -2533,12 +2533,12 @@ static void* httpd_HostThread( void *data )
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
&(int){ 1 }, sizeof(int)); &(int){ 1 }, sizeof(int));
tls_session_t *p_tls; vlc_tls_t *p_tls;
if( host->p_tls != NULL ) if( host->p_tls != NULL )
{ {
p_tls = tls_ServerSessionCreate( host->p_tls, fd ); p_tls = vlc_tls_ServerSessionCreate( host->p_tls, fd );
switch( tls_ServerSessionHandshake( p_tls ) ) switch( vlc_tls_ServerSessionHandshake( p_tls ) )
{ {
case -1: case -1:
msg_Err( host, "Rejecting TLS connection" ); msg_Err( host, "Rejecting TLS connection" );
......
...@@ -46,14 +46,12 @@ ...@@ -46,14 +46,12 @@
* *
* @return NULL on error. * @return NULL on error.
*/ */
tls_server_t * vlc_tls_creds_t *
tls_ServerCreate (vlc_object_t *obj, const char *cert_path, vlc_tls_ServerCreate (vlc_object_t *obj, const char *cert_path,
const char *key_path) const char *key_path)
{ {
tls_server_t *srv; vlc_tls_creds_t *srv = vlc_custom_create (obj, sizeof (*srv), "tls creds");
if (unlikely(srv == NULL))
srv = (tls_server_t *)vlc_custom_create (obj, sizeof (*srv), "tls server");
if (srv == NULL)
return NULL; return NULL;
var_Create (srv, "tls-x509-cert", VLC_VAR_STRING); var_Create (srv, "tls-x509-cert", VLC_VAR_STRING);
...@@ -68,8 +66,8 @@ tls_ServerCreate (vlc_object_t *obj, const char *cert_path, ...@@ -68,8 +66,8 @@ tls_ServerCreate (vlc_object_t *obj, const char *cert_path,
var_SetString (srv, "tls-x509-key", key_path); var_SetString (srv, "tls-x509-key", key_path);
} }
srv->p_module = module_need (srv, "tls server", NULL, false ); srv->module = module_need (srv, "tls server", NULL, false );
if (srv->p_module == NULL) if (srv->module == NULL)
{ {
msg_Err (srv, "TLS server plugin not available"); msg_Err (srv, "TLS server plugin not available");
vlc_object_release (srv); vlc_object_release (srv);
...@@ -82,15 +80,15 @@ tls_ServerCreate (vlc_object_t *obj, const char *cert_path, ...@@ -82,15 +80,15 @@ tls_ServerCreate (vlc_object_t *obj, const char *cert_path,
/** /**
* Releases data allocated with tls_ServerCreate. * Releases data allocated with vlc_tls_ServerCreate().
* @param srv TLS server object to be destroyed, or NULL * @param srv TLS server object to be destroyed, or NULL
*/ */
void tls_ServerDelete (tls_server_t *srv) void vlc_tls_ServerDelete (vlc_tls_creds_t *srv)
{ {
if (srv == NULL) if (srv == NULL)
return; return;
module_unneed (srv, srv->p_module); module_unneed (srv, srv->module);
vlc_object_release (srv); vlc_object_release (srv);
} }
...@@ -99,9 +97,9 @@ void tls_ServerDelete (tls_server_t *srv) ...@@ -99,9 +97,9 @@ void tls_ServerDelete (tls_server_t *srv)
* Adds one or more certificate authorities from a file. * Adds one or more certificate authorities from a file.
* @return -1 on error, 0 on success. * @return -1 on error, 0 on success.
*/ */
int tls_ServerAddCA (tls_server_t *srv, const char *path) int vlc_tls_ServerAddCA (vlc_tls_creds_t *srv, const char *path)
{ {
return srv->pf_add_CA (srv, path); return srv->add_CA (srv, path);
} }
...@@ -109,37 +107,54 @@ int tls_ServerAddCA (tls_server_t *srv, const char *path) ...@@ -109,37 +107,54 @@ int tls_ServerAddCA (tls_server_t *srv, const char *path)
* Adds one or more certificate revocation list from a file. * Adds one or more certificate revocation list from a file.
* @return -1 on error, 0 on success. * @return -1 on error, 0 on success.
*/ */
int tls_ServerAddCRL (tls_server_t *srv, const char *path) int vlc_tls_ServerAddCRL (vlc_tls_creds_t *srv, const char *path)
{ {
return srv->pf_add_CRL (srv, path); return srv->add_CRL (srv, path);
} }
tls_session_t *tls_ServerSessionCreate (tls_server_t *srv, int fd) vlc_tls_t *vlc_tls_ServerSessionCreate (vlc_tls_creds_t *srv, int fd)
{ {
tls_session_t *ses = srv->pf_open (srv); return srv->open (srv, fd);
if (ses != NULL)
ses->pf_set_fd (ses, fd);
return ses;
} }
void tls_ServerSessionDelete (tls_session_t *ses) void vlc_tls_ServerSessionDelete (vlc_tls_t *ses)
{ {
tls_server_t *srv = (tls_server_t *)(ses->p_parent); ses->u.close (ses);
srv->pf_close (srv, ses);
} }
int tls_ServerSessionHandshake (tls_session_t *ses) int vlc_tls_ServerSessionHandshake (vlc_tls_t *ses)
{ {
int val = ses->pf_handshake (ses); int val = ses->handshake (ses);
if (val < 0) if (val < 0)
tls_ServerSessionDelete (ses); vlc_tls_ServerSessionDelete (ses);
return val; return val;
} }
/*** TLS client session ***/
/* TODO: cache certificates for the whole VLC instance lifetime */
static int tls_client_start(void *func, va_list ap)
{
int (*activate) (vlc_tls_t *, int fd, const char *hostname) = func;
vlc_tls_t *session = va_arg (ap, vlc_tls_t *);
int fd = va_arg (ap, int);
const char *hostname = va_arg (ap, const char *);
return activate (session, fd, hostname);
}
static void tls_client_stop(void *func, va_list ap)
{
void (*deactivate) (vlc_tls_t *) = func;
vlc_tls_t *session = va_arg (ap, vlc_tls_t *);
deactivate (session);
}
/** /**
* Allocates a client's TLS credentials and shakes hands through the network. * Allocates a client's TLS credentials and shakes hands through the network.
* This is a blocking network operation. * This is a blocking network operation.
...@@ -150,61 +165,49 @@ int tls_ServerSessionHandshake (tls_session_t *ses) ...@@ -150,61 +165,49 @@ int tls_ServerSessionHandshake (tls_session_t *ses)
* *
* @return NULL on error. * @return NULL on error.
**/ **/
tls_session_t * vlc_tls_t *
tls_ClientCreate (vlc_object_t *obj, int fd, const char *psz_hostname) vlc_tls_ClientCreate (vlc_object_t *obj, int fd, const char *hostname)
{ {
tls_session_t *cl; vlc_tls_t *cl = vlc_custom_create (obj, sizeof (*cl), "tls client");
int val; if (unlikely(cl == NULL))
cl = (tls_session_t *)vlc_custom_create (obj, sizeof (*cl), "tls client");
if (cl == NULL)
return NULL; return NULL;
var_Create (cl, "tls-server-name", VLC_VAR_STRING); cl->u.module = vlc_module_load (cl, "tls client", NULL, false,
if (psz_hostname != NULL) tls_client_start, cl, fd, hostname);
{ if (cl->u.module == NULL)
msg_Dbg (cl, "requested server name: %s", psz_hostname);
var_SetString (cl, "tls-server-name", psz_hostname);
}
else
msg_Dbg (cl, "requested anonymous server");
cl->p_module = module_need (cl, "tls client", NULL, false );
if (cl->p_module == NULL)
{ {
msg_Err (cl, "TLS client plugin not available"); msg_Err (cl, "TLS client plugin not available");
vlc_object_release (cl); vlc_object_release (cl);
return NULL; return NULL;
} }
cl->pf_set_fd (cl, fd); /* TODO: do this directly in the TLS plugin */
int val;
do do
val = cl->pf_handshake (cl); val = cl->handshake (cl);
while (val > 0); while (val > 0);
if (val == 0) if (val != 0)
{ {
msg_Dbg (cl, "TLS client session initialized");
return cl;
}
msg_Err (cl, "TLS client session handshake error"); msg_Err (cl, "TLS client session handshake error");
vlc_module_unload (cl->u.module, tls_client_stop, cl);
module_unneed (cl, cl->p_module);
vlc_object_release (cl); vlc_object_release (cl);
return NULL; return NULL;
}
msg_Dbg (cl, "TLS client session initialized");
return cl;
} }
/** /**
* Releases data allocated with tls_ClientCreate. * Releases data allocated with vlc_tls_ClientCreate().
* It is your job to close the underlying socket. * It is your job to close the underlying socket.
*/ */
void tls_ClientDelete (tls_session_t *cl) void vlc_tls_ClientDelete (vlc_tls_t *cl)
{ {
if (cl == NULL) if (cl == NULL)
return; return;
module_unneed (cl, cl->p_module); vlc_module_unload (cl->u.module, tls_client_stop, cl);
vlc_object_release (cl); vlc_object_release (cl);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment