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:
Martin Storsjö 2012-12-30 22:39:38 +02:00
parent 33f28a3be3
commit 08225d0126
3 changed files with 161 additions and 5 deletions

View File

@ -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

View File

@ -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;

View File

@ -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, \