Commit 583c5c24 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

st: partial update

parent cb30cb28
...@@ -86,18 +86,22 @@ typedef struct { ...@@ -86,18 +86,22 @@ typedef struct {
} vlc_tls_creds_sys_t; } vlc_tls_creds_sys_t;
typedef struct { typedef struct {
vlc_tls_t tls;
SSLContextRef p_context; SSLContextRef p_context;
vlc_tls_creds_sys_t *p_cred; vlc_tls_creds_sys_t *p_cred;
size_t i_send_buffered_bytes; size_t i_send_buffered_bytes;
vlc_tls_t *sock; vlc_tls_t *sock;
vlc_object_t *obj;
bool b_blocking_send; bool b_blocking_send;
bool b_handshaked; bool b_handshaked;
bool b_server_mode; bool b_server_mode;
} vlc_tls_sys_t; } vlc_tls_st_t;
static int st_Error (vlc_tls_t *obj, int val) static int st_Error (vlc_tls_t *obj, int val)
{ {
vlc_tls_st_t *sys = (vlc_tls_st_t *)obj;
switch (val) switch (val)
{ {
case errSSLWouldBlock: case errSSLWouldBlock:
...@@ -106,11 +110,11 @@ static int st_Error (vlc_tls_t *obj, int val) ...@@ -106,11 +110,11 @@ static int st_Error (vlc_tls_t *obj, int val)
case errSSLClosedGraceful: case errSSLClosedGraceful:
case errSSLClosedAbort: case errSSLClosedAbort:
msg_Dbg(obj->obj, "Connection closed with code %d", val); msg_Dbg(sys->obj, "Connection closed with code %d", val);
errno = ECONNRESET; errno = ECONNRESET;
break; break;
default: default:
msg_Err(obj->obj, "Found error %d", val); msg_Err(sys->obj, "Found error %d", val);
errno = ECONNRESET; errno = ECONNRESET;
} }
return -1; return -1;
...@@ -126,7 +130,7 @@ static OSStatus st_SocketReadFunc (SSLConnectionRef connection, ...@@ -126,7 +130,7 @@ static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
size_t *dataLength) { size_t *dataLength) {
vlc_tls_t *session = (vlc_tls_t *)connection; vlc_tls_t *session = (vlc_tls_t *)connection;
vlc_tls_sys_t *sys = session->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
struct iovec iov = { struct iovec iov = {
.iov_base = data, .iov_base = data,
.iov_len = *dataLength, .iov_len = *dataLength,
...@@ -137,7 +141,7 @@ static OSStatus st_SocketReadFunc (SSLConnectionRef connection, ...@@ -137,7 +141,7 @@ static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
ssize_t val = sys->sock->readv(sys->sock, &iov, 1); ssize_t val = sys->sock->readv(sys->sock, &iov, 1);
if (val <= 0) { if (val <= 0) {
if (val == 0) { if (val == 0) {
msg_Dbg(session->obj, "found eof"); msg_Dbg(sys->obj, "found eof");
retValue = errSSLClosedGraceful; retValue = errSSLClosedGraceful;
} else { /* do the switch */ } else { /* do the switch */
switch (errno) { switch (errno) {
...@@ -153,7 +157,7 @@ static OSStatus st_SocketReadFunc (SSLConnectionRef connection, ...@@ -153,7 +157,7 @@ static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
sys->b_blocking_send = false; sys->b_blocking_send = false;
break; break;
default: default:
msg_Err(session->obj, "try to read %zu bytes, " msg_Err(sys->obj, "try to read %zu bytes, "
"got error %d", iov.iov_len, errno); "got error %d", iov.iov_len, errno);
retValue = ioErr; retValue = ioErr;
break; break;
...@@ -180,7 +184,7 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection, ...@@ -180,7 +184,7 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
size_t *dataLength) { size_t *dataLength) {
vlc_tls_t *session = (vlc_tls_t *)connection; vlc_tls_t *session = (vlc_tls_t *)connection;
vlc_tls_sys_t *sys = session->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
struct iovec iov = { struct iovec iov = {
.iov_base = (void *)data, .iov_base = (void *)data,
.iov_len = *dataLength, .iov_len = *dataLength,
...@@ -202,7 +206,7 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection, ...@@ -202,7 +206,7 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
break; break;
default: default:
msg_Err(session->obj, "error while writing: %d", errno); msg_Err(sys->obj, "error while writing: %d", errno);
retValue = ioErr; retValue = ioErr;
break; break;
} }
...@@ -219,14 +223,14 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection, ...@@ -219,14 +223,14 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) { static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) {
vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
int result = -1; int result = -1;
vlc_tls_sys_t *sys = session->sys;
SecCertificateRef leaf_cert = NULL; SecCertificateRef leaf_cert = NULL;
SecTrustRef trust = NULL; SecTrustRef trust = NULL;
OSStatus ret = SSLCopyPeerTrust(sys->p_context, &trust); OSStatus ret = SSLCopyPeerTrust(sys->p_context, &trust);
if (ret != noErr || trust == NULL) { if (ret != noErr || trust == NULL) {
msg_Err(session->obj, "error getting certifictate chain"); msg_Err(sys->obj, "error getting certifictate chain");
return -1; return -1;
} }
...@@ -238,7 +242,7 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam ...@@ -238,7 +242,7 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam
/* enable default root / anchor certificates */ /* enable default root / anchor certificates */
ret = SecTrustSetAnchorCertificates(trust, NULL); ret = SecTrustSetAnchorCertificates(trust, NULL);
if (ret != noErr) { if (ret != noErr) {
msg_Err(session->obj, "error setting anchor certificates"); msg_Err(sys->obj, "error setting anchor certificates");
result = -1; result = -1;
goto out; goto out;
} }
...@@ -247,7 +251,7 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam ...@@ -247,7 +251,7 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam
ret = SecTrustEvaluate(trust, &trust_eval_result); ret = SecTrustEvaluate(trust, &trust_eval_result);
if (ret != noErr) { if (ret != noErr) {
msg_Err(session->obj, "error calling SecTrustEvaluate"); msg_Err(sys->obj, "error calling SecTrustEvaluate");
result = -1; result = -1;
goto out; goto out;
} }
...@@ -255,14 +259,14 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam ...@@ -255,14 +259,14 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam
switch (trust_eval_result) { switch (trust_eval_result) {
case kSecTrustResultUnspecified: case kSecTrustResultUnspecified:
case kSecTrustResultProceed: case kSecTrustResultProceed:
msg_Dbg(session->obj, "cerfificate verification successful, result is %d", trust_eval_result); msg_Dbg(sys->obj, "cerfificate verification successful, result is %d", trust_eval_result);
result = 0; result = 0;
goto out; goto out;
case kSecTrustResultRecoverableTrustFailure: case kSecTrustResultRecoverableTrustFailure:
case kSecTrustResultDeny: case kSecTrustResultDeny:
default: default:
msg_Warn(session->obj, "cerfificate verification failed, result is %d", trust_eval_result); msg_Warn(sys->obj, "cerfificate verification failed, result is %d", trust_eval_result);
} }
/* get leaf certificate */ /* get leaf certificate */
...@@ -307,7 +311,7 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam ...@@ -307,7 +311,7 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam
continue; continue;
if (CFEqual(knownHost, cfHostname) && CFEqual(knownCert, leaf_cert)) { if (CFEqual(knownHost, cfHostname) && CFEqual(knownCert, leaf_cert)) {
msg_Warn(session->obj, "certificate already accepted, continuing"); msg_Warn(sys->obj, "certificate already accepted, continuing");
result = 0; result = 0;
goto out; goto out;
} }
...@@ -330,13 +334,13 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam ...@@ -330,13 +334,13 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam
"This problem may be caused by a configuration error " "This problem may be caused by a configuration error "
"or an attempt to breach your security or your privacy.\n\n" "or an attempt to breach your security or your privacy.\n\n"
"If in doubt, abort now.\n"); "If in doubt, abort now.\n");
int answer = vlc_dialog_wait_question(session->obj, int answer = vlc_dialog_wait_question(sys->obj,
VLC_DIALOG_QUESTION_WARNING, _("Abort"), VLC_DIALOG_QUESTION_WARNING, _("Abort"),
_("Accept certificate temporarily"), _("Accept certificate temporarily"),
NULL, _("Insecure site"), NULL, _("Insecure site"),
vlc_gettext (msg), hostname); vlc_gettext (msg), hostname);
if (answer == 1) { if (answer == 1) {
msg_Warn(session->obj, "Proceeding despite of failed certificate validation"); msg_Warn(sys->obj, "Proceeding despite of failed certificate validation");
/* save leaf certificate in whitelist */ /* save leaf certificate in whitelist */
const void *keys[] = {cfKeyHost, cfKeyCertificate}; const void *keys[] = {cfKeyHost, cfKeyCertificate};
...@@ -346,7 +350,7 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam ...@@ -346,7 +350,7 @@ static int st_validateServerCertificate (vlc_tls_t *session, const char *hostnam
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks); &kCFTypeDictionaryValueCallBacks);
if (!dict) { if (!dict) {
msg_Err(session->obj, "error creating dict"); msg_Err(sys->obj, "error creating dict");
result = -1; result = -1;
goto out; goto out;
} }
...@@ -381,9 +385,10 @@ out: ...@@ -381,9 +385,10 @@ out:
static int st_Handshake (vlc_tls_creds_t *crd, vlc_tls_t *session, static int st_Handshake (vlc_tls_creds_t *crd, vlc_tls_t *session,
const char *host, const char *service, const char *host, const char *service,
char **restrict alp) { char **restrict alp) {
VLC_UNUSED(service);
vlc_tls_sys_t *sys = session->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
VLC_UNUSED(service);
OSStatus retValue = SSLHandshake(sys->p_context); OSStatus retValue = SSLHandshake(sys->p_context);
if (alp != NULL) { if (alp != NULL) {
...@@ -426,7 +431,7 @@ static int st_Handshake (vlc_tls_creds_t *crd, vlc_tls_t *session, ...@@ -426,7 +431,7 @@ static int st_Handshake (vlc_tls_creds_t *crd, vlc_tls_t *session,
static int st_GetFD (vlc_tls_t *session) static int st_GetFD (vlc_tls_t *session)
{ {
vlc_tls_sys_t *sys = session->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
vlc_tls_t *sock = sys->sock; vlc_tls_t *sock = sys->sock;
return vlc_tls_GetFD(sock); return vlc_tls_GetFD(sock);
...@@ -438,8 +443,7 @@ static int st_GetFD (vlc_tls_t *session) ...@@ -438,8 +443,7 @@ static int st_GetFD (vlc_tls_t *session)
static ssize_t st_Send (vlc_tls_t *session, const struct iovec *iov, static ssize_t st_Send (vlc_tls_t *session, const struct iovec *iov,
unsigned count) unsigned count)
{ {
vlc_tls_sys_t *sys = session->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
assert(sys);
OSStatus ret = noErr; OSStatus ret = noErr;
if (unlikely(count == 0)) if (unlikely(count == 0))
...@@ -496,8 +500,7 @@ static ssize_t st_Send (vlc_tls_t *session, const struct iovec *iov, ...@@ -496,8 +500,7 @@ static ssize_t st_Send (vlc_tls_t *session, const struct iovec *iov,
*/ */
static ssize_t st_Recv (vlc_tls_t *session, struct iovec *iov, unsigned count) static ssize_t st_Recv (vlc_tls_t *session, struct iovec *iov, unsigned count)
{ {
vlc_tls_sys_t *sys = session->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
assert(sys);
if (unlikely(count == 0)) if (unlikely(count == 0))
return 0; return 0;
...@@ -511,7 +514,7 @@ static ssize_t st_Recv (vlc_tls_t *session, struct iovec *iov, unsigned count) ...@@ -511,7 +514,7 @@ static ssize_t st_Recv (vlc_tls_t *session, struct iovec *iov, unsigned count)
/* peer performed shutdown */ /* peer performed shutdown */
if (ret == errSSLClosedNoNotify || ret == errSSLClosedGraceful) { if (ret == errSSLClosedNoNotify || ret == errSSLClosedGraceful) {
msg_Dbg(session->obj, "Got close notification with code %i", (int)ret); msg_Dbg(sys->obj, "Got close notification with code %i", (int)ret);
return 0; return 0;
} }
...@@ -523,9 +526,11 @@ static ssize_t st_Recv (vlc_tls_t *session, struct iovec *iov, unsigned count) ...@@ -523,9 +526,11 @@ static ssize_t st_Recv (vlc_tls_t *session, struct iovec *iov, unsigned count)
*/ */
static int st_SessionShutdown (vlc_tls_t *session, bool duplex) { static int st_SessionShutdown (vlc_tls_t *session, bool duplex) {
msg_Dbg(session->obj, "shutdown TLS session");
vlc_tls_sys_t *sys = session->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
msg_Dbg(sys->obj, "shutdown TLS session");
OSStatus ret = noErr; OSStatus ret = noErr;
VLC_UNUSED(duplex); VLC_UNUSED(duplex);
...@@ -534,7 +539,7 @@ static int st_SessionShutdown (vlc_tls_t *session, bool duplex) { ...@@ -534,7 +539,7 @@ static int st_SessionShutdown (vlc_tls_t *session, bool duplex) {
} }
if (ret != noErr) { if (ret != noErr) {
msg_Warn(session->obj, "Cannot close ssl context (%i)", (int)ret); msg_Warn(sys->obj, "Cannot close ssl context (%i)", (int)ret);
return ret; return ret;
} }
...@@ -543,15 +548,15 @@ static int st_SessionShutdown (vlc_tls_t *session, bool duplex) { ...@@ -543,15 +548,15 @@ static int st_SessionShutdown (vlc_tls_t *session, bool duplex) {
static void st_SessionClose (vlc_tls_t *session) { static void st_SessionClose (vlc_tls_t *session) {
vlc_tls_sys_t *sys = session->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
msg_Dbg(session->obj, "close TLS session"); msg_Dbg(sys->obj, "close TLS session");
if (sys->p_context) { if (sys->p_context) {
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
CFRelease(sys->p_context); CFRelease(sys->p_context);
#else #else
if (SSLDisposeContext(sys->p_context) != noErr) { if (SSLDisposeContext(sys->p_context) != noErr) {
msg_Err(session->obj, "error deleting context"); msg_Err(sys->obj, "error deleting context");
} }
#endif #endif
} }
...@@ -562,12 +567,12 @@ static void st_SessionClose (vlc_tls_t *session) { ...@@ -562,12 +567,12 @@ static void st_SessionClose (vlc_tls_t *session) {
* Initializes a client-side TLS session. * Initializes a client-side TLS session.
*/ */
static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *tls, static vlc_tls_t *st_SessionOpenCommon(vlc_tls_creds_t *crd, vlc_tls_t *sock,
vlc_tls_t *sock, bool b_server) bool b_server)
{ {
vlc_tls_sys_t *sys = malloc(sizeof(vlc_tls_sys_t)); vlc_tls_st_t *sys = malloc(sizeof (*st));
if (unlikely(sys == NULL)) if (unlikely(sys == NULL))
return VLC_ENOMEM; return NULL;
sys->p_cred = crd->sys; sys->p_cred = crd->sys;
sys->b_handshaked = false; sys->b_handshaked = false;
...@@ -575,8 +580,11 @@ static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *tls, ...@@ -575,8 +580,11 @@ static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *tls,
sys->i_send_buffered_bytes = 0; sys->i_send_buffered_bytes = 0;
sys->p_context = NULL; sys->p_context = NULL;
sys->sock = sock; sys->sock = sock;
sys->b_server_mode = b_server;
sys->obj = VLC_OBJECT(crd);
vlc_tls_t *tls = &sys->tls;
tls->sys = sys;
tls->get_fd = st_GetFD; tls->get_fd = st_GetFD;
tls->readv = st_Recv; tls->readv = st_Recv;
tls->writev = st_Send; tls->writev = st_Send;
...@@ -589,12 +597,12 @@ static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *tls, ...@@ -589,12 +597,12 @@ static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *tls,
p_context = SSLCreateContext(NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType); p_context = SSLCreateContext(NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
if (p_context == NULL) { if (p_context == NULL) {
msg_Err(crd, "cannot create ssl context"); msg_Err(crd, "cannot create ssl context");
return -1; goto error;
} }
#else #else
if (SSLNewContext(b_server, &p_context) != noErr) { if (SSLNewContext(b_server, &p_context) != noErr) {
msg_Err(crd, "error calling SSLNewContext"); msg_Err(crd, "error calling SSLNewContext");
return -1; goto error;
} }
#endif #endif
...@@ -603,21 +611,24 @@ static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *tls, ...@@ -603,21 +611,24 @@ static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *tls,
OSStatus ret = SSLSetIOFuncs(p_context, st_SocketReadFunc, st_SocketWriteFunc); OSStatus ret = SSLSetIOFuncs(p_context, st_SocketReadFunc, st_SocketWriteFunc);
if (ret != noErr) { if (ret != noErr) {
msg_Err(crd, "cannot set io functions"); msg_Err(crd, "cannot set io functions");
return -1; goto error;
} }
ret = SSLSetConnection(p_context, tls); ret = SSLSetConnection(p_context, tls);
if (ret != noErr) { if (ret != noErr) {
msg_Err(crd, "cannot set connection"); msg_Err(crd, "cannot set connection");
return -1; goto error;
} }
return 0; return tls;
error:
st_SessionClose(tls);
return NULL;
} }
static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls, static vlc_tls_t *st_ClientSessionOpen(vlc_tls_creds_t *crd, vlc_tls_t *sock,
vlc_tls_t *sock, const char *hostname, const char *hostname, const char *const *alpn)
const char *const *alpn)
{ {
if (alpn != NULL) { if (alpn != NULL) {
msg_Warn(crd, "Ignoring ALPN request due to lack of support in the backend. Proxy behavior potentially undefined."); msg_Warn(crd, "Ignoring ALPN request due to lack of support in the backend. Proxy behavior potentially undefined.");
...@@ -626,13 +637,11 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls, ...@@ -626,13 +637,11 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
msg_Dbg(crd, "open TLS session for %s", hostname); msg_Dbg(crd, "open TLS session for %s", hostname);
int ret = st_SessionOpenCommon(crd, tls, sock, false); vlc_tls_t *tls = st_SessionOpenCommon(crd, sock, false);
if (ret != noErr) { if (tls == NULL)
goto error; return NULL;
}
vlc_tls_sys_t *sys = tls->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)tls;
sys->b_server_mode = false;
ret = SSLSetPeerDomainName(sys->p_context, hostname, strlen(hostname)); ret = SSLSetPeerDomainName(sys->p_context, hostname, strlen(hostname));
if (ret != noErr) { if (ret != noErr) {
...@@ -658,12 +667,12 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls, ...@@ -658,12 +667,12 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
} }
#endif #endif
return VLC_SUCCESS; return tls;
error: error:
st_SessionShutdown(tls, true); st_SessionShutdown(tls, true);
st_SessionClose(tls); st_SessionClose(tls);
return VLC_EGENERIC; return NULL;
} }
/** /**
...@@ -703,21 +712,19 @@ static void CloseClient (vlc_tls_creds_t *crd) { ...@@ -703,21 +712,19 @@ static void CloseClient (vlc_tls_creds_t *crd) {
/** /**
* Initializes a server-side TLS session. * Initializes a server-side TLS session.
*/ */
static int st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls, static vlc_tls_t *st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *sock,
vlc_tls_t *sock, const char *hostname, const char *const *alpn) { const char *hostname, const char *const *alpn) {
VLC_UNUSED(hostname); VLC_UNUSED(hostname);
VLC_UNUSED(alpn); VLC_UNUSED(alpn);
msg_Dbg(crd, "open TLS server session"); msg_Dbg(crd, "open TLS server session");
int ret = st_SessionOpenCommon(crd, tls, sock, true); vlc_tls_t *tls = st_SessionOpenCommon(crd, sock, true);
if (ret != noErr) { if (tls != NULL)
goto error; return NULL;
}
vlc_tls_sys_t *sys = tls->sys; vlc_tls_st_t *sys = (vlc_tls_st_t *)tls;
vlc_tls_creds_sys_t *p_cred_sys = crd->sys; vlc_tls_creds_sys_t *p_cred_sys = crd->sys;
sys->b_server_mode = true;
ret = SSLSetCertificate(sys->p_context, p_cred_sys->server_cert_chain); ret = SSLSetCertificate(sys->p_context, p_cred_sys->server_cert_chain);
if (ret != noErr) { if (ret != noErr) {
...@@ -725,12 +732,12 @@ static int st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls, ...@@ -725,12 +732,12 @@ static int st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
goto error; goto error;
} }
return VLC_SUCCESS; return tls;
error: error:
st_SessionShutdown(tls, true); st_SessionShutdown(tls, true);
st_SessionClose(tls); st_SessionClose(tls);
return VLC_EGENERIC; return NULL;
} }
/** /**
......
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