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