librtmp does not contain any authentication methods. The easier way it to use RTSP. This way you can use authentication in the URL as such rtsp://:@. If you require using RTMP then you will need to patch librtmp, recompile it and then rebuild ffmpeg.
Here is the patch that I used. You will need to do some manual patching if you apply this to the current release.
Index: librtmp/rtmp.c
===================================================================
--- librtmp/rtmp.c (revision 513)
+++ librtmp/rtmp.c (working copy)
@@ -27,6 +27,7 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
+#include <time.h>
#include "rtmp_sys.h"
#include "log.h"
@@ -34,11 +35,18 @@
#ifdef CRYPTO
#ifdef USE_POLARSSL
#include <polarssl/havege.h>
+#include <polarssl/md5.h>
+#include <polarssl/base64.h>
+#define MD5_DIGEST_LENGTH 16
#elif defined(USE_GNUTLS)
#include <gnutls/gnutls.h>
+#include <gnutls/openssl.h>
#else /* USE_OPENSSL */
#include <openssl/ssl.h>
#include <openssl/rc4.h>
+#include <openssl/md5.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
#endif
TLS_CTX RTMP_TLS_ctx;
#endif
@@ -491,6 +499,10 @@
"Buffer time in milliseconds" },
{ AVC("timeout"), OFF(Link.timeout), OPT_INT, 0,
"Session timeout in seconds" },
+ { AVC("pubUser"), OFF(Link.pubUser), OPT_STR, 0,
+ "Publisher username" },
+ { AVC("pubPasswd"), OFF(Link.pubPasswd), OPT_STR, 0,
+ "Publisher password" },
{ {NULL,0}, 0, 0}
};
@@ -2221,6 +2233,241 @@
free(vals);
}
+
+#ifdef CRYPTO
+static int
+b64enc(const unsigned char *input, int length, char *output, int maxsize)
+{
+#ifdef USE_POLARSSL
+ int buf_size = maxsize;
+ if(base64_encode((unsigned char *) output, &buf_size, input, length) == 0)
+ {
+ output[buf_size] = '\0';
+ return 1;
+ }
+ else
+ {
+ RTMP_Log(RTMP_LOGDEBUG, "%s, error", __FUNCTION__);
+ return 0;
+ }
+#elif defined(USE_GNUTLS)
+ //TODO: gnutls have SRP-base64 encoder, use it if possible
+ static const char b64str[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ while (length && maxsize)
+ {
+ *output++ = b64str[(input[0] >> 2) & 0x3f];
+ if (!--maxsize)
+ break;
+ *output++ = b64str[((input[0] << 4) + (--length ? input[1] >> 4 : 0)) & 0x3f];
+ if (!--maxsize)
+ break;
+ *output++ = (length ? b64str[((input[1] << 2) + (--length ? input[2] >> 6 : 0)) & 0x3f] : '=');
+ if (!--maxsize)
+ break;
+ *output++ = length ? b64str[input[2] & 0x3f] : '=';
+ if (!--maxsize)
+ break;
+ if (length)
+ length--;
+ if (length)
+ input += 3;
+ }
+ if (maxsize)
+ *output = '\0';
+#else /* USE_OPENSSL */
+ BIO *bmem, *b64;
+ BUF_MEM *bptr;
+
+ b64 = BIO_new(BIO_f_base64());
+ bmem = BIO_new(BIO_s_mem());
+ b64 = BIO_push(b64, bmem);
+ BIO_write(b64, input, length);
+ if (BIO_flush(b64) == 1)
+ {
+ BIO_get_mem_ptr(b64, &bptr);
+ memcpy(output, bptr->data, bptr->length-1);
+ output[bptr->length-1] = '\0';
+ }
+ else
+ {
+ RTMP_Log(RTMP_LOGDEBUG, "%s, error", __FUNCTION__);
+ return 0;
+ }
+ BIO_free_all(b64);
+#endif
+ return 1;
+}
+
+#ifdef USE_POLARSSL
+#define md5sum(x,y,z) md5(x,y,z);
+#else
+#define md5sum(x,y,z) MD5(x,y,z);
+#endif
+
+static const AVal av_authmod_adboe = AVC("authmod=adobe");
+
+static int
+PublisherAuth(RTMP *r, AVal *description)
+{
+ char *token_in = NULL;
+ char *ptr;
+ unsigned char md5sum_val[MD5_DIGEST_LENGTH+1];
+ int challenge2_data;
+#define RESPONSE_LEN 32
+#define CHALLENGE2_LEN 16
+#define SALTED2_LEN (32+8+8+8)
+ char response[RESPONSE_LEN];
+ char challenge2[CHALLENGE2_LEN];
+ char salted2[SALTED2_LEN];
+ AVal pubToken;
+
+ if (strstr(description->av_val, av_authmod_adboe.av_val) != NULL)
+ {
+ if(strstr(description->av_val, "code=403 need auth") != NULL)
+ {
+ if (strstr(r->Link.app.av_val, av_authmod_adboe.av_val) != NULL) {
+ RTMP_Log(RTMP_LOGERROR, "%s, wrong pubUser & pubPasswd for publisher auth", __FUNCTION__);
+ r->Link.pFlags |= RTMP_PUB_CLEAN;
+ return 0;
+ } else if(r->Link.pubUser.av_len && r->Link.pubPasswd.av_len) {
+ pubToken.av_val = malloc(r->Link.pubUser.av_len + av_authmod_adboe.av_len + 8);
+ pubToken.av_len = sprintf(pubToken.av_val, "?%s&user=%s",
+ av_authmod_adboe.av_val,
+ r->Link.pubUser.av_val);
+ RTMP_Log(RTMP_LOGDEBUG, "%s, pubToken1: %s", __FUNCTION__, pubToken.av_val);
+ r->Link.pFlags |= RTMP_PUB_NAME;
+ } else {
+ RTMP_Log(RTMP_LOGERROR, "%s, need to set pubUser & pubPasswd for publisher auth", __FUNCTION__);
+ r->Link.pFlags |= RTMP_PUB_CLEAN;
+ return 0;
+ }
+ }
+ else if((token_in = strstr(description->av_val, "?reason=needauth")) != NULL)
+ {
+ ptr = strdup(token_in);
+ char *par, *val = NULL;
+ char *user = NULL;
+ char *salt = NULL;
+ char *opaque = NULL;
+ char *salted1;
+
+ while (ptr)
+ {
+ par = ptr;
+ ptr = strchr(par, '&');
+ if(ptr)
+ *ptr++ = '\0';
+
+ val = strchr(par, '=');
+ if(val)
+ *val++ = '\0';
+
+ if (strcmp(par, "user") == 0){
+ user = val;
+ } else if (strcmp(par, "salt") == 0){
+ salt = val;
+ } else if (strcmp(par, "opaque") == 0){
+ opaque = val;
+ }
+
+ RTMP_Log(RTMP_LOGDEBUG, "%s, par:\"%s\" = val:\"%s\"", __FUNCTION__, par, val);
+ }
+
+ /* hash1 = base64enc(md5(user + _aodbeAuthSalt + password)) */
+ salted1 = malloc(strlen(user)+strlen(salt)+r->Link.pubPasswd.av_len+1);
+ strcpy(salted1, user);
+ strcat(salted1, salt);
+ strcat(salted1, r->Link.pubPasswd.av_val);
+ md5sum((unsigned char*) salted1, strlen(salted1), md5sum_val);
+ RTMP_Log(RTMP_LOGDEBUG, "%s, md5(%s) =>", __FUNCTION__, salted1);
+ free(salted1);
+
+ RTMP_LogHexString(RTMP_LOGDEBUG, md5sum_val, MD5_DIGEST_LENGTH);
+
+ b64enc(md5sum_val, MD5_DIGEST_LENGTH, salted2, SALTED2_LEN);
+ RTMP_Log(RTMP_LOGDEBUG, "%s, b64(md5_1) = %s", __FUNCTION__, salted2);
+
+ srand( time(NULL) );
+ challenge2_data = rand();
+
+ b64enc((unsigned char *) &challenge2_data, sizeof(int), challenge2, CHALLENGE2_LEN);
+ RTMP_Log(RTMP_LOGDEBUG, "%s, b64(%d) = %s", __FUNCTION__, challenge2_data, challenge2);
+
+ /* response = base64enc(md5(hash1 + opaque + challenge2)) */
+ strcat(salted2, opaque);
+ strcat(salted2, challenge2);
+
+ md5sum((unsigned char*) salted2, strlen(salted2), md5sum_val);
+ RTMP_Log(RTMP_LOGDEBUG, "%s, md5(%s) =>", __FUNCTION__, salted2);
+ RTMP_LogHexString(RTMP_LOGDEBUG, md5sum_val, MD5_DIGEST_LENGTH);
+
+ b64enc(md5sum_val, MD5_DIGEST_LENGTH, response, RESPONSE_LEN);
+ RTMP_Log(RTMP_LOGDEBUG, "%s, b64(md5_2) = %s", __FUNCTION__, response);
+
+ /* have all hashes, create auth token for the end of app */
+ pubToken.av_val = malloc(32 + strlen(challenge2) + strlen(response) + strlen(opaque));
+ pubToken.av_len = sprintf(pubToken.av_val,
+ "&challenge=%s&response=%s&opaque=%s",
+ challenge2,
+ response,
+ opaque);
+ RTMP_Log(RTMP_LOGDEBUG, "%s, pubToken2: %s", __FUNCTION__, pubToken.av_val);
+ free(ptr);
+ r->Link.pFlags |= RTMP_PUB_RESP|RTMP_PUB_CLATE;
+ }
+ else if(strstr(description->av_val, "?reason=authfailed") != NULL)
+ {
+ RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed: wrong password", __FUNCTION__);
+ r->Link.pFlags |= RTMP_PUB_CLEAN;
+ return 0;
+ }
+ else if(strstr(description->av_val, "?reason=nosuchuser") != NULL)
+ {
+ RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed: no such user", __FUNCTION__);
+ r->Link.pFlags |= RTMP_PUB_CLEAN;
+ return 0;
+ }
+ else
+ {
+ RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed: unknown auth mode: %s",
+ __FUNCTION__, description->av_val);
+ r->Link.pFlags |= RTMP_PUB_CLEAN;
+ return 0;
+ }
+
+ ptr = malloc(r->Link.app.av_len + pubToken.av_len);
+ strncpy(ptr, r->Link.app.av_val, r->Link.app.av_len);
+ strncpy(ptr + r->Link.app.av_len, pubToken.av_val, pubToken.av_len);
+ r->Link.app.av_len += pubToken.av_len;
+ if(r->Link.pFlags & RTMP_PUB_ALLOC)
+ free(r->Link.app.av_val);
+ r->Link.app.av_val = ptr;
+
+ ptr = malloc(r->Link.tcUrl.av_len + pubToken.av_len);
+ strncpy(ptr, r->Link.tcUrl.av_val, r->Link.tcUrl.av_len);
+ strncpy(ptr + r->Link.tcUrl.av_len, pubToken.av_val, pubToken.av_len);
+ r->Link.tcUrl.av_len += pubToken.av_len;
+ if(r->Link.pFlags & RTMP_PUB_ALLOC)
+ free(r->Link.tcUrl.av_val);
+ r->Link.tcUrl.av_val = ptr;
+
+ free(pubToken.av_val);
+ r->Link.pFlags |= RTMP_PUB_ALLOC;
+
+ RTMP_Log(RTMP_LOGDEBUG, "%s, new app: %.*s tcUrl: %.*s playpath: %s", __FUNCTION__,
+ r->Link.app.av_len, r->Link.app.av_val,
+ r->Link.tcUrl.av_len, r->Link.tcUrl.av_val,
+ r->Link.playpath.av_val);
+ }
+ else
+ {
+ return 0;
+ }
+ return 1;
+}
+#endif
+
+
SAVC(onBWDone);
SAVC(onFCSubscribe);
SAVC(onFCUnsubscribe);
@@ -2230,6 +2477,7 @@
SAVC(close);
SAVC(code);
SAVC(level);
+SAVC(description);
SAVC(onStatus);
SAVC(playlist_ready);
static const AVal av_NetStream_Failed = AVC("NetStream.Failed");
@@ -2246,6 +2494,8 @@
static const AVal av_NetStream_Play_UnpublishNotify =
AVC("NetStream.Play.UnpublishNotify");
static const AVal av_NetStream_Publish_Start = AVC("NetStream.Publish.Start");
+static const AVal av_NetConnection_Connect_Rejected =
+AVC("NetConnection.Connect.Rejected");
/* Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' */
static int
@@ -2383,12 +2633,72 @@
}
else if (AVMATCH(&method, &av__error))
{
+#ifdef CRYPTO
+ AVal methodInvoked = {0};
+ int i;
+
+ if (r->Link.protocol & RTMP_FEATURE_WRITE)
+ {
+ for (i=0; i<r->m_numCalls; i++)
+ {
+ if (r->m_methodCalls[i].num == txn)
+ {
+ methodInvoked = r->m_methodCalls[i].name;
+ AV_erase(r->m_methodCalls, &r->m_numCalls, i, FALSE);
+ break;
+ }
+ }
+ if (!methodInvoked.av_val)
+ {
+ RTMP_Log(RTMP_LOGDEBUG, "%s, received result id %d without matching request",
+ __FUNCTION__, txn);
+ goto leave;
+ }
+
+ RTMP_Log(RTMP_LOGDEBUG, "%s, received error for method call <%s>", __FUNCTION__,
+ methodInvoked.av_val);
+
+ if (AVMATCH(&methodInvoked, &av_connect))
+ {
+ AMFObject obj2;
+ AVal code, level, description;
+ AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
+ AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code);
+ AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level);
+ AMFProp_GetString(AMF_GetProp(&obj2, &av_description, -1), &description);
+ RTMP_Log(RTMP_LOGDEBUG, "%s, error description: %s", __FUNCTION__, description.av_val);
+ /* if PublisherAuth returns 1, then reconnect */
+ PublisherAuth(r, &description);
+ }
+ }
+ else
+ {
+ RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
+ }
+#else
RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
+#endif
}
else if (AVMATCH(&method, &av_close))
{
RTMP_Log(RTMP_LOGERROR, "rtmp server requested close");
RTMP_Close(r);
+#ifdef CRYPTO
+ if ((r->Link.protocol & RTMP_FEATURE_WRITE) &&
+ !(r->Link.pFlags & RTMP_PUB_CLEAN) &&
+ ( !(r->Link.pFlags & RTMP_PUB_NAME) ||
+ !(r->Link.pFlags & RTMP_PUB_RESP) ||
+ (r->Link.pFlags & RTMP_PUB_CLATE) ) )
+ {
+ /* clean later */
+ if(r->Link.pFlags & RTMP_PUB_CLATE)
+ r->Link.pFlags |= RTMP_PUB_CLEAN;
+ RTMP_Log(RTMP_LOGERROR, "authenticating publisher");
+
+ if (!RTMP_Connect(r, NULL) || !RTMP_ConnectStream(r, 0))
+ goto leave;
+ }
+#endif
}
else if (AVMATCH(&method, &av_onStatus))
{
@@ -3419,10 +3729,21 @@
r->m_resplen = 0;
r->m_unackd = 0;
- free(r->Link.playpath0.av_val);
- r->Link.playpath0.av_val = NULL;
-
#ifdef CRYPTO
+ if (!(r->Link.protocol & RTMP_FEATURE_WRITE) || (r->Link.pFlags & RTMP_PUB_CLEAN))
+ {
+ free(r->Link.playpath0.av_val);
+ r->Link.playpath0.av_val = NULL;
+ }
+ if ((r->Link.protocol & RTMP_FEATURE_WRITE) &&
+ (r->Link.pFlags & RTMP_PUB_CLEAN) &&
+ (r->Link.pFlags & RTMP_PUB_ALLOC))
+ {
+ free(r->Link.app.av_val);
+ r->Link.app.av_val = NULL;
+ free(r->Link.tcUrl.av_val);
+ r->Link.tcUrl.av_val = NULL;
+ }
if (r->Link.dh)
{
MDH_free(r->Link.dh);
@@ -3438,6 +3759,9 @@
RC4_free(r->Link.rc4keyOut);
r->Link.rc4keyOut = NULL;
}
+#else
+ free(r->Link.playpath0.av_val);
+ r->Link.playpath0.av_val = NULL;
#endif
}
Index: librtmp/rtmp.h
===================================================================
--- librtmp/rtmp.h (revision 513)
+++ librtmp/rtmp.h (working copy)
@@ -136,6 +136,8 @@
AVal flashVer;
AVal subscribepath;
AVal token;
+ AVal pubUser;
+ AVal pubPasswd;
AMFObject extras;
int edepth;
@@ -154,6 +156,13 @@
int protocol;
int timeout; /* connection timeout in seconds */
+#define RTMP_PUB_NAME 0x0001 /* send login to server */
+#define RTMP_PUB_RESP 0x0002 /* send salted password hash */
+#define RTMP_PUB_ALLOC 0x0004 /* allocated data for new tcUrl & app */
+#define RTMP_PUB_CLEAN 0x0008 /* need to free allocated data for newer tcUrl & app at exit */
+#define RTMP_PUB_CLATE 0x0010 /* late clean tcUrl & app at exit */
+ int pFlags;
+
unsigned short socksport;
unsigned short port;
Index: librtmp/Makefile
===================================================================
--- librtmp/Makefile (revision 513)
+++ librtmp/Makefile (working copy)
@@ -14,7 +14,7 @@
DEF_=-DNO_CRYPTO
REQ_GNUTLS=gnutls
REQ_OPENSSL=libssl,libcrypto
-LIB_GNUTLS=-lgnutls -lgcrypt
+LIB_GNUTLS=-lgnutls -lgcrypt -lgnutls-openssl
LIB_OPENSSL=-lssl -lcrypto
LIB_POLARSSL=-lpolarssl
CRYPTO_LIB=$(LIB_$(CRYPTO))
Index: Makefile
===================================================================
--- Makefile (revision 513)
+++ Makefile (working copy)
@@ -11,7 +11,7 @@
CRYPTO=OPENSSL
#CRYPTO=POLARSSL
#CRYPTO=GNUTLS
-LIB_GNUTLS=-lgnutls -lgcrypt
+LIB_GNUTLS=-lgnutls -lgcrypt -lgnutls-openssl
LIB_OPENSSL=-lssl -lcrypto
LIB_POLARSSL=-lpolarssl
CRYPTO_LIB=$(LIB_$(CRYPTO))