[FFmpeg-cvslog] rtmp: Add support for adobe authentication
Martin Storsjö
git at videolan.org
Tue Jan 1 14:09:55 CET 2013
ffmpeg | branch: master | Martin Storsjö <martin at martin.st> | Sun Dec 30 22:39:38 2012 +0200| [08225d01262b638e1c4c86679a1375e02123fd4d] | committer: Martin Storsjö
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 at martin.st>
> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=08225d01262b638e1c4c86679a1375e02123fd4d
---
Changelog | 1 +
libavformat/rtmpproto.c | 163 +++++++++++++++++++++++++++++++++++++++++++++--
libavformat/version.h | 2 +-
3 files changed, 161 insertions(+), 5 deletions(-)
diff --git a/Changelog b/Changelog
index b595021..fa8cd48 100644
--- a/Changelog
+++ b/Changelog
@@ -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
diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c
index 4e059d6..1ffa748 100644
--- a/libavformat/rtmpproto.c
+++ b/libavformat/rtmpproto.c
@@ -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;
diff --git a/libavformat/version.h b/libavformat/version.h
index 349ba80..5130979 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -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, \
More information about the ffmpeg-cvslog
mailing list