1/*
2 * TLS/SSL Protocol
3 * Copyright (c) 2011 Martin Storsjo
4 * Copyright (c) 2017 sfan5 <sfan5@live.de>
5 *
6 * This file is part of FFmpeg.
7 *
8 * FFmpeg is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with FFmpeg; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23#include "avformat.h"
24#include "internal.h"
25#include "network.h"
26#include "url.h"
27#include "tls.h"
28#include "libavcodec/internal.h"
29#include "libavutil/avutil.h"
30#include "libavutil/opt.h"
31
32#include <tls.h>
33
34typedef struct TLSContext {
35    const AVClass *class;
36    TLSShared tls_shared;
37    struct tls *ctx;
38} TLSContext;
39
40static int ff_tls_close(URLContext *h)
41{
42    TLSContext *p = h->priv_data;
43    if (p->ctx) {
44        tls_close(p->ctx);
45        tls_free(p->ctx);
46    }
47    ffurl_closep(&p->tls_shared.tcp);
48    return 0;
49}
50
51static ssize_t tls_read_callback(struct tls *ctx, void *buf, size_t buflen, void *cb_arg)
52{
53    URLContext *h = (URLContext*) cb_arg;
54    int ret = ffurl_read(h, buf, buflen);
55    if (ret == AVERROR(EAGAIN))
56        return TLS_WANT_POLLIN;
57    else if (ret == AVERROR_EXIT)
58        return 0;
59    return ret >= 0 ? ret : -1;
60}
61
62static ssize_t tls_write_callback(struct tls *ctx, const void *buf, size_t buflen, void *cb_arg)
63{
64    URLContext *h = (URLContext*) cb_arg;
65    int ret = ffurl_write(h, buf, buflen);
66    if (ret == AVERROR(EAGAIN))
67        return TLS_WANT_POLLOUT;
68    else if (ret == AVERROR_EXIT)
69        return 0;
70    return ret >= 0 ? ret : -1;
71}
72
73static int ff_tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
74{
75    TLSContext *p = h->priv_data;
76    TLSShared *c = &p->tls_shared;
77    struct tls_config *cfg = NULL;
78    int ret;
79
80    if (tls_init() == -1) {
81        ret = AVERROR(EIO);
82        goto fail;
83    }
84
85    if ((ret = ff_tls_open_underlying(c, h, uri, options)) < 0)
86        goto fail;
87
88    p->ctx = !c->listen ? tls_client() : tls_server();
89    if (!p->ctx) {
90        ret = AVERROR(EIO);
91        goto fail;
92    }
93
94    cfg = tls_config_new();
95    if (!p->ctx) {
96        ret = AVERROR(EIO);
97        goto fail;
98    }
99    if (tls_config_set_protocols(cfg, TLS_PROTOCOLS_ALL) == -1)
100        goto err_config;
101    // While TLSv1.0 and TLSv1.1 are already enabled by the above,
102    // we need to be less strict with ciphers so it works in practice.
103    if (tls_config_set_ciphers(cfg, "compat") == -1)
104        goto err_config;
105    if (c->ca_file && tls_config_set_ca_file(cfg, c->ca_file) == -1)
106        goto err_config;
107    if (c->cert_file && tls_config_set_cert_file(cfg, c->cert_file) == -1)
108        goto err_config;
109    if (c->key_file && tls_config_set_key_file(cfg, c->key_file) == -1)
110        goto err_config;
111    if (!c->verify) {
112        tls_config_insecure_noverifycert(cfg);
113        tls_config_insecure_noverifyname(cfg);
114        tls_config_insecure_noverifytime(cfg);
115    }
116    if (tls_configure(p->ctx, cfg) == -1)
117        goto err_ctx;
118
119    if (!c->listen) {
120        ret = tls_connect_cbs(p->ctx, tls_read_callback, tls_write_callback,
121            c->tcp, c->host);
122    } else {
123        struct tls *ctx_new;
124        ret = tls_accept_cbs(p->ctx, &ctx_new, tls_read_callback,
125            tls_write_callback, c->tcp);
126        if (ret == 0) {
127            // free "server" context and replace by "connection" context
128            tls_free(p->ctx);
129            p->ctx = ctx_new;
130        }
131    }
132    if (ret == -1)
133        goto err_ctx;
134
135    tls_config_free(cfg);
136    return 0;
137err_config:
138    av_log(h, AV_LOG_ERROR, "%s\n", tls_config_error(cfg));
139    ret = AVERROR(EIO);
140    goto fail;
141err_ctx:
142    av_log(h, AV_LOG_ERROR, "%s\n", tls_error(p->ctx));
143    ret = AVERROR(EIO);
144    /* fallthrough */
145fail:
146    if (cfg)
147        tls_config_free(cfg);
148    ff_tls_close(h);
149    return ret;
150}
151
152static int ff_tls_read(URLContext *h, uint8_t *buf, int size)
153{
154    TLSContext *p = h->priv_data;
155    ssize_t ret;
156    ret = tls_read(p->ctx, buf, size);
157    if (ret > 0)
158        return ret;
159    else if (ret == 0)
160        return AVERROR_EOF;
161    else if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
162        return AVERROR(EAGAIN);
163    av_log(h, AV_LOG_ERROR, "%s\n", tls_error(p->ctx));
164    return AVERROR(EIO);
165}
166
167static int ff_tls_write(URLContext *h, const uint8_t *buf, int size)
168{
169    TLSContext *p = h->priv_data;
170    ssize_t ret;
171    ret = tls_write(p->ctx, buf, size);
172    if (ret > 0)
173        return ret;
174    else if (ret == 0)
175        return AVERROR_EOF;
176    else if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
177        return AVERROR(EAGAIN);
178    av_log(h, AV_LOG_ERROR, "%s\n", tls_error(p->ctx));
179    return AVERROR(EIO);
180}
181
182static int tls_get_file_handle(URLContext *h)
183{
184    TLSContext *c = h->priv_data;
185    return ffurl_get_file_handle(c->tls_shared.tcp);
186}
187
188static int tls_get_short_seek(URLContext *h)
189{
190    TLSContext *s = h->priv_data;
191    return ffurl_get_short_seek(s->tls_shared.tcp);
192}
193
194static const AVOption options[] = {
195    TLS_COMMON_OPTIONS(TLSContext, tls_shared),
196    { NULL }
197};
198
199static const AVClass tls_class = {
200    .class_name = "tls",
201    .item_name  = av_default_item_name,
202    .option     = options,
203    .version    = LIBAVUTIL_VERSION_INT,
204};
205
206const URLProtocol ff_tls_protocol = {
207    .name           = "tls",
208    .url_open2      = ff_tls_open,
209    .url_read       = ff_tls_read,
210    .url_write      = ff_tls_write,
211    .url_close      = ff_tls_close,
212    .url_get_file_handle = tls_get_file_handle,
213    .url_get_short_seek  = tls_get_short_seek,
214    .priv_data_size = sizeof(TLSContext),
215    .flags          = URL_PROTOCOL_FLAG_NETWORK,
216    .priv_data_class = &tls_class,
217};
218