RTMP push output adobe-style authentication support
This commit is contained in:
parent
b4694b5a3c
commit
2475955125
2 changed files with 126 additions and 52 deletions
|
@ -5,6 +5,7 @@
|
|||
#include <mist/triggers.h>
|
||||
#include <mist/encode.h>
|
||||
#include <mist/util.h>
|
||||
#include <mist/auth.h>
|
||||
#include <sys/stat.h>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
@ -14,10 +15,11 @@ namespace Mist{
|
|||
lastOutTime = 0;
|
||||
rtmpOffset = 0;
|
||||
bootMsOffset = 0;
|
||||
authAttempts = 0;
|
||||
maxbps = config->getInteger("maxkbps")*128;
|
||||
if (config->getString("target").size()){
|
||||
streamName = config->getString("streamname");
|
||||
HTTP::URL pushUrl(config->getString("target"));
|
||||
pushUrl = HTTP::URL(config->getString("target"));
|
||||
#ifdef SSL
|
||||
if (pushUrl.protocol != "rtmp" && pushUrl.protocol != "rtmps"){
|
||||
FAIL_MSG("Protocol not supported: %s", pushUrl.protocol.c_str());
|
||||
|
@ -43,57 +45,7 @@ namespace Mist{
|
|||
}
|
||||
initialize();
|
||||
INFO_MSG("About to push stream %s out. Host: %s, port: %d, app: %s, stream: %s", streamName.c_str(), pushUrl.host.c_str(), pushUrl.getPort(), app.c_str(), streamOut.c_str());
|
||||
if (pushUrl.protocol == "rtmp"){myConn.open(pushUrl.host, pushUrl.getPort(), false);}
|
||||
#ifdef SSL
|
||||
if (pushUrl.protocol == "rtmps"){myConn.open(pushUrl.host, pushUrl.getPort(), false, true);}
|
||||
#endif
|
||||
if (!myConn){
|
||||
FAIL_MSG("Could not connect to %s:%d!", pushUrl.host.c_str(), pushUrl.getPort());
|
||||
return;
|
||||
}
|
||||
//do handshake
|
||||
myConn.SendNow("\003", 1);//protocol version. Always 3
|
||||
char * temp = (char*)malloc(3072);
|
||||
if (!temp){
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
*((uint32_t *)temp) = 0; //time zero
|
||||
*(((uint32_t *)(temp + 4))) = htonl(0x01020304); //version 1 2 3 4
|
||||
for (int i = 8; i < 3072; ++i){
|
||||
temp[i] = FILLER_DATA[i % sizeof(FILLER_DATA)];
|
||||
}//"random" data
|
||||
myConn.SendNow(temp, 3072);
|
||||
free(temp);
|
||||
setBlocking(true);
|
||||
while (!myConn.Received().available(3073) && myConn.connected() && config->is_active){
|
||||
myConn.spool();
|
||||
}
|
||||
if (!myConn || !config->is_active){return;}
|
||||
conn.Received().remove(3073);
|
||||
RTMPStream::rec_cnt += 3073;
|
||||
RTMPStream::snd_cnt += 3073;
|
||||
setBlocking(false);
|
||||
VERYHIGH_MSG("Push out handshake completed");
|
||||
|
||||
{
|
||||
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||
amfReply.addContent(AMF::Object("", "connect")); //command
|
||||
amfReply.addContent(AMF::Object("", (double)1)); //transaction ID
|
||||
amfReply.addContent(AMF::Object("")); //options
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("app", app));
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("type", "nonprivate"));
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("flashVer", "FMLE/3.0 (compatible; MistServer/" PACKAGE_VERSION "/" RELEASE ")"));
|
||||
if (pushUrl.getPort() != 1935){
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("tcUrl", "rtmp://" + pushUrl.host + ":" + JSON::Value(pushUrl.getPort()).asString() + "/" + app));
|
||||
}else{
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("tcUrl", "rtmp://" + pushUrl.host + "/" + app));
|
||||
}
|
||||
sendCommand(amfReply, 20, 0);
|
||||
}
|
||||
RTMPStream::chunk_snd_max = 65536; //64KiB
|
||||
myConn.SendNow(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max)); //send chunk size max (msg 1)
|
||||
HIGH_MSG("Waiting for server to acknowledge connect request...");
|
||||
startPushOut("");
|
||||
}else{
|
||||
setBlocking(true);
|
||||
while (!conn.Received().available(1537) && conn.connected() && config->is_active){
|
||||
|
@ -122,6 +74,81 @@ namespace Mist{
|
|||
maxSkipAhead = 1500;
|
||||
}
|
||||
|
||||
void OutRTMP::startPushOut(const char * args){
|
||||
|
||||
myConn.close();
|
||||
myConn.Received().clear();
|
||||
|
||||
RTMPStream::chunk_rec_max = 128;
|
||||
RTMPStream::chunk_snd_max = 128;
|
||||
RTMPStream::rec_window_size = 2500000;
|
||||
RTMPStream::snd_window_size = 2500000;
|
||||
RTMPStream::rec_window_at = 0;
|
||||
RTMPStream::snd_window_at = 0;
|
||||
RTMPStream::rec_cnt = 0;
|
||||
RTMPStream::snd_cnt = 0;
|
||||
|
||||
RTMPStream::lastsend.clear();
|
||||
RTMPStream::lastrecv.clear();
|
||||
|
||||
std::string app = Encodings::URL::encode(pushUrl.path, "/:=@[]");
|
||||
size_t slash = app.find('/');
|
||||
if (slash != std::string::npos){app = app.substr(0, slash);}
|
||||
|
||||
if (pushUrl.protocol == "rtmp"){myConn.open(pushUrl.host, pushUrl.getPort(), false);}
|
||||
#ifdef SSL
|
||||
if (pushUrl.protocol == "rtmps"){myConn.open(pushUrl.host, pushUrl.getPort(), false, true);}
|
||||
#endif
|
||||
if (!myConn){
|
||||
FAIL_MSG("Could not connect to %s:%d!", pushUrl.host.c_str(), pushUrl.getPort());
|
||||
return;
|
||||
}
|
||||
//do handshake
|
||||
myConn.SendNow("\003", 1);//protocol version. Always 3
|
||||
char * temp = (char*)malloc(3072);
|
||||
if (!temp){
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
*((uint32_t *)temp) = 0; //time zero
|
||||
*(((uint32_t *)(temp + 4))) = htonl(0x01020304); //version 1 2 3 4
|
||||
for (int i = 8; i < 3072; ++i){
|
||||
temp[i] = FILLER_DATA[i % sizeof(FILLER_DATA)];
|
||||
}//"random" data
|
||||
myConn.SendNow(temp, 3072);
|
||||
free(temp);
|
||||
setBlocking(true);
|
||||
while (!myConn.Received().available(3073) && myConn.connected() && config->is_active){
|
||||
myConn.spool();
|
||||
}
|
||||
if (!myConn || !config->is_active){return;}
|
||||
myConn.Received().remove(3073);
|
||||
RTMPStream::rec_cnt += 3073;
|
||||
RTMPStream::snd_cnt += 3073;
|
||||
setBlocking(false);
|
||||
VERYHIGH_MSG("Push out handshake completed");
|
||||
std::string pushHost = "rtmp://" + pushUrl.host + "/";
|
||||
if (pushUrl.getPort() != 1935){
|
||||
pushHost = "rtmp://" + pushUrl.host + ":" + JSON::Value(pushUrl.getPort()).asString() + "/";
|
||||
}
|
||||
|
||||
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||
amfReply.addContent(AMF::Object("", "connect")); //command
|
||||
amfReply.addContent(AMF::Object("", (double)1)); //transaction ID
|
||||
amfReply.addContent(AMF::Object("")); //options
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("app", app + args));
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("type", "nonprivate"));
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("flashVer", "FMLE/3.0 (compatible; MistServer/" PACKAGE_VERSION "/" RELEASE ")"));
|
||||
amfReply.getContentP(2)->addContent(AMF::Object("tcUrl", pushHost + app + args));
|
||||
sendCommand(amfReply, 20, 0);
|
||||
|
||||
RTMPStream::chunk_snd_max = 65536; //64KiB
|
||||
myConn.SendNow(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max)); //send chunk size max (msg 1)
|
||||
HIGH_MSG("Waiting for server to acknowledge connect request...");
|
||||
|
||||
|
||||
}
|
||||
|
||||
bool OutRTMP::listenMode(){
|
||||
return !(config->getString("target").size());
|
||||
}
|
||||
|
@ -977,6 +1004,48 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
if (code.size() || description.size()){
|
||||
if (description.find("authmod=adobe") != std::string::npos){
|
||||
if (!pushUrl.user.size() && !pushUrl.pass.size()){
|
||||
FAIL_MSG("Receiving side wants credentials, but none were provided in the target");
|
||||
return;
|
||||
}
|
||||
if (description.find("?reason=authfailed") != std::string::npos || authAttempts > 1){
|
||||
FAIL_MSG("Credentials provided in the target were not accepted by the receiving side");
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
if (description.find("?reason=needauth") != std::string::npos){
|
||||
std::map<std::string, std::string> authVars;
|
||||
HTTP::parseVars(description.substr(description.find("?reason=needauth")+1), authVars);
|
||||
std::string authSalt = authVars.count("salt") ? authVars["salt"] : "";
|
||||
std::string authOpaque = authVars.count("opaque") ? authVars["opaque"] : "";
|
||||
std::string authChallenge = authVars.count("challenge") ? authVars["challenge"] : "";
|
||||
std::string authNonce = authVars.count("nonce") ? authVars["nonce"] : "";
|
||||
INFO_MSG("Adobe auth: sending credentials phase 2 (salt=%s, opaque=%s, challenge=%s, nonce=%s)", authSalt.c_str(), authOpaque.c_str(), authChallenge.c_str(), authNonce.c_str());
|
||||
authAttempts++;
|
||||
|
||||
char md5buffer[16];
|
||||
std::string to_hash = pushUrl.user + authSalt + pushUrl.pass;
|
||||
Secure::md5bin(to_hash.data(), to_hash.size(), md5buffer);
|
||||
std::string hash_one = Encodings::Base64::encode(std::string(md5buffer, 16));
|
||||
if (authOpaque.size()){
|
||||
to_hash = hash_one+authOpaque+"00000000";
|
||||
}else if (authOpaque.size()){
|
||||
to_hash = hash_one+authChallenge+"00000000";
|
||||
}
|
||||
Secure::md5bin(to_hash.data(), to_hash.size(), md5buffer);
|
||||
std::string hash_two = Encodings::Base64::encode(std::string(md5buffer, 16));
|
||||
std::string authStr = "?authmod=adobe&user=" + Encodings::URL::encode(pushUrl.user) + "&challenge=00000000&response=" + hash_two;
|
||||
if (authOpaque.size()){authStr += "&opaque=" + authOpaque;}
|
||||
startPushOut(authStr.c_str());
|
||||
return;
|
||||
}
|
||||
INFO_MSG("Adobe auth: sending credentials phase 1");
|
||||
authAttempts++;
|
||||
std::string authStr = "?authmod=adobe&user=" + Encodings::URL::encode(pushUrl.user);
|
||||
startPushOut(authStr.c_str());
|
||||
return;
|
||||
}
|
||||
WARN_MSG("Received error response: %s; %s", amfData.getContentP(3)->getContentP("code")->StrValue().c_str(), amfData.getContentP(3)->getContentP("description")->StrValue().c_str());
|
||||
}else{
|
||||
WARN_MSG("Received generic error response (no useful content)");
|
||||
|
@ -1121,6 +1190,7 @@ namespace Mist{
|
|||
break;
|
||||
case 6:
|
||||
MEDIUM_MSG("CTRL: UCM PingRequest %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
myConn.SendNow(RTMPStream::SendUSR(7, 1)); //send UCM PingResponse (7)
|
||||
break;
|
||||
case 7:
|
||||
MEDIUM_MSG("CTRL: UCM PingResponse %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <mist/flv_tag.h>
|
||||
#include <mist/amf.h>
|
||||
#include <mist/rtmpchunks.h>
|
||||
#include <mist/http_parser.h>
|
||||
|
||||
|
||||
namespace Mist {
|
||||
|
@ -26,6 +27,9 @@ namespace Mist {
|
|||
void parseChunk(Socket::Buffer & inputBuffer);
|
||||
void parseAMFCommand(AMF::Object & amfData, int messageType, int streamId);
|
||||
void sendCommand(AMF::Object & amfReply, int messageType, int streamId);
|
||||
void startPushOut(const char * args);
|
||||
HTTP::URL pushApp, pushUrl;
|
||||
uint8_t authAttempts;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue