mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-11-26 19:01:44 +02:00
rtmp: Add support for adobe authentication
This is mostly used to authenticate the client when publishing. Tested with wowza and akamai. Some but not all servers support resending a new connect invoke within the same connection, so always reconnect for sending a new connection attempt. This matches what other applications do as well. The authentication scheme is structurally pretty similar to http digest authentication, but uses base64 instead of hex strings. Signed-off-by: Martin Storsjö <martin@martin.st>
This commit is contained in:
parent
33f28a3be3
commit
08225d0126
@ -3,6 +3,7 @@ releases are sorted from youngest to oldest.
|
||||
|
||||
version <next>:
|
||||
- av_basename and av_dirname
|
||||
- adobe publisher authentication in RTMP
|
||||
|
||||
version 9_beta3:
|
||||
- ashowinfo audio filter
|
||||
|
@ -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)
|
||||
@ -314,7 +324,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");
|
||||
@ -329,7 +339,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);
|
||||
@ -1512,8 +1522,122 @@ 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 handle_connect_error(URLContext *s, const char *desc)
|
||||
{
|
||||
RTMPContext *rt = s->priv_data;
|
||||
char buf[300], *ptr;
|
||||
int i = 0, ret = 0;
|
||||
const char *user = "", *salt = "", *opaque = NULL,
|
||||
*challenge = NULL, *cptr = NULL;
|
||||
|
||||
if (!(cptr = strstr(desc, "authmod=adobe"))) {
|
||||
av_log(s, AV_LOG_ERROR,
|
||||
"Unknown connect error (unsupported authentication method?)\n");
|
||||
return AVERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
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", "adobe", 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;
|
||||
}
|
||||
ptr = next;
|
||||
}
|
||||
|
||||
if ((ret = do_adobe_auth(rt, user, salt, challenge, opaque)) < 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;
|
||||
@ -1532,6 +1656,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);
|
||||
@ -1958,6 +2088,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;
|
||||
@ -2060,7 +2194,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;
|
||||
@ -2072,9 +2206,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);
|
||||
@ -2110,6 +2254,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);
|
||||
@ -2239,6 +2384,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 20
|
||||
#define LIBAVFORMAT_VERSION_MICRO 0
|
||||
#define LIBAVFORMAT_VERSION_MICRO 1
|
||||
|
||||
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
|
||||
LIBAVFORMAT_VERSION_MINOR, \
|
||||
|
Loading…
Reference in New Issue
Block a user