tls.c 15.9 KB
Newer Older
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1 2 3
/*****************************************************************************
 * tls.c
 *****************************************************************************
4
 * Copyright © 2004-2016 Rémi Denis-Courmont
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
5
 * $Id$
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
6
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
7 8 9
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
10 11 12 13
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
14 15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
16
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
17 18 19
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
20 21
 *****************************************************************************/

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
22 23 24 25 26
/**
 * @file
 * libvlc interface to the Transport Layer Security (TLS) plugins.
 */

27 28 29 30
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

31 32 33 34
#ifdef HAVE_POLL
# include <poll.h>
#endif
#include <assert.h>
35 36 37
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
38
#include <string.h>
39 40 41
#ifdef HAVE_SYS_UIO_H
# include <sys/uio.h>
#endif
42

43
#include <vlc_common.h>
44
#include "libvlc.h"
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
45

Clément Stenac's avatar
Clément Stenac committed
46
#include <vlc_tls.h>
47
#include <vlc_modules.h>
48
#include <vlc_interrupt.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
49

50 51
/*** TLS credentials ***/

52 53 54 55 56 57 58 59 60 61
static int tls_server_load(void *func, va_list ap)
{
    int (*activate) (vlc_tls_creds_t *, const char *, const char *) = func;
    vlc_tls_creds_t *crd = va_arg (ap, vlc_tls_creds_t *);
    const char *cert = va_arg (ap, const char *);
    const char *key = va_arg (ap, const char *);

    return activate (crd, cert, key);
}

62 63 64 65 66 67 68 69
static int tls_client_load(void *func, va_list ap)
{
    int (*activate) (vlc_tls_creds_t *) = func;
    vlc_tls_creds_t *crd = va_arg (ap, vlc_tls_creds_t *);

    return activate (crd);
}

70 71 72 73 74 75 76 77
static void tls_unload(void *func, va_list ap)
{
    void (*deactivate) (vlc_tls_creds_t *) = func;
    vlc_tls_creds_t *crd = va_arg (ap, vlc_tls_creds_t *);

    deactivate (crd);
}

78 79 80
vlc_tls_creds_t *
vlc_tls_ServerCreate (vlc_object_t *obj, const char *cert_path,
                      const char *key_path)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
81
{
82 83
    vlc_tls_creds_t *srv = vlc_custom_create (obj, sizeof (*srv),
                                              "tls server");
84
    if (unlikely(srv == NULL))
85
        return NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
86

87 88
    if (key_path == NULL)
        key_path = cert_path;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
89

90 91
    srv->module = vlc_module_load (srv, "tls server", NULL, false,
                                   tls_server_load, srv, cert_path, key_path);
92
    if (srv->module == NULL)
93 94
    {
        msg_Err (srv, "TLS server plugin not available");
95
        vlc_object_release (srv);
96 97 98 99
        return NULL;
    }

    return srv;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
100 101
}

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
vlc_tls_creds_t *vlc_tls_ClientCreate (vlc_object_t *obj)
{
    vlc_tls_creds_t *crd = vlc_custom_create (obj, sizeof (*crd),
                                              "tls client");
    if (unlikely(crd == NULL))
        return NULL;

    crd->module = vlc_module_load (crd, "tls client", NULL, false,
                                   tls_client_load, crd);
    if (crd->module == NULL)
    {
        msg_Err (crd, "TLS client plugin not available");
        vlc_object_release (crd);
        return NULL;
    }

    return crd;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
120

121
void vlc_tls_Delete (vlc_tls_creds_t *crd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
122
{
123
    if (crd == NULL)
124
        return;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
125

126 127
    vlc_module_unload (crd->module, tls_unload, crd);
    vlc_object_release (crd);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
128 129
}

130

131 132
/*** TLS  session ***/

133 134
static vlc_tls_t *vlc_tls_SessionCreate(vlc_tls_creds_t *crd,
                                        vlc_tls_t *sock,
135 136
                                        const char *host,
                                        const char *const *alpn)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
137
{
138
    vlc_tls_t *session;
139
    int canc = vlc_savecancel();
140
    session = crd->open(crd, sock, host, alpn);
141
    vlc_restorecancel(canc);
142 143
    if (session != NULL)
        session->p = sock;
144
    return session;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
145 146
}

147
void vlc_tls_SessionDelete (vlc_tls_t *session)
148
{
149 150 151
    int canc = vlc_savecancel();
    session->close(session);
    vlc_restorecancel(canc);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
152 153
}

154 155 156 157 158 159 160
static void cleanup_tls(void *data)
{
    vlc_tls_t *session = data;

    vlc_tls_SessionDelete (session);
}

161 162 163 164
#undef vlc_tls_ClientSessionCreate
vlc_tls_t *vlc_tls_ClientSessionCreate(vlc_tls_creds_t *crd, vlc_tls_t *sock,
                                       const char *host, const char *service,
                                       const char *const *alpn, char **alp)
165
{
166
    int val;
167

168
    vlc_tls_t *session = vlc_tls_SessionCreate(crd, sock, host, alpn);
169
    if (session == NULL)
170
        return NULL;
171

172
    int canc = vlc_savecancel();
173 174 175 176
    mtime_t deadline = mdate ();
    deadline += var_InheritInteger (crd, "ipv4-timeout") * 1000;

    struct pollfd ufd[1];
177
    ufd[0].fd = vlc_tls_GetFD(sock);
178

179
    vlc_cleanup_push (cleanup_tls, session);
180
    while ((val = crd->handshake(crd, session, host, service, alp)) != 0)
181
    {
182 183
        if (val < 0)
        {
184
            msg_Err(crd, "TLS session handshake error");
185 186 187 188
error:
            vlc_tls_SessionDelete (session);
            session = NULL;
            break;
189 190
        }

191 192 193 194 195 196 197
        mtime_t now = mdate ();
        if (now > deadline)
           now = deadline;

        assert (val <= 2);
        ufd[0] .events = (val == 1) ? POLLIN : POLLOUT;

198 199 200 201
        vlc_restorecancel(canc);
        val = poll (ufd, 1, (deadline - now) / 1000);
        canc = vlc_savecancel();
        if (val == 0)
202
        {
203
            msg_Err(crd, "TLS session handshake timeout");
204
            goto error;
205 206
        }
    }
207 208
    vlc_cleanup_pop();
    vlc_restorecancel(canc);
209
    return session;
210
}
211

212 213
vlc_tls_t *vlc_tls_ServerSessionCreate(vlc_tls_creds_t *crd,
                                       vlc_tls_t *sock,
214 215
                                       const char *const *alpn)
{
216
    return vlc_tls_SessionCreate(crd, sock, NULL, alpn);
217 218
}

219
ssize_t vlc_tls_Read(vlc_tls_t *session, void *buf, size_t len, bool waitall)
220
{
221
    struct pollfd ufd;
222
    struct iovec iov;
223

224
    ufd.fd = vlc_tls_GetFD(session);
225
    ufd.events = POLLIN;
226 227
    iov.iov_base = buf;
    iov.iov_len = len;
228 229 230

    for (size_t rcvd = 0;;)
    {
231
        if (vlc_killed())
232 233 234 235 236
        {
            errno = EINTR;
            return -1;
        }

237
        ssize_t val = session->readv(session, &iov, 1);
238 239 240 241
        if (val > 0)
        {
            if (!waitall)
                return val;
242 243
            iov.iov_base = (char *)iov.iov_base + val;
            iov.iov_len -= val;
244 245
            rcvd += val;
        }
246
        if (iov.iov_len == 0 || val == 0)
247 248 249 250
            return rcvd;
        if (val == -1 && errno != EINTR && errno != EAGAIN)
            return rcvd ? (ssize_t)rcvd : -1;

251
        vlc_poll_i11e(&ufd, 1, -1);
252 253 254
    }
}

255
ssize_t vlc_tls_Write(vlc_tls_t *session, const void *buf, size_t len)
256
{
257
    struct pollfd ufd;
258
    struct iovec iov;
259

260
    ufd.fd = vlc_tls_GetFD(session);
261
    ufd.events = POLLOUT;
262 263
    iov.iov_base = (void *)buf;
    iov.iov_len = len;
264 265 266

    for (size_t sent = 0;;)
    {
267
        if (vlc_killed())
268 269 270 271 272
        {
            errno = EINTR;
            return -1;
        }

273
        ssize_t val = session->writev(session, &iov, 1);
274 275
        if (val > 0)
        {
276 277
            iov.iov_base = ((char *)iov.iov_base) + val;
            iov.iov_len -= val;
278 279
            sent += val;
        }
280
        if (iov.iov_len == 0 || val == 0)
281 282 283 284
            return sent;
        if (val == -1 && errno != EINTR && errno != EAGAIN)
            return sent ? (ssize_t)sent : -1;

285
        vlc_poll_i11e(&ufd, 1, -1);
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    }
}

char *vlc_tls_GetLine(vlc_tls_t *session)
{
    char *line = NULL;
    size_t linelen = 0, linesize = 0;

    do
    {
        if (linelen == linesize)
        {
            linesize += 1024;

            char *newline = realloc(line, linesize);
            if (unlikely(newline == NULL))
                goto error;
            line = newline;
        }

        if (vlc_tls_Read(session, line + linelen, 1, false) <= 0)
            goto error;
    }
    while (line[linelen++] != '\n');

    if (linelen >= 2 && line[linelen - 2] == '\r')
        line[linelen - 2] = '\0';
    return line;

error:
    free(line);
    return NULL;
}
319

320 321 322 323
typedef struct vlc_tls_socket
{
    struct vlc_tls tls;
    int fd;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
324 325
    socklen_t peerlen;
    struct sockaddr peer[];
326 327
} vlc_tls_socket_t;

328
static int vlc_tls_SocketGetFD(vlc_tls_t *tls)
329
{
330 331 332
    vlc_tls_socket_t *sock = (struct vlc_tls_socket *)tls;

    return sock->fd;
333 334
}

335 336
static ssize_t vlc_tls_SocketRead(vlc_tls_t *tls, struct iovec *iov,
                                  unsigned count)
337
{
338 339 340 341 342
    struct msghdr msg =
    {
        .msg_iov = iov,
        .msg_iovlen = count,
    };
343 344

    return recvmsg(vlc_tls_SocketGetFD(tls), &msg, 0);
345 346
}

347 348
static ssize_t vlc_tls_SocketWrite(vlc_tls_t *tls, const struct iovec *iov,
                                   unsigned count)
349
{
350 351 352 353 354
    const struct msghdr msg =
    {
        .msg_iov = (struct iovec *)iov,
        .msg_iovlen = count,
    };
355 356

    return sendmsg(vlc_tls_SocketGetFD(tls), &msg, MSG_NOSIGNAL);
357 358
}

359
static int vlc_tls_SocketShutdown(vlc_tls_t *tls, bool duplex)
360
{
361
    return shutdown(vlc_tls_SocketGetFD(tls), duplex ? SHUT_RDWR : SHUT_WR);
362 363
}

364
static void vlc_tls_SocketClose(vlc_tls_t *tls)
365
{
366 367
    net_Close(vlc_tls_SocketGetFD(tls));
    free(tls);
368 369
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
370 371 372
static vlc_tls_t *vlc_tls_SocketAlloc(int fd,
                                      const struct sockaddr *restrict peer,
                                      socklen_t peerlen)
373
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
374
    vlc_tls_socket_t *sock = malloc(sizeof (*sock) + peerlen);
375
    if (unlikely(sock == NULL))
376 377
        return NULL;

378 379 380 381 382 383 384 385
    vlc_tls_t *tls = &sock->tls;

    tls->get_fd = vlc_tls_SocketGetFD;
    tls->readv = vlc_tls_SocketRead;
    tls->writev = vlc_tls_SocketWrite;
    tls->shutdown = vlc_tls_SocketShutdown;
    tls->close = vlc_tls_SocketClose;
    tls->p = NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
386

387
    sock->fd = fd;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
388 389 390
    sock->peerlen = peerlen;
    if (peerlen > 0)
        memcpy(sock->peer, peer, peerlen);
391
    return tls;
392
}
393

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
394 395 396 397 398
vlc_tls_t *vlc_tls_SocketOpen(int fd)
{
    return vlc_tls_SocketAlloc(fd, NULL, 0);
}

399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
int vlc_tls_SocketPair(int family, int protocol, vlc_tls_t *pair[2])
{
    int fds[2];

    if (vlc_socketpair(family, SOCK_STREAM, protocol, fds, true))
        return -1;

    for (size_t i = 0; i < 2; i++)
    {
        setsockopt(fds[i], SOL_SOCKET, SO_REUSEADDR,
                   &(int){ 1 }, sizeof (int));

        pair[i] = vlc_tls_SocketAlloc(fds[i], NULL, 0);
        if (unlikely(pair[i] == NULL))
        {
            net_Close(fds[i]);
            if (i)
                vlc_tls_SessionDelete(pair[0]);
            else
                net_Close(fds[1]);
            return -1;
        }
    }
    return 0;
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
425 426 427 428
/**
 * Allocates an unconnected transport layer socket.
 */
static vlc_tls_t *vlc_tls_SocketAddrInfo(const struct addrinfo *restrict info)
429 430 431 432 433 434 435 436
{
    int fd = vlc_socket(info->ai_family, info->ai_socktype, info->ai_protocol,
                        true /* nonblocking */);
    if (fd == -1)
        return NULL;

    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof (int));

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
437 438 439 440 441
    vlc_tls_t *sk = vlc_tls_SocketAlloc(fd, info->ai_addr, info->ai_addrlen);
    if (unlikely(sk == NULL))
        net_Close(fd);
    return sk;
}
442

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
443 444 445 446 447 448 449
/**
 * Waits for pending transport layer socket connection.
 */
static int vlc_tls_WaitConnect(vlc_tls_t *tls)
{
    const int fd = vlc_tls_GetFD(tls);
    struct pollfd ufd;
450

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
451 452
    ufd.fd = fd;
    ufd.events = POLLOUT;
453

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
454 455 456
    do
    {
        if (vlc_killed())
457
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
458 459
            errno = EINTR;
            return -1;
460
        }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
461 462
    }
    while (vlc_poll_i11e(&ufd, 1, -1) <= 0);
463

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
464 465
    int val;
    socklen_t len = sizeof (val);
466

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
467 468 469 470 471 472 473
    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &len))
        return -1;

    if (val != 0)
    {
        errno = val;
        return -1;
474
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
475 476
    return 0;
}
477

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
/**
 * Connects a transport layer socket.
 */
static ssize_t vlc_tls_Connect(vlc_tls_t *tls)
{
    const vlc_tls_socket_t *sock = (vlc_tls_socket_t *)tls;

    if (connect(sock->fd, sock->peer, sock->peerlen) == 0)
        return 0;
#ifndef _WIN32
    if (errno != EINPROGRESS)
        return -1;
#else
    if (WSAGetLastError() != WSAEWOULDBLOCK)
        return -1;
#endif
    return vlc_tls_WaitConnect(tls);
}
496

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
497 498 499 500
/* Callback for combined connection establishment and initial send */
static ssize_t vlc_tls_ConnectWrite(vlc_tls_t *tls,
                                    const struct iovec *iov,unsigned count)
{
501 502 503 504 505 506 507 508 509 510 511
#ifdef MSG_FASTOPEN
    vlc_tls_socket_t *sock = (vlc_tls_socket_t *)tls;
    const struct msghdr msg =
    {
        .msg_name = sock->peer,
        .msg_namelen = sock->peerlen,
        .msg_iov = (struct iovec *)iov,
        .msg_iovlen = count,
    };
    ssize_t ret;

512 513 514
    /* Next time, write directly. Do not retry to connect. */
    tls->writev = vlc_tls_SocketWrite;

515 516 517 518 519 520 521
    ret = sendmsg(vlc_tls_SocketGetFD(tls), &msg, MSG_NOSIGNAL|MSG_FASTOPEN);
    if (ret >= 0)
    {   /* Fast open in progress */
        return ret;
    }

    if (errno == EINPROGRESS)
522 523 524 525 526
    {
        if (vlc_tls_WaitConnect(tls))
            return -1;
    }
    else
527 528 529
    if (errno != EOPNOTSUPP)
        return -1;
    /* Fast open not supported or disabled... fallback to normal mode */
530 531
#else
    tls->writev = vlc_tls_SocketWrite;
532 533
#endif

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
534 535
    if (vlc_tls_Connect(tls))
        return -1;
536

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
537 538 539
    return vlc_tls_SocketWrite(tls, iov, count);
}

540
vlc_tls_t *vlc_tls_SocketOpenAddrInfo(const struct addrinfo *restrict info)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
541
{
542 543
    vlc_tls_t *sock = vlc_tls_SocketAddrInfo(info);
    if (sock == NULL)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
544 545
        return NULL;

546
    if (vlc_tls_Connect(sock))
547
    {
548 549
        vlc_tls_SessionDelete(sock);
        sock = NULL;
550
    }
551
    return sock;
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
}

vlc_tls_t *vlc_tls_SocketOpenTCP(vlc_object_t *obj, const char *name,
                                 unsigned port)
{
    struct addrinfo hints =
    {
        .ai_socktype = SOCK_STREAM,
        .ai_protocol = IPPROTO_TCP,
    }, *res;

    assert(name != NULL);
    msg_Dbg(obj, "resolving %s ...", name);

    int val = vlc_getaddrinfo_i11e(name, port, &hints, &res);
    if (val != 0)
    {   /* TODO: C locale for gai_strerror() */
        msg_Err(obj, "cannot resolve %s port %u: %s", name, port,
                gai_strerror(val));
        return NULL;
    }

    msg_Dbg(obj, "connecting to %s port %u ...", name, port);

576 577 578 579 580 581 582 583 584 585 586 587 588 589
    /* TODO: implement RFC6555 */
    for (const struct addrinfo *p = res; p != NULL; p = p->ai_next)
    {
        vlc_tls_t *tls = vlc_tls_SocketOpenAddrInfo(p);
        if (tls == NULL)
        {
            msg_Err(obj, "connection error: %s", vlc_strerror_c(errno));
            continue;
        }

        freeaddrinfo(res);
        return tls;
    }

590
    freeaddrinfo(res);
591
    return NULL;
592
}
593 594 595 596 597

vlc_tls_t *vlc_tls_SocketOpenTLS(vlc_tls_creds_t *creds, const char *name,
                                 unsigned port, const char *service,
                                 const char *const *alpn, char **alp)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
598 599 600 601 602 603 604 605 606 607 608 609 610
    struct addrinfo hints =
    {
        .ai_socktype = SOCK_STREAM,
        .ai_protocol = IPPROTO_TCP,
    }, *res;

    msg_Dbg(creds, "resolving %s ...", name);

    int val = vlc_getaddrinfo_i11e(name, port, &hints, &res);
    if (val != 0)
    {   /* TODO: C locale for gai_strerror() */
        msg_Err(creds, "cannot resolve %s port %u: %s", name, port,
                gai_strerror(val));
611
        return NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
612
    }
613

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
    for (const struct addrinfo *p = res; p != NULL; p = p->ai_next)
    {
        vlc_tls_t *tcp = vlc_tls_SocketAddrInfo(p);
        if (tcp == NULL)
        {
            msg_Err(creds, "socket error: %s", vlc_strerror_c(errno));
            continue;
        }

        /* The socket is not connected yet.
         * The connection will be triggered on the first send. */
        tcp->writev = vlc_tls_ConnectWrite;

        vlc_tls_t *tls = vlc_tls_ClientSessionCreate(creds, tcp, name, service,
                                                     alpn, alp);
        if (tls != NULL)
        {   /* Success! */
            freeaddrinfo(res);
            return tls;
        }

        msg_Err(creds, "connection error: %s", vlc_strerror_c(errno));
636
        vlc_tls_SessionDelete(tcp);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
637 638 639 640 641
    }

    /* Failure! */
    freeaddrinfo(res);
    return NULL;
642
}