From 08225d01262b638e1c4c86679a1375e02123fd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Sun, 30 Dec 2012 22:39:38 +0200 Subject: [PATCH] rtmp: Add support for adobe authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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ö --- Changelog | 1 + libavformat/rtmpproto.c | 163 +++++++++++++++++++++++++++++++++++++++- libavformat/version.h | 2 +- 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/Changelog b/Changelog index b5950219a3..fa8cd48955 100644 --- a/Changelog +++ b/Changelog @@ -3,6 +3,7 @@ releases are sorted from youngest to oldest. version : - 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 4e059d6674..1ffa748b25 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 349ba806ff..51309797e4 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, \