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