EasyDarwin拉流支持基本认证和摘要认证(2)

这篇文章排版较差,大家请参照http://blog.csdn.net/xiejiashu/article/details/52331342

在前面《EasyDarwin拉流支持基本认证和摘要认证》一文中讲述了如何通过修改qtaccessqtusers来让EasyDarwin对我们创建的用户支持基本认证和摘要认证,之后在与群主的沟通中感觉这种方式的体验性太差,用户的需求是多方面的,可能有的想在配置文件中配置、有的想从数据库中读取、有的想在程序中写死……,我们需要提供一种便于用户自己扩展的方式,而不是与qtaccessqtusers来打交道。

综上考虑,我们想实现在程序中指定用户名、密码的方式来作为用户后续扩展的一种呈现形式,这样无论用户是想从配置文件获取或者从数据库读取,代码的修改都比较简单。

下面的工作就是讲解如何修改EasyDarwin的代码来实现我们的目的。

使用工具:推流使用EasyPusher_File,下载地址为https://github.com/EasyDarwin/EasyPusher;拉流使用VLC

首先下载EasyDarwin工程代码,地址在https://github.com/EasyDarwin/EasyDarwin,当前文档使用的是20160826下载的代码来进行操作的。然后按照文档进行编译运行,在调试之前我们先参考http://blog.csdn.net/cai6811376/article/details/52063666QTSSAccessModule.cpp文件添加调试信息。之后我们尝试进行推流、拉流发现并没有用户认证提示,这是因为EasyDarwin默认的是跳过了对认证的处理,这段代码在RTSPSession::Run函数kRoutingRequest状态的处理中:

if (fRequest->SkipAuthorization())
                                                             {
                                                                            // Skip the authentication and authorization states
 
                                                                            // The foll. normally gets executed at the end of the authorization state 
                                                                            // Prepare for kPreprocessingRequest state.
                                                                            fState = kPreprocessingRequest;
 
                                                                            if (fRequest->GetMethod() == qtssSetupMethod)
                                                                                            // Make sure to erase the session ID stored in the request at this point.
                                                                                            // If we fail to do so, this same session would be used if another
                                                                                            // SETUP was issued on this same TCP connection.
                                                                                            fLastRTPSessionIDPtr.Len = 0;
                                                                            else if (fLastRTPSessionIDPtr.Len == 0)
                                                                                            fLastRTPSessionIDPtr.Len = ::strlen(fLastRTPSessionIDPtr.Ptr);
 
                                                                            break;
                                                             }
                                                             else
                                                                            fState = kAuthenticatingRequest;



可以看到fRequest->SkipAuthorization()的返回值决定了我们是否进行认证,在此我们将fRequest->SkipAuthorization()更改为if(false),然后在QTSSAccessModule.cpp中进行相应的更改:


1.添加默认用户全局变量

static QTSS_AuthScheme sAuth = qtssAuthDigest;
static StrPtrLen  sAuthRealm = "EasyDarwin";
static StrPtrLen  sUserName  = "admin";
static StrPtrLen  sPassword  = "admin";
static StrPtrLen  sDigestPassword;

以上几个量分别表示用于认证的认证方法、认证领域、用户名、密码、摘要认证密码

2.Initialize()函数中添加以下语句来计算上面添加的摘要认证密码

          StrPtrLen strptr;
               CalcMD5HA1(&sUserName, &sAuthRealm, &sPassword, &strptr);
               HashToString((unsigned char *)strptr.Ptr, &sDigestPassword);

这里面根据sAuthRealm sUserNamesPassword来计算sDigestPassword,使用了md5运算,需要进行头文件包含#include "md5digest.h"
3.AuthenticateRTSPRequest()函数由之前的内容的更改为

QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams)
{
               QTSS_RTSPRequestObject  theRTSPRequest = inParams->inRTSPRequest;
               UInt32 fileErr;
 
               OSMutexLocker locker(sUserMutex);
 
               if ((NULL == inParams) || (NULL == inParams->inRTSPRequest))
                               return QTSS_RequestFailed;
               // Get the user profile object from the request object
               QTSS_UserProfileObject theUserProfile = NULL;
               UInt32 len = sizeof(QTSS_UserProfileObject);
               QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqUserProfile, 0, (void*)&theUserProfile, &len);
               Assert(len == sizeof(QTSS_UserProfileObject));
               if (theErr != QTSS_NoErr)
                               return theErr;
               if (sAuth == qtssAuthNone)
               {
                               // Get the authentication scheme from the request object
                               len = sizeof(sAuth);
                               theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&sAuth, &len);
                               Assert(len == sizeof(sAuth));
                               if (theErr != QTSS_NoErr)
                                              return theErr;
               }
               else
               {
                               theErr = QTSS_SetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&sAuth, sizeof(sAuth));
                               if (theErr != QTSS_NoErr)
                                              return theErr;
               }
 
               (void)QTSS_SetValue(theUserProfile, qtssUserRealm, 0, (void*)(sAuthRealm.Ptr), (sAuthRealm.Len));
 
               // Get the username from the user profile object
               char*   usernameBuf = NULL;
               theErr = QTSS_GetValueAsString(theUserProfile, qtssUserName, 0, &usernameBuf);
               OSCharArrayDeleter usernameBufDeleter(usernameBuf);
               StrPtrLen username(usernameBuf);
               if (theErr != QTSS_NoErr)
                               return theErr;
               // No memory is allocated; just a pointer to the profile is returned
               if(!username.Equal(sUserName))//用户名不相等
               {
                               return QTSS_NoErr;
               }
               if (sAuth == qtssAuthBasic)
                               (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)(sPassword.Ptr), sPassword.Len);
               else if (sAuth == qtssAuthDigest)
               {
                               (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)(sDigestPassword.Ptr), sDigestPassword.Len);
               }
               return QTSS_NoErr;
}

之前的代码有很多是读取qtaccessqtusers的操作,比较繁琐,现在只用判断请求的用户名与我们设置的用户名是否一致,如果一致就将密码(基本认证)/摘要认证密码(摘要认证)写入到用户的theUserProfile对象中,之后在模块外进行密码比较的工作。

4.AccessAuthorizeRTSPRequest()函数代码更改为

QTSS_Error AccessAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams)
{
               Bool16 allowNoAccessFiles = sAllowGuestDefaultEnabled; //no access files allowed means allowing guest access (unknown users)
               QTSS_ActionFlags noAction = ~qtssActionFlagsRead; // allow any action
               QTSS_ActionFlags authorizeAction = QTSSModuleUtils::GetRequestActions(inParams->inRTSPRequest);
 
               
               QTSS_Error theErr = QTSS_NoErr;
               if  ( (NULL == inParams) || (NULL == inParams->inRTSPRequest))
                               return QTSS_RequestFailed;
               QTSS_RTSPRequestObject  theRTSPRequest = inParams->inRTSPRequest;
               QTSS_UserProfileObject theUserProfile = QTSSModuleUtils::GetUserProfileObject(theRTSPRequest);
               if (NULL == theUserProfile)
                               return QTSS_RequestFailed;
 
               char* username = QTSSModuleUtils::GetUserName_Copy(theUserProfile);
               OSCharArrayDeleter usernameDeleter(username);
 
               Bool16 allowRequest;
               if(username == NULL)
               {
                               allowRequest = false;
               }
               else
               {
                               allowRequest = (strcmp(username, sUserName.Ptr) == 0)?TRUE:FALSE;
               }
 
               Bool16 founduser = allowRequest;
               Bool16 authContinue = true;
               QTSS_SetValue(theRTSPRequest,qtssRTSPReqURLRealm, 0, sAuthRealm.Ptr, ::strlen(sAuthRealm.Ptr));
               
               if (allowRequest)
                               theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &founduser,&authContinue);
               if (!allowRequest)
                               theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &founduser,&authContinue);
               return theErr;
               
}

这个函数对功能是对读写权限进行判断,我们现在的更改比较简单,只要用户名密码正确,就拥有所有的读写权限。

好,经过了这些我们在再次运行程序,发现一推流就崩溃,最后定位到QTAccessFile.cpp->AuthorizeRequest->ReadEntireFile函数,这是因为之前在做SDP缓存的时候修改了QTSSModuleUtils::ReadEntireFile的代码,我们在QTSSModuleUtils.cpp中添加如下函数以及在QTSSModuleUtils.h中添加相应的函数声明。

QTSS_Error QTSSModuleUtils::ReadEntireFileEx(char* inPath, StrPtrLen* outData, QTSS_TimeVal inModDate, QTSS_TimeVal* outModDate)
{              
               QTSS_Object theFileObject = NULL;
               QTSS_Error theErr = QTSS_NoErr;
 
               outData->Ptr = NULL;
               outData->Len = 0;
               do { 
                               // Use the QTSS file system API to read the file
                               theErr = QTSS_OpenFileObject(inPath, 0, &theFileObject);
                               if (theErr != QTSS_NoErr)
                                              break;
                               UInt32 theParamLen = 0;
                               QTSS_TimeVal* theModDate = NULL;
                               theErr = QTSS_GetValuePtr(theFileObject, qtssFlObjModDate, 0, (void**)&theModDate, &theParamLen);
                               Assert(theParamLen == sizeof(QTSS_TimeVal));
                               if(theParamLen != sizeof(QTSS_TimeVal))
                                              break;
                               if(outModDate != NULL)
                                              *outModDate = (QTSS_TimeVal)*theModDate;
 
                               if(inModDate != -1) {   
                                              // If file hasn't been modified since inModDate, don't have to read the file
                                              if(*theModDate <= inModDate)
                                                             break;
                               }
                               theParamLen = 0;
                               UInt64* theLength = NULL;
                               theErr = QTSS_GetValuePtr(theFileObject, qtssFlObjLength, 0, (void**)&theLength, &theParamLen);
                               if (theParamLen != sizeof(UInt64))
                                              break;
                               if (*theLength > kSInt32_Max)
                                              break;
                               // Allocate memory for the file data
                               outData->Ptr = NEW char[ (SInt32) (*theLength + 1) ];
                               outData->Len = (SInt32) *theLength;
                               outData->Ptr[outData->Len] = 0;
                               // Read the data
                               UInt32 recvLen = 0;
                               theErr = QTSS_Read(theFileObject, outData->Ptr, outData->Len, &recvLen);
                               if (theErr != QTSS_NoErr)
                               {
                                              outData->Delete();
                                              break;
                               }   
                               Assert(outData->Len == recvLen);
               }while(false);
 
               // Close the file
               if(theFileObject != NULL) {
                               theErr = QTSS_CloseFileObject(theFileObject);
               }
               return theErr;
}

之后将QTAccessFile.cpp->AuthorizeRequest中使用到的ReadEntireFile都改为使用ReadEntireFileEx函数。
再次进行推流测试,发现推流一直返回401,这是因为推送库还没有RTSP认证的功能,我们先不做推流验证。因此我们将前面的认证开关由if(false)更改为if(fRequest->GetMethod()  != qtssDescribeMethod),也就说只要是推流就跳过认证。(这个地方不太完美,但还找到完美的方法。)

再次进行推流测试,发现这次可以推流成功;然后我们进行拉流测试发现程序又崩溃了,定位到QTSSAccessLogModule.cppLogRequest函数中,在此我们将

UInt32 clientBytesRecv = (UInt32)((*rtpBytesSent * (100.0 - *packetLossPercent)) / 100.0);

更改为

               UInt32 clientBytesRecv = 0;
               if(packetLossPercent != NULL)
                               clientBytesRecv = (UInt32)((*rtpBytesSent * (100.0 - *packetLossPercent)) / 100.0);

if (*rtpPacketsSent == 0)更改为if(rtpPacketsSent == NULL || *rtpPacketsSent == 0) 

之后再进行测试,发现不会崩溃了,但是一直提示进行RTSP认证,即使我们输入了上面设置的正确用户名和密码还是不行。原来在进行基本认证时不同平台的处理是不一样的,我们通过将如下的代码

#ifdef __Win32__
                            // The password is md5 encoded for win32
                            char md5EncodeResult[120];
                            // no memory is allocated in this function call
                            MD5Encode(reqPasswdStr, userPasswdStr, md5EncodeResult,sizeof(md5EncodeResult));
                            if (::strcmp(userPasswdStr, md5EncodeResult) != 0)
                                     authenticated = false;
#else
                            if (::strcmp(userPasswdStr, (char*)crypt(reqPasswdStr,userPasswdStr)) != 0)
                                     authenticated = false;
#endif

更改为

#if  0
                            // The password is md5 encoded for win32
                            char md5EncodeResult[120];
                            // no memory is allocated in this function call
                            MD5Encode(reqPasswdStr, userPasswdStr, md5EncodeResult,sizeof(md5EncodeResult));
                            if (::strcmp(userPasswdStr, md5EncodeResult) != 0)
                                     authenticated = false;
#else
                            if (::strcmp(userPasswdStr, (char*)crypt(reqPasswdStr,userPasswdStr)) != 0)
                                     authenticated = false;
#endif
我们发现windows下基本验证的时候对密码又进行了md5运算,这不是我们想要的,我们把它改为一致。

再次进行测试,发现还是不行……经排查,发现是对RTSP请求的清理没有到位,将RTSPSession::CleanupRequest由下面代码

void RTSPSession::CleanupRequest()
{
               if (fRTPSession != NULL)
               {
                               // Release the ref.
                               OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap();
                               theMap->Release(fRTPSession->GetRef());
 
                               // NULL out any references to this RTP session
                               fRTPSession = NULL;
                               fRoleParams.rtspRequestParams.inClientSession = NULL;
               }
               if (this->IsLiveSession() == false) //clear out the ID so it can't be re-used.
               {
                               fLastRTPSessionID[0] = 0;
                               fLastRTPSessionIDPtr.Set(fLastRTPSessionID, 0);
               }
               if (fRequest != NULL)
               {
                               // Check to see if a filter module has replaced the request. If so, delete
                               // their request now.
                               if (fRequest->GetValue(qtssRTSPReqFullRequest) && fInputStream.GetRequestBuffer())
                               {
                                              if (fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr != fInputStream.GetRequestBuffer()->Ptr)
                                                             delete[] fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr;
                               }
 
                               // NULL out any references to the current request
                               //delete fRequest;
                               //fRequest = NULL;
                               fRoleParams.rtspRequestParams.inRTSPRequest = NULL;
                               fRoleParams.rtspRequestParams.inRTSPHeaders = NULL;
               }
 
               fSessionMutex.Unlock();
               fReadMutex.Unlock();
 
               // Clear out our last value for request body length before moving onto the next request
               this->SetRequestBodyLength(-1);
}

更改为下面代码

void RTSPSession::CleanupRequest()
{
               if (fRTPSession != NULL)
               {
                               // Release the ref.
                               OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap();
                               theMap->Release(fRTPSession->GetRef());
 
                               // NULL out any references to this RTP session
                               fRTPSession = NULL;
                               fRoleParams.rtspRequestParams.inClientSession = NULL;
               }
               if (this->IsLiveSession() == false) //clear out the ID so it can't be re-used.
               {
                               fLastRTPSessionID[0] = 0;
                               fLastRTPSessionIDPtr.Set(fLastRTPSessionID, 0);
               }
               if (fRequest != NULL)
               {
                               // Check to see if a filter module has replaced the request. If so, delete
                               // their request now.
                               if (fRequest->GetValue(qtssRTSPReqFullRequest) && fInputStream.GetRequestBuffer())
                               {
                                              if (fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr != fInputStream.GetRequestBuffer()->Ptr)
                                                             delete[] fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr;
                               }
 
                               // NULL out any references to the current request
                               delete fRequest;
                               fRequest = NULL;
                               fRoleParams.rtspRequestParams.inRTSPRequest = NULL;
                               fRoleParams.rtspRequestParams.inRTSPHeaders = NULL;
               }
               fSessionMutex.Unlock();
               fReadMutex.Unlock();
 
               // Clear out our last value for request body length before moving onto the next request
               this->SetRequestBodyLength(-1);
}

再次测试,OK!

如有错误,欢迎指正!


 



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值