RTMP push output adobe-style authentication support

This commit is contained in:
Thulinma 2019-08-08 16:56:48 +02:00
parent b4694b5a3c
commit 2475955125
2 changed files with 126 additions and 52 deletions

View file

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

View file

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