mirror of https://git.ffmpeg.org/ffmpeg.git
Merge remote-tracking branch 'qatar/master'
* qatar/master: rtmp: Add support for limelight authentication rtmp: Add support for adobe authentication Conflicts: Changelog libavformat/version.h Merged-by: Michael Niedermayer <michaelni@gmx.at>
This commit is contained in:
commit
9336a980bb
|
@ -52,6 +52,7 @@ version <next>:
|
|||
- NIST Sphere demuxer
|
||||
- MPL2, VPlayer, MPlayer, AQTitle, PJS and SubViewer v1 subtitles demuxers and decoders
|
||||
- Sony Wave64 muxer
|
||||
- adobe and limelight publisher authentication in RTMP
|
||||
|
||||
|
||||
version 1.0:
|
||||
|
|
|
@ -26,8 +26,10 @@
|
|||
|
||||
#include "libavcodec/bytestream.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/base64.h"
|
||||
#include "libavutil/intfloat.h"
|
||||
#include "libavutil/lfg.h"
|
||||
#include "libavutil/md5.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/random_seed.h"
|
||||
#include "libavutil/sha.h"
|
||||
|
@ -116,6 +118,11 @@ typedef struct RTMPContext {
|
|||
int listen; ///< listen mode flag
|
||||
int listen_timeout; ///< listen timeout to wait for new connections
|
||||
int nb_streamid; ///< The next stream id to return on createStream calls
|
||||
char username[50];
|
||||
char password[50];
|
||||
char auth_params[500];
|
||||
int do_reconnect;
|
||||
int auth_tried;
|
||||
} RTMPContext;
|
||||
|
||||
#define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing
|
||||
|
@ -202,6 +209,9 @@ static void free_tracked_methods(RTMPContext *rt)
|
|||
for (i = 0; i < rt->nb_tracked_methods; i ++)
|
||||
av_free(rt->tracked_methods[i].name);
|
||||
av_free(rt->tracked_methods);
|
||||
rt->tracked_methods = NULL;
|
||||
rt->tracked_methods_size = 0;
|
||||
rt->nb_tracked_methods = 0;
|
||||
}
|
||||
|
||||
static int rtmp_send_packet(RTMPContext *rt, RTMPPacket *pkt, int track)
|
||||
|
@ -311,7 +321,7 @@ static int gen_connect(URLContext *s, RTMPContext *rt)
|
|||
ff_amf_write_number(&p, ++rt->nb_invokes);
|
||||
ff_amf_write_object_start(&p);
|
||||
ff_amf_write_field_name(&p, "app");
|
||||
ff_amf_write_string(&p, rt->app);
|
||||
ff_amf_write_string2(&p, rt->app, rt->auth_params);
|
||||
|
||||
if (!rt->is_input) {
|
||||
ff_amf_write_field_name(&p, "type");
|
||||
|
@ -326,7 +336,7 @@ static int gen_connect(URLContext *s, RTMPContext *rt)
|
|||
}
|
||||
|
||||
ff_amf_write_field_name(&p, "tcUrl");
|
||||
ff_amf_write_string(&p, rt->tcurl);
|
||||
ff_amf_write_string2(&p, rt->tcurl, rt->auth_params);
|
||||
if (rt->is_input) {
|
||||
ff_amf_write_field_name(&p, "fpad");
|
||||
ff_amf_write_bool(&p, 0);
|
||||
|
@ -1509,8 +1519,191 @@ static int handle_server_bw(URLContext *s, RTMPPacket *pkt)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int do_adobe_auth(RTMPContext *rt, const char *user, const char *salt,
|
||||
const char *opaque, const char *challenge)
|
||||
{
|
||||
uint8_t hash[16];
|
||||
char hashstr[AV_BASE64_SIZE(sizeof(hash))], challenge2[10];
|
||||
struct AVMD5 *md5 = av_md5_alloc();
|
||||
if (!md5)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
snprintf(challenge2, sizeof(challenge2), "%08x", av_get_random_seed());
|
||||
|
||||
av_md5_init(md5);
|
||||
av_md5_update(md5, user, strlen(user));
|
||||
av_md5_update(md5, salt, strlen(salt));
|
||||
av_md5_update(md5, rt->password, strlen(rt->password));
|
||||
av_md5_final(md5, hash);
|
||||
av_base64_encode(hashstr, sizeof(hashstr), hash,
|
||||
sizeof(hash));
|
||||
av_md5_init(md5);
|
||||
av_md5_update(md5, hashstr, strlen(hashstr));
|
||||
if (opaque)
|
||||
av_md5_update(md5, opaque, strlen(opaque));
|
||||
else if (challenge)
|
||||
av_md5_update(md5, challenge, strlen(challenge));
|
||||
av_md5_update(md5, challenge2, strlen(challenge2));
|
||||
av_md5_final(md5, hash);
|
||||
av_base64_encode(hashstr, sizeof(hashstr), hash,
|
||||
sizeof(hash));
|
||||
snprintf(rt->auth_params, sizeof(rt->auth_params),
|
||||
"?authmod=%s&user=%s&challenge=%s&response=%s",
|
||||
"adobe", user, challenge2, hashstr);
|
||||
if (opaque)
|
||||
av_strlcatf(rt->auth_params, sizeof(rt->auth_params),
|
||||
"&opaque=%s", opaque);
|
||||
|
||||
av_free(md5);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_llnw_auth(RTMPContext *rt, const char *user, const char *nonce)
|
||||
{
|
||||
uint8_t hash[16];
|
||||
char hashstr1[33], hashstr2[33];
|
||||
const char *realm = "live";
|
||||
const char *method = "publish";
|
||||
const char *qop = "auth";
|
||||
const char *nc = "00000001";
|
||||
char cnonce[10];
|
||||
struct AVMD5 *md5 = av_md5_alloc();
|
||||
if (!md5)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
snprintf(cnonce, sizeof(cnonce), "%08x", av_get_random_seed());
|
||||
|
||||
av_md5_init(md5);
|
||||
av_md5_update(md5, user, strlen(user));
|
||||
av_md5_update(md5, ":", 1);
|
||||
av_md5_update(md5, realm, strlen(realm));
|
||||
av_md5_update(md5, ":", 1);
|
||||
av_md5_update(md5, rt->password, strlen(rt->password));
|
||||
av_md5_final(md5, hash);
|
||||
ff_data_to_hex(hashstr1, hash, 16, 1);
|
||||
hashstr1[32] = '\0';
|
||||
|
||||
av_md5_init(md5);
|
||||
av_md5_update(md5, method, strlen(method));
|
||||
av_md5_update(md5, ":/", 2);
|
||||
av_md5_update(md5, rt->app, strlen(rt->app));
|
||||
av_md5_final(md5, hash);
|
||||
ff_data_to_hex(hashstr2, hash, 16, 1);
|
||||
hashstr2[32] = '\0';
|
||||
|
||||
av_md5_init(md5);
|
||||
av_md5_update(md5, hashstr1, strlen(hashstr1));
|
||||
av_md5_update(md5, ":", 1);
|
||||
if (nonce)
|
||||
av_md5_update(md5, nonce, strlen(nonce));
|
||||
av_md5_update(md5, ":", 1);
|
||||
av_md5_update(md5, nc, strlen(nc));
|
||||
av_md5_update(md5, ":", 1);
|
||||
av_md5_update(md5, cnonce, strlen(cnonce));
|
||||
av_md5_update(md5, ":", 1);
|
||||
av_md5_update(md5, qop, strlen(qop));
|
||||
av_md5_update(md5, ":", 1);
|
||||
av_md5_update(md5, hashstr2, strlen(hashstr2));
|
||||
av_md5_final(md5, hash);
|
||||
ff_data_to_hex(hashstr1, hash, 16, 1);
|
||||
|
||||
snprintf(rt->auth_params, sizeof(rt->auth_params),
|
||||
"?authmod=%s&user=%s&nonce=%s&cnonce=%s&nc=%s&response=%s",
|
||||
"llnw", user, nonce, cnonce, nc, hashstr1);
|
||||
|
||||
av_free(md5);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_connect_error(URLContext *s, const char *desc)
|
||||
{
|
||||
RTMPContext *rt = s->priv_data;
|
||||
char buf[300], *ptr, authmod[15];
|
||||
int i = 0, ret = 0;
|
||||
const char *user = "", *salt = "", *opaque = NULL,
|
||||
*challenge = NULL, *cptr = NULL, *nonce = NULL;
|
||||
|
||||
if (!(cptr = strstr(desc, "authmod=adobe")) &&
|
||||
!(cptr = strstr(desc, "authmod=llnw"))) {
|
||||
av_log(s, AV_LOG_ERROR,
|
||||
"Unknown connect error (unsupported authentication method?)\n");
|
||||
return AVERROR_UNKNOWN;
|
||||
}
|
||||
cptr += strlen("authmod=");
|
||||
while (*cptr && *cptr != ' ' && i < sizeof(authmod) - 1)
|
||||
authmod[i++] = *cptr++;
|
||||
authmod[i] = '\0';
|
||||
|
||||
if (!rt->username[0] || !rt->password[0]) {
|
||||
av_log(s, AV_LOG_ERROR, "No credentials set\n");
|
||||
return AVERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
if (strstr(desc, "?reason=authfailed")) {
|
||||
av_log(s, AV_LOG_ERROR, "Incorrect username/password\n");
|
||||
return AVERROR_UNKNOWN;
|
||||
} else if (strstr(desc, "?reason=nosuchuser")) {
|
||||
av_log(s, AV_LOG_ERROR, "Incorrect username\n");
|
||||
return AVERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
if (rt->auth_tried) {
|
||||
av_log(s, AV_LOG_ERROR, "Authentication failed\n");
|
||||
return AVERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
rt->auth_params[0] = '\0';
|
||||
|
||||
if (strstr(desc, "code=403 need auth")) {
|
||||
snprintf(rt->auth_params, sizeof(rt->auth_params),
|
||||
"?authmod=%s&user=%s", authmod, rt->username);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(cptr = strstr(desc, "?reason=needauth"))) {
|
||||
av_log(s, AV_LOG_ERROR, "No auth parameters found\n");
|
||||
return AVERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
av_strlcpy(buf, cptr + 1, sizeof(buf));
|
||||
ptr = buf;
|
||||
|
||||
while (ptr) {
|
||||
char *next = strchr(ptr, '&');
|
||||
char *value = strchr(ptr, '=');
|
||||
if (next)
|
||||
*next++ = '\0';
|
||||
if (value)
|
||||
*value++ = '\0';
|
||||
if (!strcmp(ptr, "user")) {
|
||||
user = value;
|
||||
} else if (!strcmp(ptr, "salt")) {
|
||||
salt = value;
|
||||
} else if (!strcmp(ptr, "opaque")) {
|
||||
opaque = value;
|
||||
} else if (!strcmp(ptr, "challenge")) {
|
||||
challenge = value;
|
||||
} else if (!strcmp(ptr, "nonce")) {
|
||||
nonce = value;
|
||||
}
|
||||
ptr = next;
|
||||
}
|
||||
|
||||
if (!strcmp(authmod, "adobe")) {
|
||||
if ((ret = do_adobe_auth(rt, user, salt, challenge, opaque)) < 0)
|
||||
return ret;
|
||||
} else {
|
||||
if ((ret = do_llnw_auth(rt, user, nonce)) < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
rt->auth_tried = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
|
||||
{
|
||||
RTMPContext *rt = s->priv_data;
|
||||
const uint8_t *data_end = pkt->data + pkt->data_size;
|
||||
char *tracked_method = NULL;
|
||||
int level = AV_LOG_ERROR;
|
||||
|
@ -1529,6 +1722,12 @@ static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
|
|||
/* Gracefully ignore Adobe-specific historical artifact errors. */
|
||||
level = AV_LOG_WARNING;
|
||||
ret = 0;
|
||||
} else if (tracked_method && !strcmp(tracked_method, "connect")) {
|
||||
ret = handle_connect_error(s, tmpstr);
|
||||
if (!ret) {
|
||||
rt->do_reconnect = 1;
|
||||
level = AV_LOG_VERBOSE;
|
||||
}
|
||||
} else
|
||||
ret = AVERROR_UNKNOWN;
|
||||
av_log(s, level, "Server error: %s\n", tmpstr);
|
||||
|
@ -1955,6 +2154,10 @@ static int get_packet(URLContext *s, int for_header)
|
|||
ff_rtmp_packet_destroy(&rpkt);
|
||||
return ret;
|
||||
}
|
||||
if (rt->do_reconnect && for_header) {
|
||||
ff_rtmp_packet_destroy(&rpkt);
|
||||
return 0;
|
||||
}
|
||||
if (rt->state == STATE_STOPPED) {
|
||||
ff_rtmp_packet_destroy(&rpkt);
|
||||
return AVERROR_EOF;
|
||||
|
@ -2057,7 +2260,7 @@ static int rtmp_close(URLContext *h)
|
|||
static int rtmp_open(URLContext *s, const char *uri, int flags)
|
||||
{
|
||||
RTMPContext *rt = s->priv_data;
|
||||
char proto[8], hostname[256], path[1024], *fname;
|
||||
char proto[8], hostname[256], path[1024], auth[100], *fname;
|
||||
char *old_app;
|
||||
uint8_t buf[2048];
|
||||
int port;
|
||||
|
@ -2069,9 +2272,19 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
|
|||
|
||||
rt->is_input = !(flags & AVIO_FLAG_WRITE);
|
||||
|
||||
av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
|
||||
av_url_split(proto, sizeof(proto), auth, sizeof(auth),
|
||||
hostname, sizeof(hostname), &port,
|
||||
path, sizeof(path), s->filename);
|
||||
|
||||
if (auth[0]) {
|
||||
char *ptr = strchr(auth, ':');
|
||||
if (ptr) {
|
||||
*ptr = '\0';
|
||||
av_strlcpy(rt->username, auth, sizeof(rt->username));
|
||||
av_strlcpy(rt->password, ptr + 1, sizeof(rt->password));
|
||||
}
|
||||
}
|
||||
|
||||
if (rt->listen && strcmp(proto, "rtmp")) {
|
||||
av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n",
|
||||
proto);
|
||||
|
@ -2107,6 +2320,7 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
|
|||
ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
|
||||
}
|
||||
|
||||
reconnect:
|
||||
if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
|
||||
&s->interrupt_callback, &opts)) < 0) {
|
||||
av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf);
|
||||
|
@ -2236,6 +2450,16 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
|
|||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
if (rt->do_reconnect) {
|
||||
ffurl_close(rt->stream);
|
||||
rt->stream = NULL;
|
||||
rt->do_reconnect = 0;
|
||||
rt->nb_invokes = 0;
|
||||
memset(rt->prev_pkt, 0, sizeof(rt->prev_pkt));
|
||||
free_tracked_methods(rt);
|
||||
goto reconnect;
|
||||
}
|
||||
|
||||
if (rt->is_input) {
|
||||
// generate FLV header for demuxer
|
||||
rt->flv_size = 13;
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
#define LIBAVFORMAT_VERSION_MAJOR 54
|
||||
#define LIBAVFORMAT_VERSION_MINOR 58
|
||||
#define LIBAVFORMAT_VERSION_MICRO 100
|
||||
#define LIBAVFORMAT_VERSION_MICRO 102
|
||||
|
||||
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
|
||||
LIBAVFORMAT_VERSION_MINOR, \
|
||||
|
|
Loading…
Reference in New Issue