Support for BASIC and DIGEST authentication schemes in RTSP. Support for malformed packet descriptions that end lines in LF only, instead of CRLF.
Change-Id: I57eaefdc4b300a8f56bbe5cf3a34c424e8efe63a related-to-bug: 3084183
This commit is contained in:
@@ -23,11 +23,13 @@
|
||||
#include <media/stagefright/foundation/ABuffer.h>
|
||||
#include <media/stagefright/foundation/ADebug.h>
|
||||
#include <media/stagefright/foundation/AMessage.h>
|
||||
#include <media/stagefright/foundation/base64.h>
|
||||
#include <media/stagefright/MediaErrors.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <openssl/md5.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace android {
|
||||
@@ -37,6 +39,7 @@ const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll;
|
||||
|
||||
ARTSPConnection::ARTSPConnection()
|
||||
: mState(DISCONNECTED),
|
||||
mAuthType(NONE),
|
||||
mSocket(-1),
|
||||
mConnectionID(0),
|
||||
mNextCSeq(0),
|
||||
@@ -114,10 +117,13 @@ void ARTSPConnection::onMessageReceived(const sp<AMessage> &msg) {
|
||||
|
||||
// static
|
||||
bool ARTSPConnection::ParseURL(
|
||||
const char *url, AString *host, unsigned *port, AString *path) {
|
||||
const char *url, AString *host, unsigned *port, AString *path,
|
||||
AString *user, AString *pass) {
|
||||
host->clear();
|
||||
*port = 0;
|
||||
path->clear();
|
||||
user->clear();
|
||||
pass->clear();
|
||||
|
||||
if (strncasecmp("rtsp://", url, 7)) {
|
||||
return false;
|
||||
@@ -133,6 +139,24 @@ bool ARTSPConnection::ParseURL(
|
||||
path->setTo(slashPos);
|
||||
}
|
||||
|
||||
ssize_t atPos = host->find("@");
|
||||
|
||||
if (atPos >= 0) {
|
||||
// Split of user:pass@ from hostname.
|
||||
|
||||
AString userPass(*host, 0, atPos);
|
||||
host->erase(0, atPos + 1);
|
||||
|
||||
ssize_t colonPos = userPass.find(":");
|
||||
|
||||
if (colonPos < 0) {
|
||||
*user = userPass;
|
||||
} else {
|
||||
user->setTo(userPass, 0, colonPos);
|
||||
pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1);
|
||||
}
|
||||
}
|
||||
|
||||
const char *colonPos = strchr(host->c_str(), ':');
|
||||
|
||||
if (colonPos != NULL) {
|
||||
@@ -187,7 +211,12 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
|
||||
|
||||
AString host, path;
|
||||
unsigned port;
|
||||
if (!ParseURL(url.c_str(), &host, &port, &path)) {
|
||||
if (!ParseURL(url.c_str(), &host, &port, &path, &mUser, &mPass)
|
||||
|| (mUser.size() > 0 && mPass.size() == 0)) {
|
||||
// If we have a user name but no password we have to give up
|
||||
// right here, since we currently have no way of asking the user
|
||||
// for this information.
|
||||
|
||||
LOGE("Malformed rtsp url %s", url.c_str());
|
||||
|
||||
reply->setInt32("result", ERROR_MALFORMED);
|
||||
@@ -197,6 +226,10 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mUser.size() > 0) {
|
||||
LOGV("user = '%s', pass = '%s'", mUser.c_str(), mPass.c_str());
|
||||
}
|
||||
|
||||
struct hostent *ent = gethostbyname(host.c_str());
|
||||
if (ent == NULL) {
|
||||
LOGE("Unknown host %s", host.c_str());
|
||||
@@ -262,6 +295,11 @@ void ARTSPConnection::onDisconnect(const sp<AMessage> &msg) {
|
||||
reply->setInt32("result", OK);
|
||||
mState = DISCONNECTED;
|
||||
|
||||
mUser.clear();
|
||||
mPass.clear();
|
||||
mAuthType = NONE;
|
||||
mNonce.clear();
|
||||
|
||||
reply->post();
|
||||
}
|
||||
|
||||
@@ -335,6 +373,12 @@ void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) {
|
||||
AString request;
|
||||
CHECK(msg->findString("request", &request));
|
||||
|
||||
// Just in case we need to re-issue the request with proper authentication
|
||||
// later, stash it away.
|
||||
reply->setString("original-request", request.c_str(), request.size());
|
||||
|
||||
addAuthentication(&request);
|
||||
|
||||
// Find the boundary between headers and the body.
|
||||
ssize_t i = request.find("\r\n\r\n");
|
||||
CHECK_GE(i, 0);
|
||||
@@ -347,7 +391,7 @@ void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) {
|
||||
|
||||
request.insert(cseqHeader, i + 2);
|
||||
|
||||
LOGV("%s", request.c_str());
|
||||
LOGV("request: '%s'", request.c_str());
|
||||
|
||||
size_t numBytesSent = 0;
|
||||
while (numBytesSent < request.size()) {
|
||||
@@ -612,6 +656,30 @@ bool ARTSPConnection::receiveRTSPReponse() {
|
||||
}
|
||||
}
|
||||
|
||||
if (response->mStatusCode == 401) {
|
||||
if (mAuthType == NONE && mUser.size() > 0
|
||||
&& parseAuthMethod(response)) {
|
||||
ssize_t i;
|
||||
CHECK_EQ((status_t)OK, findPendingRequest(response, &i));
|
||||
CHECK_GE(i, 0);
|
||||
|
||||
sp<AMessage> reply = mPendingRequests.valueAt(i);
|
||||
mPendingRequests.removeItemsAt(i);
|
||||
|
||||
AString request;
|
||||
CHECK(reply->findString("original-request", &request));
|
||||
|
||||
sp<AMessage> msg = new AMessage(kWhatSendRequest, id());
|
||||
msg->setMessage("reply", reply);
|
||||
msg->setString("request", request.c_str(), request.size());
|
||||
|
||||
LOGI("re-sending request with authentication headers...");
|
||||
onSendRequest(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return notifyResponseListener(response);
|
||||
}
|
||||
|
||||
@@ -628,26 +696,47 @@ bool ARTSPConnection::ParseSingleUnsignedLong(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ARTSPConnection::notifyResponseListener(
|
||||
const sp<ARTSPResponse> &response) {
|
||||
status_t ARTSPConnection::findPendingRequest(
|
||||
const sp<ARTSPResponse> &response, ssize_t *index) const {
|
||||
*index = 0;
|
||||
|
||||
ssize_t i = response->mHeaders.indexOfKey("cseq");
|
||||
|
||||
if (i < 0) {
|
||||
return true;
|
||||
// This is an unsolicited server->client message.
|
||||
return OK;
|
||||
}
|
||||
|
||||
AString value = response->mHeaders.valueAt(i);
|
||||
|
||||
unsigned long cseq;
|
||||
if (!ParseSingleUnsignedLong(value.c_str(), &cseq)) {
|
||||
return false;
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
|
||||
i = mPendingRequests.indexOfKey(cseq);
|
||||
|
||||
if (i < 0) {
|
||||
// Unsolicited response?
|
||||
TRESPASS();
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
*index = i;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool ARTSPConnection::notifyResponseListener(
|
||||
const sp<ARTSPResponse> &response) {
|
||||
ssize_t i;
|
||||
status_t err = findPendingRequest(response, &i);
|
||||
|
||||
if (err == OK && i < 0) {
|
||||
// An unsolicited server response is not a problem.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sp<AMessage> reply = mPendingRequests.valueAt(i);
|
||||
@@ -660,4 +749,150 @@ bool ARTSPConnection::notifyResponseListener(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ARTSPConnection::parseAuthMethod(const sp<ARTSPResponse> &response) {
|
||||
ssize_t i = response->mHeaders.indexOfKey("www-authenticate");
|
||||
|
||||
if (i < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AString value = response->mHeaders.valueAt(i);
|
||||
|
||||
if (!strncmp(value.c_str(), "Basic", 5)) {
|
||||
mAuthType = BASIC;
|
||||
} else {
|
||||
CHECK(!strncmp(value.c_str(), "Digest", 6));
|
||||
mAuthType = DIGEST;
|
||||
|
||||
i = value.find("nonce=");
|
||||
CHECK_GE(i, 0);
|
||||
CHECK_EQ(value.c_str()[i + 6], '\"');
|
||||
ssize_t j = value.find("\"", i + 7);
|
||||
CHECK_GE(j, 0);
|
||||
|
||||
mNonce.setTo(value, i + 7, j - i - 7);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void H(const AString &s, AString *out) {
|
||||
out->clear();
|
||||
|
||||
MD5_CTX m;
|
||||
MD5_Init(&m);
|
||||
MD5_Update(&m, s.c_str(), s.size());
|
||||
|
||||
uint8_t key[16];
|
||||
MD5_Final(key, &m);
|
||||
|
||||
for (size_t i = 0; i < 16; ++i) {
|
||||
char nibble = key[i] >> 4;
|
||||
if (nibble <= 9) {
|
||||
nibble += '0';
|
||||
} else {
|
||||
nibble += 'a' - 10;
|
||||
}
|
||||
out->append(&nibble, 1);
|
||||
|
||||
nibble = key[i] & 0x0f;
|
||||
if (nibble <= 9) {
|
||||
nibble += '0';
|
||||
} else {
|
||||
nibble += 'a' - 10;
|
||||
}
|
||||
out->append(&nibble, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetMethodAndURL(
|
||||
const AString &request, AString *method, AString *url) {
|
||||
ssize_t space1 = request.find(" ");
|
||||
CHECK_GE(space1, 0);
|
||||
|
||||
ssize_t space2 = request.find(" ", space1 + 1);
|
||||
CHECK_GE(space2, 0);
|
||||
|
||||
method->setTo(request, 0, space1);
|
||||
url->setTo(request, space1 + 1, space2 - space1);
|
||||
}
|
||||
|
||||
void ARTSPConnection::addAuthentication(AString *request) {
|
||||
if (mAuthType == NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the boundary between headers and the body.
|
||||
ssize_t i = request->find("\r\n\r\n");
|
||||
CHECK_GE(i, 0);
|
||||
|
||||
if (mAuthType == BASIC) {
|
||||
AString tmp;
|
||||
tmp.append(mUser);
|
||||
tmp.append(":");
|
||||
tmp.append(mPass);
|
||||
|
||||
AString out;
|
||||
encodeBase64(tmp.c_str(), tmp.size(), &out);
|
||||
|
||||
AString fragment;
|
||||
fragment.append("Authorization: Basic ");
|
||||
fragment.append(out);
|
||||
fragment.append("\r\n");
|
||||
|
||||
request->insert(fragment, i + 2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK_EQ((int)mAuthType, (int)DIGEST);
|
||||
|
||||
AString method, url;
|
||||
GetMethodAndURL(*request, &method, &url);
|
||||
|
||||
AString A1;
|
||||
A1.append(mUser);
|
||||
A1.append(":");
|
||||
A1.append("Streaming Server");
|
||||
A1.append(":");
|
||||
A1.append(mPass);
|
||||
|
||||
AString A2;
|
||||
A2.append(method);
|
||||
A2.append(":");
|
||||
A2.append(url);
|
||||
|
||||
AString HA1, HA2;
|
||||
H(A1, &HA1);
|
||||
H(A2, &HA2);
|
||||
|
||||
AString tmp;
|
||||
tmp.append(HA1);
|
||||
tmp.append(":");
|
||||
tmp.append(mNonce);
|
||||
tmp.append(":");
|
||||
tmp.append(HA2);
|
||||
|
||||
AString digest;
|
||||
H(tmp, &digest);
|
||||
|
||||
AString fragment;
|
||||
fragment.append("Authorization: Digest ");
|
||||
fragment.append("nonce=\"");
|
||||
fragment.append(mNonce);
|
||||
fragment.append("\", ");
|
||||
fragment.append("username=\"");
|
||||
fragment.append(mUser);
|
||||
fragment.append("\", ");
|
||||
fragment.append("uri=\"");
|
||||
fragment.append(url);
|
||||
fragment.append("\", ");
|
||||
fragment.append("response=\"");
|
||||
fragment.append(digest);
|
||||
fragment.append("\"");
|
||||
fragment.append("\r\n");
|
||||
|
||||
request->insert(fragment, i + 2);
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
@@ -42,6 +42,10 @@ struct ARTSPConnection : public AHandler {
|
||||
|
||||
void observeBinaryData(const sp<AMessage> &reply);
|
||||
|
||||
static bool ParseURL(
|
||||
const char *url, AString *host, unsigned *port, AString *path,
|
||||
AString *user, AString *pass);
|
||||
|
||||
protected:
|
||||
virtual ~ARTSPConnection();
|
||||
virtual void onMessageReceived(const sp<AMessage> &msg);
|
||||
@@ -62,9 +66,18 @@ private:
|
||||
kWhatObserveBinaryData = 'obin',
|
||||
};
|
||||
|
||||
enum AuthType {
|
||||
NONE,
|
||||
BASIC,
|
||||
DIGEST
|
||||
};
|
||||
|
||||
static const int64_t kSelectTimeoutUs;
|
||||
|
||||
State mState;
|
||||
AString mUser, mPass;
|
||||
AuthType mAuthType;
|
||||
AString mNonce;
|
||||
int mSocket;
|
||||
int32_t mConnectionID;
|
||||
int32_t mNextCSeq;
|
||||
@@ -90,8 +103,11 @@ private:
|
||||
sp<ABuffer> receiveBinaryData();
|
||||
bool notifyResponseListener(const sp<ARTSPResponse> &response);
|
||||
|
||||
static bool ParseURL(
|
||||
const char *url, AString *host, unsigned *port, AString *path);
|
||||
bool parseAuthMethod(const sp<ARTSPResponse> &response);
|
||||
void addAuthentication(AString *request);
|
||||
|
||||
status_t findPendingRequest(
|
||||
const sp<ARTSPResponse> &response, ssize_t *index) const;
|
||||
|
||||
static bool ParseSingleUnsignedLong(
|
||||
const char *from, unsigned long *x);
|
||||
|
||||
@@ -57,12 +57,20 @@ bool ASessionDescription::parse(const void *data, size_t size) {
|
||||
|
||||
size_t i = 0;
|
||||
for (;;) {
|
||||
ssize_t eolPos = desc.find("\r\n", i);
|
||||
ssize_t eolPos = desc.find("\n", i);
|
||||
|
||||
if (eolPos < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
AString line(desc, i, eolPos - i);
|
||||
AString line;
|
||||
if ((size_t)eolPos > i && desc.c_str()[eolPos - 1] == '\r') {
|
||||
// We accept both '\n' and '\r\n' line endings, if it's
|
||||
// the latter, strip the '\r' as well.
|
||||
line.setTo(desc, i, eolPos - i - 1);
|
||||
} else {
|
||||
line.setTo(desc, i, eolPos - i);
|
||||
}
|
||||
|
||||
if (line.size() < 2 || line.c_str()[1] != '=') {
|
||||
return false;
|
||||
@@ -141,7 +149,7 @@ bool ASessionDescription::parse(const void *data, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
i = eolPos + 2;
|
||||
i = eolPos + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -245,7 +253,7 @@ bool ASessionDescription::getDurationUs(int64_t *durationUs) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value == "npt=now-") {
|
||||
if (value == "npt=now-" || value == "npt=0-") {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ LOCAL_C_INCLUDES:= \
|
||||
$(JNI_H_INCLUDE) \
|
||||
$(TOP)/frameworks/base/include/media/stagefright/openmax \
|
||||
$(TOP)/frameworks/base/media/libstagefright/include \
|
||||
$(TOP)/external/openssl/include
|
||||
|
||||
LOCAL_MODULE:= libstagefright_rtsp
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ struct MyHandler : public AHandler {
|
||||
mNetLooper(new ALooper),
|
||||
mConn(new ARTSPConnection),
|
||||
mRTPConn(new ARTPConnection),
|
||||
mOriginalSessionURL(url),
|
||||
mSessionURL(url),
|
||||
mSetupTracksSuccessful(false),
|
||||
mSeekPending(false),
|
||||
@@ -113,6 +114,23 @@ struct MyHandler : public AHandler {
|
||||
mNetLooper->start(false /* runOnCallingThread */,
|
||||
false /* canCallJava */,
|
||||
PRIORITY_HIGHEST);
|
||||
|
||||
// Strip any authentication info from the session url, we don't
|
||||
// want to transmit user/pass in cleartext.
|
||||
AString host, path, user, pass;
|
||||
unsigned port;
|
||||
if (ARTSPConnection::ParseURL(
|
||||
mSessionURL.c_str(), &host, &port, &path, &user, &pass)
|
||||
&& user.size() > 0) {
|
||||
mSessionURL.clear();
|
||||
mSessionURL.append("rtsp://");
|
||||
mSessionURL.append(host);
|
||||
mSessionURL.append(":");
|
||||
mSessionURL.append(StringPrintf("%u", port));
|
||||
mSessionURL.append(path);
|
||||
|
||||
LOGI("rewritten session url: '%s'", mSessionURL.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void connect(const sp<AMessage> &doneMsg) {
|
||||
@@ -126,7 +144,7 @@ struct MyHandler : public AHandler {
|
||||
mConn->observeBinaryData(notify);
|
||||
|
||||
sp<AMessage> reply = new AMessage('conn', id());
|
||||
mConn->connect(mSessionURL.c_str(), reply);
|
||||
mConn->connect(mOriginalSessionURL.c_str(), reply);
|
||||
}
|
||||
|
||||
void disconnect(const sp<AMessage> &doneMsg) {
|
||||
@@ -312,7 +330,7 @@ struct MyHandler : public AHandler {
|
||||
int32_t reconnect;
|
||||
if (msg->findInt32("reconnect", &reconnect) && reconnect) {
|
||||
sp<AMessage> reply = new AMessage('conn', id());
|
||||
mConn->connect(mSessionURL.c_str(), reply);
|
||||
mConn->connect(mOriginalSessionURL.c_str(), reply);
|
||||
} else {
|
||||
(new AMessage('quit', id()))->post();
|
||||
}
|
||||
@@ -922,7 +940,7 @@ struct MyHandler : public AHandler {
|
||||
CHECK(GetAttribute(range.c_str(), "npt", &val));
|
||||
float npt1, npt2;
|
||||
|
||||
if (val == "now-") {
|
||||
if (val == "now-" || val == "0-") {
|
||||
// This is a live stream and therefore not seekable.
|
||||
return;
|
||||
} else {
|
||||
@@ -992,6 +1010,7 @@ private:
|
||||
sp<ARTSPConnection> mConn;
|
||||
sp<ARTPConnection> mRTPConn;
|
||||
sp<ASessionDescription> mSessionDesc;
|
||||
AString mOriginalSessionURL; // This one still has user:pass@
|
||||
AString mSessionURL;
|
||||
AString mBaseURL;
|
||||
AString mSessionID;
|
||||
|
||||
Reference in New Issue
Block a user