Browse code

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>

Michael Niedermayer authored on 2013/01/01 22:04:50
Showing 3 changed files
... ...
@@ -52,6 +52,7 @@ version <next>:
52 52
 - NIST Sphere demuxer
53 53
 - MPL2, VPlayer, MPlayer, AQTitle, PJS and SubViewer v1 subtitles demuxers and decoders
54 54
 - Sony Wave64 muxer
55
+- adobe and limelight publisher authentication in RTMP
55 56
 
56 57
 
57 58
 version 1.0:
... ...
@@ -26,8 +26,10 @@
26 26
 
27 27
 #include "libavcodec/bytestream.h"
28 28
 #include "libavutil/avstring.h"
29
+#include "libavutil/base64.h"
29 30
 #include "libavutil/intfloat.h"
30 31
 #include "libavutil/lfg.h"
32
+#include "libavutil/md5.h"
31 33
 #include "libavutil/opt.h"
32 34
 #include "libavutil/random_seed.h"
33 35
 #include "libavutil/sha.h"
... ...
@@ -116,6 +118,11 @@ typedef struct RTMPContext {
116 116
     int           listen;                     ///< listen mode flag
117 117
     int           listen_timeout;             ///< listen timeout to wait for new connections
118 118
     int           nb_streamid;                ///< The next stream id to return on createStream calls
119
+    char          username[50];
120
+    char          password[50];
121
+    char          auth_params[500];
122
+    int           do_reconnect;
123
+    int           auth_tried;
119 124
 } RTMPContext;
120 125
 
121 126
 #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)
202 202
     for (i = 0; i < rt->nb_tracked_methods; i ++)
203 203
         av_free(rt->tracked_methods[i].name);
204 204
     av_free(rt->tracked_methods);
205
+    rt->tracked_methods      = NULL;
206
+    rt->tracked_methods_size = 0;
207
+    rt->nb_tracked_methods   = 0;
205 208
 }
206 209
 
207 210
 static int rtmp_send_packet(RTMPContext *rt, RTMPPacket *pkt, int track)
... ...
@@ -311,7 +321,7 @@ static int gen_connect(URLContext *s, RTMPContext *rt)
311 311
     ff_amf_write_number(&p, ++rt->nb_invokes);
312 312
     ff_amf_write_object_start(&p);
313 313
     ff_amf_write_field_name(&p, "app");
314
-    ff_amf_write_string(&p, rt->app);
314
+    ff_amf_write_string2(&p, rt->app, rt->auth_params);
315 315
 
316 316
     if (!rt->is_input) {
317 317
         ff_amf_write_field_name(&p, "type");
... ...
@@ -326,7 +336,7 @@ static int gen_connect(URLContext *s, RTMPContext *rt)
326 326
     }
327 327
 
328 328
     ff_amf_write_field_name(&p, "tcUrl");
329
-    ff_amf_write_string(&p, rt->tcurl);
329
+    ff_amf_write_string2(&p, rt->tcurl, rt->auth_params);
330 330
     if (rt->is_input) {
331 331
         ff_amf_write_field_name(&p, "fpad");
332 332
         ff_amf_write_bool(&p, 0);
... ...
@@ -1509,8 +1519,191 @@ static int handle_server_bw(URLContext *s, RTMPPacket *pkt)
1509 1509
     return 0;
1510 1510
 }
1511 1511
 
1512
+static int do_adobe_auth(RTMPContext *rt, const char *user, const char *salt,
1513
+                         const char *opaque, const char *challenge)
1514
+{
1515
+    uint8_t hash[16];
1516
+    char hashstr[AV_BASE64_SIZE(sizeof(hash))], challenge2[10];
1517
+    struct AVMD5 *md5 = av_md5_alloc();
1518
+    if (!md5)
1519
+        return AVERROR(ENOMEM);
1520
+
1521
+    snprintf(challenge2, sizeof(challenge2), "%08x", av_get_random_seed());
1522
+
1523
+    av_md5_init(md5);
1524
+    av_md5_update(md5, user, strlen(user));
1525
+    av_md5_update(md5, salt, strlen(salt));
1526
+    av_md5_update(md5, rt->password, strlen(rt->password));
1527
+    av_md5_final(md5, hash);
1528
+    av_base64_encode(hashstr, sizeof(hashstr), hash,
1529
+                     sizeof(hash));
1530
+    av_md5_init(md5);
1531
+    av_md5_update(md5, hashstr, strlen(hashstr));
1532
+    if (opaque)
1533
+        av_md5_update(md5, opaque, strlen(opaque));
1534
+    else if (challenge)
1535
+        av_md5_update(md5, challenge, strlen(challenge));
1536
+    av_md5_update(md5, challenge2, strlen(challenge2));
1537
+    av_md5_final(md5, hash);
1538
+    av_base64_encode(hashstr, sizeof(hashstr), hash,
1539
+                     sizeof(hash));
1540
+    snprintf(rt->auth_params, sizeof(rt->auth_params),
1541
+             "?authmod=%s&user=%s&challenge=%s&response=%s",
1542
+             "adobe", user, challenge2, hashstr);
1543
+    if (opaque)
1544
+        av_strlcatf(rt->auth_params, sizeof(rt->auth_params),
1545
+                    "&opaque=%s", opaque);
1546
+
1547
+    av_free(md5);
1548
+    return 0;
1549
+}
1550
+
1551
+static int do_llnw_auth(RTMPContext *rt, const char *user, const char *nonce)
1552
+{
1553
+    uint8_t hash[16];
1554
+    char hashstr1[33], hashstr2[33];
1555
+    const char *realm = "live";
1556
+    const char *method = "publish";
1557
+    const char *qop = "auth";
1558
+    const char *nc = "00000001";
1559
+    char cnonce[10];
1560
+    struct AVMD5 *md5 = av_md5_alloc();
1561
+    if (!md5)
1562
+        return AVERROR(ENOMEM);
1563
+
1564
+    snprintf(cnonce, sizeof(cnonce), "%08x", av_get_random_seed());
1565
+
1566
+    av_md5_init(md5);
1567
+    av_md5_update(md5, user, strlen(user));
1568
+    av_md5_update(md5, ":", 1);
1569
+    av_md5_update(md5, realm, strlen(realm));
1570
+    av_md5_update(md5, ":", 1);
1571
+    av_md5_update(md5, rt->password, strlen(rt->password));
1572
+    av_md5_final(md5, hash);
1573
+    ff_data_to_hex(hashstr1, hash, 16, 1);
1574
+    hashstr1[32] = '\0';
1575
+
1576
+    av_md5_init(md5);
1577
+    av_md5_update(md5, method, strlen(method));
1578
+    av_md5_update(md5, ":/", 2);
1579
+    av_md5_update(md5, rt->app, strlen(rt->app));
1580
+    av_md5_final(md5, hash);
1581
+    ff_data_to_hex(hashstr2, hash, 16, 1);
1582
+    hashstr2[32] = '\0';
1583
+
1584
+    av_md5_init(md5);
1585
+    av_md5_update(md5, hashstr1, strlen(hashstr1));
1586
+    av_md5_update(md5, ":", 1);
1587
+    if (nonce)
1588
+        av_md5_update(md5, nonce, strlen(nonce));
1589
+    av_md5_update(md5, ":", 1);
1590
+    av_md5_update(md5, nc, strlen(nc));
1591
+    av_md5_update(md5, ":", 1);
1592
+    av_md5_update(md5, cnonce, strlen(cnonce));
1593
+    av_md5_update(md5, ":", 1);
1594
+    av_md5_update(md5, qop, strlen(qop));
1595
+    av_md5_update(md5, ":", 1);
1596
+    av_md5_update(md5, hashstr2, strlen(hashstr2));
1597
+    av_md5_final(md5, hash);
1598
+    ff_data_to_hex(hashstr1, hash, 16, 1);
1599
+
1600
+    snprintf(rt->auth_params, sizeof(rt->auth_params),
1601
+             "?authmod=%s&user=%s&nonce=%s&cnonce=%s&nc=%s&response=%s",
1602
+             "llnw", user, nonce, cnonce, nc, hashstr1);
1603
+
1604
+    av_free(md5);
1605
+    return 0;
1606
+}
1607
+
1608
+static int handle_connect_error(URLContext *s, const char *desc)
1609
+{
1610
+    RTMPContext *rt = s->priv_data;
1611
+    char buf[300], *ptr, authmod[15];
1612
+    int i = 0, ret = 0;
1613
+    const char *user = "", *salt = "", *opaque = NULL,
1614
+               *challenge = NULL, *cptr = NULL, *nonce = NULL;
1615
+
1616
+    if (!(cptr = strstr(desc, "authmod=adobe")) &&
1617
+        !(cptr = strstr(desc, "authmod=llnw"))) {
1618
+        av_log(s, AV_LOG_ERROR,
1619
+               "Unknown connect error (unsupported authentication method?)\n");
1620
+        return AVERROR_UNKNOWN;
1621
+    }
1622
+    cptr += strlen("authmod=");
1623
+    while (*cptr && *cptr != ' ' && i < sizeof(authmod) - 1)
1624
+        authmod[i++] = *cptr++;
1625
+    authmod[i] = '\0';
1626
+
1627
+    if (!rt->username[0] || !rt->password[0]) {
1628
+        av_log(s, AV_LOG_ERROR, "No credentials set\n");
1629
+        return AVERROR_UNKNOWN;
1630
+    }
1631
+
1632
+    if (strstr(desc, "?reason=authfailed")) {
1633
+        av_log(s, AV_LOG_ERROR, "Incorrect username/password\n");
1634
+        return AVERROR_UNKNOWN;
1635
+    } else if (strstr(desc, "?reason=nosuchuser")) {
1636
+        av_log(s, AV_LOG_ERROR, "Incorrect username\n");
1637
+        return AVERROR_UNKNOWN;
1638
+    }
1639
+
1640
+    if (rt->auth_tried) {
1641
+        av_log(s, AV_LOG_ERROR, "Authentication failed\n");
1642
+        return AVERROR_UNKNOWN;
1643
+    }
1644
+
1645
+    rt->auth_params[0] = '\0';
1646
+
1647
+    if (strstr(desc, "code=403 need auth")) {
1648
+        snprintf(rt->auth_params, sizeof(rt->auth_params),
1649
+                 "?authmod=%s&user=%s", authmod, rt->username);
1650
+        return 0;
1651
+    }
1652
+
1653
+    if (!(cptr = strstr(desc, "?reason=needauth"))) {
1654
+        av_log(s, AV_LOG_ERROR, "No auth parameters found\n");
1655
+        return AVERROR_UNKNOWN;
1656
+    }
1657
+
1658
+    av_strlcpy(buf, cptr + 1, sizeof(buf));
1659
+    ptr = buf;
1660
+
1661
+    while (ptr) {
1662
+        char *next  = strchr(ptr, '&');
1663
+        char *value = strchr(ptr, '=');
1664
+        if (next)
1665
+            *next++ = '\0';
1666
+        if (value)
1667
+            *value++ = '\0';
1668
+        if (!strcmp(ptr, "user")) {
1669
+            user = value;
1670
+        } else if (!strcmp(ptr, "salt")) {
1671
+            salt = value;
1672
+        } else if (!strcmp(ptr, "opaque")) {
1673
+            opaque = value;
1674
+        } else if (!strcmp(ptr, "challenge")) {
1675
+            challenge = value;
1676
+        } else if (!strcmp(ptr, "nonce")) {
1677
+            nonce = value;
1678
+        }
1679
+        ptr = next;
1680
+    }
1681
+
1682
+    if (!strcmp(authmod, "adobe")) {
1683
+        if ((ret = do_adobe_auth(rt, user, salt, challenge, opaque)) < 0)
1684
+            return ret;
1685
+    } else {
1686
+        if ((ret = do_llnw_auth(rt, user, nonce)) < 0)
1687
+            return ret;
1688
+    }
1689
+
1690
+    rt->auth_tried = 1;
1691
+    return 0;
1692
+}
1693
+
1512 1694
 static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
1513 1695
 {
1696
+    RTMPContext *rt = s->priv_data;
1514 1697
     const uint8_t *data_end = pkt->data + pkt->data_size;
1515 1698
     char *tracked_method = NULL;
1516 1699
     int level = AV_LOG_ERROR;
... ...
@@ -1529,6 +1722,12 @@ static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
1529 1529
             /* Gracefully ignore Adobe-specific historical artifact errors. */
1530 1530
             level = AV_LOG_WARNING;
1531 1531
             ret = 0;
1532
+        } else if (tracked_method && !strcmp(tracked_method, "connect")) {
1533
+            ret = handle_connect_error(s, tmpstr);
1534
+            if (!ret) {
1535
+                rt->do_reconnect = 1;
1536
+                level = AV_LOG_VERBOSE;
1537
+            }
1532 1538
         } else
1533 1539
             ret = AVERROR_UNKNOWN;
1534 1540
         av_log(s, level, "Server error: %s\n", tmpstr);
... ...
@@ -1955,6 +2154,10 @@ static int get_packet(URLContext *s, int for_header)
1955 1955
             ff_rtmp_packet_destroy(&rpkt);
1956 1956
             return ret;
1957 1957
         }
1958
+        if (rt->do_reconnect && for_header) {
1959
+            ff_rtmp_packet_destroy(&rpkt);
1960
+            return 0;
1961
+        }
1958 1962
         if (rt->state == STATE_STOPPED) {
1959 1963
             ff_rtmp_packet_destroy(&rpkt);
1960 1964
             return AVERROR_EOF;
... ...
@@ -2057,7 +2260,7 @@ static int rtmp_close(URLContext *h)
2057 2057
 static int rtmp_open(URLContext *s, const char *uri, int flags)
2058 2058
 {
2059 2059
     RTMPContext *rt = s->priv_data;
2060
-    char proto[8], hostname[256], path[1024], *fname;
2060
+    char proto[8], hostname[256], path[1024], auth[100], *fname;
2061 2061
     char *old_app;
2062 2062
     uint8_t buf[2048];
2063 2063
     int port;
... ...
@@ -2069,9 +2272,19 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
2069 2069
 
2070 2070
     rt->is_input = !(flags & AVIO_FLAG_WRITE);
2071 2071
 
2072
-    av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
2072
+    av_url_split(proto, sizeof(proto), auth, sizeof(auth),
2073
+                 hostname, sizeof(hostname), &port,
2073 2074
                  path, sizeof(path), s->filename);
2074 2075
 
2076
+    if (auth[0]) {
2077
+        char *ptr = strchr(auth, ':');
2078
+        if (ptr) {
2079
+            *ptr = '\0';
2080
+            av_strlcpy(rt->username, auth, sizeof(rt->username));
2081
+            av_strlcpy(rt->password, ptr + 1, sizeof(rt->password));
2082
+        }
2083
+    }
2084
+
2075 2085
     if (rt->listen && strcmp(proto, "rtmp")) {
2076 2086
         av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n",
2077 2087
                proto);
... ...
@@ -2107,6 +2320,7 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
2107 2107
             ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
2108 2108
     }
2109 2109
 
2110
+reconnect:
2110 2111
     if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
2111 2112
                           &s->interrupt_callback, &opts)) < 0) {
2112 2113
         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)
2236 2236
     if (ret < 0)
2237 2237
         goto fail;
2238 2238
 
2239
+    if (rt->do_reconnect) {
2240
+        ffurl_close(rt->stream);
2241
+        rt->stream       = NULL;
2242
+        rt->do_reconnect = 0;
2243
+        rt->nb_invokes   = 0;
2244
+        memset(rt->prev_pkt, 0, sizeof(rt->prev_pkt));
2245
+        free_tracked_methods(rt);
2246
+        goto reconnect;
2247
+    }
2248
+
2239 2249
     if (rt->is_input) {
2240 2250
         // generate FLV header for demuxer
2241 2251
         rt->flv_size = 13;
... ...
@@ -31,7 +31,7 @@
31 31
 
32 32
 #define LIBAVFORMAT_VERSION_MAJOR 54
33 33
 #define LIBAVFORMAT_VERSION_MINOR 58
34
-#define LIBAVFORMAT_VERSION_MICRO 100
34
+#define LIBAVFORMAT_VERSION_MICRO 102
35 35
 
36 36
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
37 37
                                                LIBAVFORMAT_VERSION_MINOR, \