tls.c 9.21 KB
Newer Older
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1 2 3
/*****************************************************************************
 * tls.c
 *****************************************************************************
4
 * Copyright © 2004-2007 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
 *
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
7
 * Authors: Rémi Denis-Courmont <rem # videolan.org>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
8
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
9 10 11
 * 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
12 13 14 15
 * (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
16 17
 * 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
18
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
19 20 21
 * 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
22 23
 *****************************************************************************/

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

29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33 34 35 36
#ifdef HAVE_POLL
# include <poll.h>
#endif
#include <assert.h>
37 38 39
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
40

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

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

48 49
/*** TLS credentials ***/

50 51 52 53 54 55 56 57 58 59
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);
}

60 61 62 63 64 65 66 67
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);
}

68 69 70 71 72 73 74 75
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);
}

76 77 78
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
79
{
80 81
    vlc_tls_creds_t *srv = vlc_custom_create (obj, sizeof (*srv),
                                              "tls server");
82
    if (unlikely(srv == NULL))
83
        return NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
84

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

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

    return srv;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
98 99
}

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
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
118

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

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

128

129 130
/*** TLS  session ***/

131
vlc_tls_t *vlc_tls_SessionCreate (vlc_tls_creds_t *crd, int fd,
132
                                  const char *host, const char *const *alpn)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
133
{
134 135 136 137 138 139
    vlc_tls_t *session = malloc(sizeof (*session));
    if (unlikely(session == NULL))
        return NULL;

    session->obj = crd->p_parent;

140
    int val = crd->open (crd, session, fd, host, alpn);
141 142
    if (val != VLC_SUCCESS)
    {
143 144
        free(session);
        session= NULL;
145 146
    }
    return session;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
147 148
}

149
void vlc_tls_SessionDelete (vlc_tls_t *session)
150
{
151 152 153 154
    int canc = vlc_savecancel();
    session->close(session);
    vlc_restorecancel(canc);

155
    free(session);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
156 157
}

158 159 160 161 162 163 164
static void cleanup_tls(void *data)
{
    vlc_tls_t *session = data;

    vlc_tls_SessionDelete (session);
}

165
vlc_tls_t *vlc_tls_ClientSessionCreate (vlc_tls_creds_t *crd, int fd,
166 167
                                        const char *host, const char *service,
                                        const char *const *alpn, char **alp)
168
{
169 170 171 172 173
    vlc_tls_t *session;
    int canc, val;

    canc = vlc_savecancel();
    session = vlc_tls_SessionCreate (crd, fd, host, alpn);
174
    if (session == NULL)
175 176
    {
        vlc_restorecancel(canc);
177
        return NULL;
178
    }
179

180 181 182 183 184 185
    mtime_t deadline = mdate ();
    deadline += var_InheritInteger (crd, "ipv4-timeout") * 1000;

    struct pollfd ufd[1];
    ufd[0].fd = fd;

186
    vlc_cleanup_push (cleanup_tls, session);
187
    while ((val = crd->handshake(crd, session, host, service, alp)) != 0)
188
    {
189 190
        if (val < 0)
        {
191
            msg_Err(crd, "TLS session handshake error");
192 193 194 195
error:
            vlc_tls_SessionDelete (session);
            session = NULL;
            break;
196 197
        }

198 199 200 201 202 203 204
        mtime_t now = mdate ();
        if (now > deadline)
           now = deadline;

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

205 206 207 208
        vlc_restorecancel(canc);
        val = poll (ufd, 1, (deadline - now) / 1000);
        canc = vlc_savecancel();
        if (val == 0)
209
        {
210
            msg_Err(crd, "TLS session handshake timeout");
211
            goto error;
212 213
        }
    }
214 215
    vlc_cleanup_pop();
    vlc_restorecancel(canc);
216
    return session;
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 324
static int vlc_tls_DummyGetFD(vlc_tls_t *tls)
{
    return (intptr_t)tls->sys;
}

325 326
static ssize_t vlc_tls_DummyReceive(vlc_tls_t *tls, struct iovec *iov,
                                    unsigned count)
327
{
328
    int fd = (intptr_t)tls->sys;
329 330 331 332 333
    struct msghdr msg =
    {
        .msg_iov = iov,
        .msg_iovlen = count,
    };
334
    return recvmsg(fd, &msg, 0);
335 336
}

337 338
static ssize_t vlc_tls_DummySend(vlc_tls_t *tls, const struct iovec *iov,
                                 unsigned count)
339
{
340
    int fd = (intptr_t)tls->sys;
341 342 343 344 345
    const struct msghdr msg =
    {
        .msg_iov = (struct iovec *)iov,
        .msg_iovlen = count,
    };
346
    return sendmsg(fd, &msg, MSG_NOSIGNAL);
347 348
}

349 350
static int vlc_tls_DummyShutdown(vlc_tls_t *tls, bool duplex)
{
351 352
    int fd = (intptr_t)tls->sys;
    return shutdown(fd, duplex ? SHUT_RDWR : SHUT_WR);
353 354
}

355 356 357 358 359 360 361 362 363 364 365 366
static void vlc_tls_DummyClose(vlc_tls_t *tls)
{
    (void) tls;
}

vlc_tls_t *vlc_tls_DummyCreate(vlc_object_t *obj, int fd)
{
    vlc_tls_t *session = malloc(sizeof (*session));
    if (unlikely(session == NULL))
        return NULL;

    session->obj = obj;
367 368
    session->sys = (void *)(intptr_t)fd;
    session->get_fd = vlc_tls_DummyGetFD;
369
    session->readv = vlc_tls_DummyReceive;
370
    session->writev = vlc_tls_DummySend;
371
    session->shutdown = vlc_tls_DummyShutdown;
372 373 374
    session->close = vlc_tls_DummyClose;
    return session;
}