[Live-devel] [patch] Authentication hiccups

Tomasz Pala gotar at polanet.pl
Sat Nov 1 03:39:54 PDT 2014


Hello,

recently I've tried to watch some recordings on my DVR. While there were
no problems with online streams from cameras attached, I couldn't find a
way to review anything older, all I got was _the first frame_ of specified
period only. I've tried vlc, mplayer and finally openRTSP without success.
However, ffmpeg did the job and fetched entire stream as requested.
After some digging I've found what causes this behaviour and made some
workaround/fix - patch attached, description follows.

My DVR is password-protected, session using live555 libs looks like this:

-> Sending request: OPTIONS
<- RTSP/1.0 401 Unauthorized	WWW-Authenticate: Digest
-> Sending request: OPTIONS	Authorization: Digest
<- RTSP/1.0 200 OK
-> Sending request: DESCRIBE
<- RTSP/1.0 401 Unauthorized	WWW-Authenticate: Digest
-> Sending request: DESCRIBE	Authorization: Digest
<- RTSP/1.0 200 OK
-> Sending request: SETUP
<- RTSP/1.0 401 Unauthorized	WWW-Authenticate: Digest
-> Sending request: SETUP	Authorization: Digest
<- RTSP/1.0 200 OK
-> Sending request: SETUP
<- RTSP/1.0 401 Unauthorized	WWW-Authenticate: Digest
-> Sending request: SETUP	Authorization: Digest
<- RTSP/1.0 200 OK
-> Sending request: PLAY
<- RTSP/1.0 401 Unauthorized	WWW-Authenticate: Digest
-> Sending request: PLAY	Authorization: Digest
<- RTSP/1.0 455 Method Not Valid In This State
-> Sending request: TEARDOWN
<- RTSP/1.0 401 Unauthorized	WWW-Authenticate: Digest
-> Sending request: TEARDOWN	Authorization: Digest
<- RTSP/1.0 200 OK

This is a bit ugly, compared to ffmpeg which follows realm and nonce
once authenticated, not only because of excessive traffic, but
apparently in case of my DVR - disturbance of SETUPed status and
preventing PLAY request from doing what it was supposed to do.
Rationale behind ffmpeg approach (keeping realm and nonce) is
straightforward - if access is protected, then every request from a
session should be authorized. There's no point in trying unauthenticated
access.

The problem with RTSPClient is that it operates on fCurrentAuthenticator
which is only a copy of main authenticator instance; this in turn is not
passed to any response handler, so realm and nonce are lost at every
send command beginning (and reappear after command resend); fCurrentAuthenticator is overwritten and there's
no path that allows saving these values in original authenticator
instance. My first workaround was to save them in sendDescribeCommand:

unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
  authenticator->setRealmAndNonce(fCurrentAuthenticator.realm(),fCurrentAuthenticator.nonce());
  // note missing sanity checks above /when authenticator == NULL/, this is just a PoC
  if (authenticator != NULL ) fCurrentAuthenticator = *authenticator;
  return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
}

and that works like a charm. However, I don't know if it is the proper
solutiuon (what if '401 Unauthorized' is returned later, i.e. after
SETUP? or if nonce changes? May I alter anything in authenticator
itself anyway?) so I've decided to create even less intrusive fix.
It simply prevents overwrites of fCurrentAuthenticator unless this is
necessary. This way there are no behaviour changes visible on
application side (resends were handled by RTSPClient internally), so
please review attached patch. 

Note that I'm not an RTSP specialist of any kind and everything above is
just my yesterday's observations, whether this is close to proper or any
other solution is required is up to you, I can't assist in this issue
anymore.

One more suggestion if I might, that it would be nice to be able to
forcefully disable Basic auth. In current code this could be enforced by
assuring nonce != NULL, e.g. by setting it to empty string ("") in
authenticator, but this is not straightforward and might change in
future.

regards,
-- 
Tomasz Pala <gotar at pld-linux.org>
-------------- next part --------------
diff -ur -x .svn -x .git -x .bzr -x CVS -ur live/liveMedia/DigestAuthentication.cpp live.new/liveMedia/DigestAuthentication.cpp
--- live/liveMedia/DigestAuthentication.cpp	2014-10-28 22:09:59.000000000 +0100
+++ live.new/liveMedia/DigestAuthentication.cpp	2014-11-01 10:04:01.884124153 +0100
@@ -48,6 +55,17 @@
   return *this;
 }
 
+Boolean Authenticator::operator<(const Authenticator* rightSide) {
+  if (rightSide != NULL && rightSide != this && (
+      rightSide->realm() != NULL ||
+      rightSide->nonce() != NULL ||
+      strcmp(rightSide->username(), this->username()) ||
+      strcmp(rightSide->password(), this->password()) ) )
+    return True;
+
+  return False;
+}
+
 Authenticator::~Authenticator() {
   reset();
 }
diff -ur -x .svn -x .git -x .bzr -x CVS -ur live/liveMedia/include/DigestAuthentication.hh live.new/liveMedia/include/DigestAuthentication.hh
--- live/liveMedia/include/DigestAuthentication.hh	2014-10-28 22:09:59.000000000 +0100
+++ live.new/liveMedia/include/DigestAuthentication.hh	2014-11-01 09:55:41.113389683 +0100
@@ -37,6 +37,7 @@
       // by md5(<username>:<realm>:<actual-password>)
   Authenticator(const Authenticator& orig);
   Authenticator& operator=(const Authenticator& rightSide);
+  Boolean operator<(const Authenticator* rightSide);
   virtual ~Authenticator();
 
   void reset();
diff -ur -x .svn -x .git -x .bzr -x CVS -ur live/liveMedia/RTSPClient.cpp live.new/liveMedia/RTSPClient.cpp
--- live/liveMedia/RTSPClient.cpp	2014-10-28 22:09:59.000000000 +0100
+++ live.new/liveMedia/RTSPClient.cpp	2014-11-01 09:55:13.433349096 +0100
@@ -37,7 +37,7 @@
 }
 
 unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
 }
 
@@ -47,7 +52,7 @@
 }
 
 unsigned RTSPClient::sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "ANNOUNCE", responseHandler, NULL, NULL, False, 0.0, 0.0, 0.0, sdpDescription));
 }
 
@@ -55,7 +60,7 @@
                                       Boolean streamOutgoing, Boolean streamUsingTCP, Boolean forceMulticastOnUnspecified,
 				      Authenticator* authenticator) {
   if (fTunnelOverHTTPPortNum != 0) streamUsingTCP = True; // RTSP-over-HTTP tunneling uses TCP (by definition)
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
 
   u_int32_t booleanFlags = 0;
   if (streamUsingTCP) booleanFlags |= 0x1;
@@ -67,7 +72,7 @@
 unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
                                      double start, double end, float scale,
                                      Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   sendDummyUDPPackets(session); // hack to improve NAT traversal
   return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, &session, NULL, 0, start, end, scale));
 }
@@ -75,7 +80,7 @@
 unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
                                      double start, double end, float scale,
                                      Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   sendDummyUDPPackets(subsession); // hack to improve NAT traversal
   return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, NULL, &subsession, 0, start, end, scale));
 }
@@ -83,7 +88,7 @@
 unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
 				     char const* absStartTime, char const* absEndTime, float scale,
                                      Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   sendDummyUDPPackets(session); // hack to improve NAT traversal
   return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, &session, NULL));
 }
@@ -91,45 +96,45 @@
 unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
 				     char const* absStartTime, char const* absEndTime, float scale,
                                      Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   sendDummyUDPPackets(subsession); // hack to improve NAT traversal
   return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, NULL, &subsession));
 }
 
 unsigned RTSPClient::sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, &session));
 }
 
 unsigned RTSPClient::sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, NULL, &subsession));
 }
 
 unsigned RTSPClient::sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, &session));
 }
 
 unsigned RTSPClient::sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, NULL, &subsession));
 }
 
 unsigned RTSPClient::sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, &session));
 }
 
 unsigned RTSPClient::sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, NULL, &subsession));
 }
 
 unsigned RTSPClient::sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler,
                                              char const* parameterName, char const* parameterValue,
                                              Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
   char* paramString = new char[strlen(parameterName) + strlen(parameterValue) + 10];
   sprintf(paramString, "%s: %s\r\n", parameterName, parameterValue);
   unsigned result = sendRequest(new RequestRecord(++fCSeq, "SET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
@@ -139,7 +144,7 @@
 
 unsigned RTSPClient::sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName,
                                              Authenticator* authenticator) {
-  if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
+  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
 
   // We assume that:
   //    parameterName is NULL means: Send no body in the request.


More information about the live-devel mailing list