UtlBoolean SipDialogMgr::isNewRemoteTransaction(const SipMessage& message) { UtlBoolean matchesTransaction = FALSE; UtlString handle; SipDialog::getDialogHandle(message, handle); UtlString callId; UtlString fromTag; UtlString toTag; Url fromField; Url toField; message.getFromUrl(fromField); message.getToUrl(toField); message.getCallIdField(callId); fromField.getFieldParameter("tag", fromTag); toField.getFieldParameter("tag", toTag); lock(); // Looking for any dialog that matches this handle SipDialog* dialog = findDialog(handle, TRUE, // if established, match early dialog TRUE); // if early, match established dialog if(dialog && dialog->isTransactionRemotelyInitiated(callId, fromTag, toTag) && dialog->isNextRemoteCseq(message)) { matchesTransaction = TRUE; } unlock(); return(matchesTransaction); }
UtlString SessionContext::getDiscriminatingTagValue( const SipMessage& message ) const { UtlString discriminatingTag; Url tempUrl; // We do not know the directionality of the message. In this case // we cannot tell if the discriminating tag will come from the From: // or To: header. Return the one that does not match the dialog's // original From-tag. // Look at the To-Tag first message.getToUrl( tempUrl ); tempUrl.getFieldParameter( "tag", discriminatingTag ); if( discriminatingTag == mDialogOriginalFromTag ) { message.getFromUrl( tempUrl ); tempUrl.getFieldParameter( "tag", discriminatingTag ); } return discriminatingTag; }
UtlBoolean SipDialogMgr::isLastLocalTransaction(const SipMessage& message, const char* dialogHandle) { UtlBoolean matchesTransaction = FALSE; UtlString handle(dialogHandle ? dialogHandle : ""); // If the dialog handle was not set, get it from the message if(handle.isNull()) { SipDialog::getDialogHandle(message, handle); } UtlString callId; UtlString fromTag; UtlString toTag; Url fromField; Url toField; message.getFromUrl(fromField); message.getToUrl(toField); message.getCallIdField(callId); fromField.getFieldParameter("tag", fromTag); toField.getFieldParameter("tag", toTag); lock(); // Looking for any dialog that matches this handle SipDialog* dialog = findDialog(handle, TRUE, // if established, match early dialog TRUE); // if early, match established dialog if(dialog && dialog->isTransactionLocallyInitiated(callId, fromTag, toTag) && dialog->isSameLocalCseq(message)) { matchesTransaction = TRUE; } unlock(); return(matchesTransaction); }
UtlBoolean SipDialog::isSameDialog(const SipMessage& message) const { UtlString messageCallId; message.getCallIdField(&messageCallId); UtlBoolean isSameDialog = FALSE; if(messageCallId.compareTo(*this, UtlString::ignoreCase) == 0) { Url messageFromUrl; message.getFromUrl(messageFromUrl); UtlString messageFromTag; messageFromUrl.getFieldParameter("tag", messageFromTag); if(messageFromTag.compareTo(mLocalTag, UtlString::ignoreCase) == 0) { Url messageToUrl; message.getToUrl(messageToUrl); UtlString messageToTag; messageToUrl.getFieldParameter("tag", messageToTag); if(messageToTag.compareTo(mRemoteTag, UtlString::ignoreCase) == 0) { isSameDialog = TRUE; } } else if(messageFromTag.compareTo(mRemoteTag, UtlString::ignoreCase) == 0) { Url messageToUrl; message.getToUrl(messageToUrl); UtlString messageToTag; messageToUrl.getFieldParameter("tag", messageToTag); if(messageToTag.compareTo(mLocalTag, UtlString::ignoreCase) == 0) { isSameDialog = TRUE; } } } return(isSameDialog); }
/// Decode the identity from a message. SipXauthIdentity::SipXauthIdentity(const SipMessage& message, const HeaderName headerName, DialogRule bindRule ) : mIsValidIdentity(FALSE) { UtlString callId; UtlString fromTag; Url fromUrl; message.getCallIdField(&callId); message.getFromUrl(fromUrl); fromUrl.getFieldParameter("tag", fromTag); decode(headerName, message, callId, fromTag, bindRule); }
/// Add identity info to a message. bool SipXauthIdentity::insert(SipMessage & message, HeaderName headerName, const OsDateTime * timestamp) { // Don't proceed if the encapsulated identity is invalid if (!mIsValidIdentity) { Os::Logger::instance().log(FAC_SIP, PRI_CRIT, "SipXauthIdentity::insert: " "encapsulated SipXauthIdentity is invalid"); } else { // make sure no existing identity in the message remove(message, headerName); // set Call-Id and from-tag for the signature calculation UtlString callId; UtlString fromTag; Url fromUrl; message.getCallIdField(&callId); message.getFromUrl(fromUrl); fromUrl.getFieldParameter("tag", fromTag); OsDateTime now; OsDateTime::getCurTime(now); if (NULL==timestamp) { timestamp = &now; } UtlString value; encode(value, callId, fromTag, *timestamp); // Insert displayName if it is an P-Asserted-Identity header. if (headerName == SipXauthIdentity::PAssertedIdentityHeaderName) { UtlString displayName; fromUrl.getDisplayName(displayName); value.prepend(displayName.data()); } message.addHeaderField(headerName, value.data()); } return mIsValidIdentity; }
UtlString SessionContext::getDiscriminatingTagValue( const SipMessage& message, bool bFromCallerToCallee ) const { UtlString discriminatingTag; Url tempUrl; if( bFromCallerToCallee ) { // caller-to-callee uses To-tag to distinguish between dialogs message.getToUrl( tempUrl ); } else { // callee-to-caller uses From-tag to distinguish between dialogs message.getFromUrl( tempUrl ); } tempUrl.getFieldParameter( "tag", discriminatingTag ); return discriminatingTag; }
/// Encode identity info into a URL bool SipXauthIdentity::encodeUri(Url & uri, const char* pCallId, const Url fromUrl, const OsDateTime * timestamp) { // Don't proceed if the encapsulated identity is invalid if (!mIsValidIdentity) { Os::Logger::instance().log(FAC_SIP, PRI_CRIT, "SipXauthIdentity::encodeUri[no msg]: " "encapsulated SipXauthIdentity is invalid"); } else { // make sure no existing identity in the URI uri.removeHeaderParameter(SipXauthIdentity::PAssertedIdentityHeaderName); // set Call-Id and from-tag for the signature calculation UtlString callId(pCallId); UtlString fromTag; fromUrl.getFieldParameter("tag", fromTag); OsDateTime now; OsDateTime::getCurTime(now); if (NULL==timestamp) { timestamp = &now; } UtlString value; encode(value, callId, fromTag, *timestamp); uri.setHeaderParameter(SipXauthIdentity::PAssertedIdentityHeaderName, value.data()); Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "SipXauthIdentity::encodeUri[o msg] " "encoded URI '%s'", uri.toString().data() ); } return mIsValidIdentity; }
/// Decode the identity from a message by searching for SipXauthIdentity then P-Asserted-Identity SipXauthIdentity::SipXauthIdentity( const SipMessage& message, UtlString& matchedHeaderName, bool bSipXauthIdentityTakesPrecedence, DialogRule bindRule ) : mIsValidIdentity(FALSE) { UtlString callId; UtlString fromTag; Url fromUrl; message.getCallIdField(&callId); message.getFromUrl(fromUrl); fromUrl.getFieldParameter("tag", fromTag); matchedHeaderName.remove(0); HeaderName firstHeaderToTest; HeaderName secondHeaderToTest; if( bSipXauthIdentityTakesPrecedence == true ) { firstHeaderToTest = AuthIdentityHeaderName; secondHeaderToTest = PAssertedIdentityHeaderName; } else { firstHeaderToTest = PAssertedIdentityHeaderName; secondHeaderToTest = AuthIdentityHeaderName; } if( decode(firstHeaderToTest, message, callId, fromTag, bindRule) ) { matchedHeaderName = firstHeaderToTest; } else if( decode(secondHeaderToTest, message, callId, fromTag, bindRule) ) { matchedHeaderName = secondHeaderToTest; } }
void SipDialog::updateDialogData(const SipMessage& message) { UtlString messageCallId; message.getCallIdField(&messageCallId); Url messageFromUrl; message.getFromUrl(messageFromUrl); UtlString messageFromTag; messageFromUrl.getFieldParameter("tag", messageFromTag); Url messageToUrl; message.getToUrl(messageToUrl); UtlString messageToTag; messageToUrl.getFieldParameter("tag", messageToTag); int cSeq; UtlString method; message.getCSeqField(&cSeq, &method); int responseCode = message.getResponseStatusCode(); // Figure out if the request is from the local or // the remote side if(isTransactionLocallyInitiated(messageCallId, messageFromTag, messageToTag)) { // This message is part of a transaction initiated by // the local side of the dialog if(cSeq > mLastLocalCseq) { mLastLocalCseq = cSeq; } if(cSeq >= mLastLocalCseq) { // Always update the contact if it is set UtlString messageContact; // Get the Contact value, but as an addr-spec. if(message.getContactUri(0, &messageContact) && !messageContact.isNull()) { if(message.isResponse()) { mRemoteContact.fromString(messageContact, TRUE); } else { mLocalContact.fromString(messageContact, TRUE); } } } // Cannot assume that we only establish a dialog with the // initial cseq. For example if there is an authentication // challenge, the dialog will not be established until the // second transaction. if(cSeq == mLastLocalCseq) { // A successful response to an INVITE or SUBSCRIBE // make this early dialog a set up dialog if(mLocalInitiatedDialog && message.isResponse() && responseCode >= SIP_2XX_CLASS_CODE && // successful dialog setup responseCode < SIP_3XX_CLASS_CODE && mRemoteTag.isNull() && // tag not set mRouteSet.isNull()) // have not yet set the route set { // Change this early dialog to a set up dialog. // The tag gets set in the 2xx response // so we need to update the URL message.getToUrl(mRemoteField); mRemoteField.getFieldParameter("tag", mRemoteTag); // Need to get the route set as well // Make sure the Request Method is allowed to set Record-Routes if(message.isRecordRouteAccepted()) { message.buildRouteField(&mRouteSet); } } } } else if(isTransactionRemotelyInitiated(messageCallId, messageFromTag, messageToTag)) { int prevRemoteCseq = mLastRemoteCseq; // This message is part of a transaction initiated by // the callee/destination of the session if(cSeq > mLastRemoteCseq) { mLastRemoteCseq = cSeq; } if(cSeq >= mLastRemoteCseq) { // Always update the contact if it is set UtlString messageContact; // Get the Contact value, but as an addr-spec. if(message.getContactUri(0, &messageContact) && !messageContact.isNull()) { if(message.isResponse()) { mLocalContact.fromString(messageContact, TRUE); } else { mRemoteContact.fromString(messageContact, TRUE); } } } // First transaction from the otherside if(cSeq == mLastRemoteCseq && prevRemoteCseq == -1) { // A response (e.g. NOTIFY) can come before we get the // successful response to the initial transaction if(!mLocalInitiatedDialog && !message.isResponse() && mRemoteTag.isNull()) // tag not set { // Change this early dialog to a set up dialog. // The tag gets set in the 2xx response // so we need to update the URL message.getFromUrl(mRemoteField); mRemoteField.getFieldParameter("tag", mRemoteTag); } } // First successful response from the local side if(cSeq == mLastRemoteCseq) { if(!mLocalInitiatedDialog && message.isResponse() && responseCode >= SIP_2XX_CLASS_CODE && // successful dialog setup responseCode < SIP_3XX_CLASS_CODE && mLocalTag.isNull()) { // Update the local tag message.getToUrl(mLocalField); mLocalField.getFieldParameter("tag", mLocalTag); } } } }
void getMessageData(UtlString& content, UtlBoolean isOutgoing, UtlString& date, UtlString& hostname, UtlString& eventCount, int outputFileDescriptor) { UtlString remoteHost; UtlString remoteAddress; UtlString remotePort; UtlString remoteSourceAddress; UtlString message; UtlString branchId; UtlString transactionId; UtlString method; UtlString responseCode; UtlString responseText; UtlBoolean failed = FALSE; int hostIndex = content.index("----Remote Host:"); if(hostIndex > 0) { hostIndex += 16; int hostEnd = content.index("----", hostIndex); remoteHost.append(&(content.data()[hostIndex]), hostEnd - hostIndex); remoteAddress = remoteHost; remoteHost.append(":"); int portIndex = hostEnd + 11; int portEnd = content.index("----", portIndex); remotePort.append(&(content.data()[portIndex]), portEnd - portIndex); remoteHost.append(remotePort); int messageIndex = portEnd + 5; size_t messageEnd; if(isOutgoing) { messageEnd = content.index("--------------------END", messageIndex); // Record whether the send failed or not. failed = (content.index("User Agent failed to send message") != UTL_NOT_FOUND); } else { messageEnd = content.index("====================END", messageIndex); } if ( UTL_NOT_FOUND == messageEnd ) { messageEnd = content.length(); } message.append(&(content.data()[messageIndex]), messageEnd - messageIndex); SipMessage sipMsg(message); remoteSourceAddress = remoteHost; if(sipMsg.isResponse()) { sipMsg.getFirstHeaderLinePart(1, &responseCode); if (failed) { responseCode = responseCode + " FAILED"; } sipMsg.getFirstHeaderLinePart(2, &responseText); } else { // Get the method. sipMsg.getRequestMethod(&method); // If it is a re-INVITE, make that clear. if (method.compareTo("INVITE", UtlString::ignoreCase) == 0) { Url to; sipMsg.getToUrl(to); UtlString toTag; to.getFieldParameter("tag", toTag); if (!toTag.isNull()) { method = "re-INVITE"; } } // Prepend FAILED if it is a failed transmission. if (failed) { method = "FAILED " + method; } //We can derive the source entity from the via in // incoming requests if(!isOutgoing) { UtlString viaAddress; UtlString protocol; int viaPortNum; sipMsg.getLastVia(&viaAddress, &viaPortNum, &protocol); char numBuff[30]; sprintf(numBuff, "%d", viaPortNum); UtlString viaPort(numBuff); remoteHost = remoteAddress + ":" + viaPort; } } // transaction token: cseq,call-id,from-tag,to=tag int cseq; UtlString cseqMethod; sipMsg.getCSeqField(&cseq, &cseqMethod); char numBuf[20]; sprintf(numBuf, "%d", cseq); UtlString callId; sipMsg.getCallIdField(&callId); Url to; sipMsg.getToUrl(to); UtlString toTag; to.getFieldParameter("tag", toTag); Url from; sipMsg.getFromUrl(from); UtlString fromTag; from.getFieldParameter("tag", fromTag); transactionId = numBuf; transactionId.append(","); transactionId.append(callId); transactionId.append(","); transactionId.append(fromTag); transactionId.append(","); transactionId.append(toTag); // Write all the stuff out // Write out the node container start writeBranchNodeBegin(outputFileDescriptor); // Write out the branchId container start writeBranchSetBegin(outputFileDescriptor); // Write out the branchIds int viaIndex = 0; UtlString topVia; while(sipMsg.getViaFieldSubField(&topVia, viaIndex)) { SipMessage::getViaTag(topVia.data(), "branch", branchId); writeBranchId(outputFileDescriptor, branchId); viaIndex++; } // Write out the branchId container finish writeBranchSetEnd(outputFileDescriptor); // Write out the rest of the node data writeBranchNodeData(outputFileDescriptor, date, isOutgoing ? hostname : remoteHost, isOutgoing ? remoteHost : hostname, isOutgoing ? hostname : remoteSourceAddress, isOutgoing ? remoteSourceAddress : hostname, transactionId, eventCount, method, responseCode, responseText, message); // Write out the node container finish writeBranchNodeEnd(outputFileDescriptor); } }
// XECS-1810: Verify that subscription is *not* terminated when NOTIFY // returns a Timeout error. void terminateSubscriptionOnErrorTimeout() { // Test MWI messages const char* mwiSubscribe = "SUBSCRIBE sip:111@localhost SIP/2.0\r\n" "From: <sip:[email protected]>;tag=1612c1612\r\n" "To: <sip:[email protected]>\r\n" "Cseq: 1 SUBSCRIBE\r\n" "Event: message-summary\r\n" "Accept: application/simple-message-summary\r\n" "Expires: 3600\r\n" "Date: Tue, 4 Nov 2008 15:59:30 GMT\r\n" "Max-Forwards: 20\r\n" "User-Agent: Pingtel/2.2.0 (VxWorks)\r\n" "Accept-Language: en\r\n" "Supported: sip-cc, sip-cc-01, timer, replaces\r\n" "Content-Length: 0\r\n" "\r\n"; // Send a SUBSCRIBE to ourselves SipMessage mwiSubscribeRequest(mwiSubscribe); { UtlString c; CallId::getNewCallId(c); mwiSubscribeRequest.setCallIdField(c); } mwiSubscribeRequest.setSipRequestFirstHeaderLine(SIP_SUBSCRIBE_METHOD, aor, SIP_PROTOCOL_VERSION); mwiSubscribeRequest.setContactField(aor_name_addr); mwiSubscribeRequest.incrementCSeqNumber(); CPPUNIT_ASSERT(userAgentp->send(mwiSubscribeRequest)); // We should get a 202 response and a NOTIFY request in the queue // Send a Timeout error response to the NOTIFY. OsTime messageTimeout(1, 0); // 1 second { const SipMessage* subscribeResponse; const SipMessage* notifyRequest; runListener(incomingClientMsgQueue, *userAgentp, messageTimeout, messageTimeout, notifyRequest, subscribeResponse, SIP_REQUEST_TIMEOUT_CODE, FALSE, 0, NULL); // We should have received a SUBSCRIBE response and a NOTIFY request. CPPUNIT_ASSERT(subscribeResponse); CPPUNIT_ASSERT(subscribeResponse->getResponseStatusCode() == SIP_ACCEPTED_CODE); CPPUNIT_ASSERT(notifyRequest); // Extract the to-tag in the response, apply it to mwiSubscribeRequest. // This allows the re-SUBSCRIBE below to be applied to the existing dialog. Url toUrl; subscribeResponse->getToUrl(toUrl); UtlString toTag; toUrl.getFieldParameter("tag", toTag); mwiSubscribeRequest.setToFieldTag(toTag); } // Send a re-SUBSCRIBE in the existing dialog, to find out if the // subscription was terminated or not. mwiSubscribeRequest.incrementCSeqNumber(); // Leave the Expires header with the default value. CPPUNIT_ASSERT(userAgentp->send(mwiSubscribeRequest)); // We should get a 202 response and a NOTIFY, because the Timeout // error suppresses the termination of the subscription. { const SipMessage* subscribeResponse; const SipMessage* notifyRequest; runListener(incomingClientMsgQueue, *userAgentp, messageTimeout, messageTimeout, notifyRequest, subscribeResponse, SIP_OK_CODE, FALSE, 0, NULL); // We should have received a SUBSCRIBE response and no NOTIFY request. CPPUNIT_ASSERT(subscribeResponse); CPPUNIT_ASSERT(notifyRequest); CPPUNIT_ASSERT(subscribeResponse->getResponseStatusCode() == SIP_ACCEPTED_CODE); } }
UtlBoolean SipXProxyCseObserver::handleMessage(OsMsg& eventMessage) { int msgType = eventMessage.getMsgType(); switch (msgType) { case OsMsg::OS_EVENT: switch (eventMessage.getMsgSubType()) { case OsEventMsg::NOTIFY: if (mpWriter) { mpWriter->flush(); } break; } break ; case OsMsg::PHONE_APP: { SipMessage* sipMsg; if(SipMessageEvent::TRANSPORT_ERROR == ((SipMessageEvent&)eventMessage).getMessageStatus()) { Os::Logger::instance().log(FAC_SIP, PRI_ERR, "SipXProxyCseObserver::handleMessage transport error"); } else if((sipMsg = (SipMessage*)((SipMessageEvent&)eventMessage).getMessage())) { UtlString method; int rspStatus = 0; UtlString rspText; UtlString contact; UtlString toTag; enum { UnInteresting, aCallRequest, aCallSetup, aCallFailure, aCallEnd, aCallTransfer } thisMsgIs = UnInteresting; Url toUrl; sipMsg->getToUrl(toUrl); // explicitly, an INVITE Request toUrl.getFieldParameter("tag", toTag); if (!sipMsg->isResponse()) { // sipMsg is a Request sipMsg->getRequestMethod(&method); if (0==method.compareTo(SIP_INVITE_METHOD, UtlString::ignoreCase)) { if (toTag.isNull()) { sipMsg->getContactEntry(0, &contact); thisMsgIs = aCallRequest; } } else if (0==method.compareTo(SIP_REFER_METHOD, UtlString::ignoreCase)) { thisMsgIs = aCallTransfer; sipMsg->getContactEntry(0, &contact); } else if (0==method.compareTo(SIP_BYE_METHOD, UtlString::ignoreCase)) { thisMsgIs = aCallEnd; // no additional information needed } else { // other request methods are not interesting } } else // this is a response { int seq; if (sipMsg->getCSeqField(&seq, &method)) // get the method out of cseq field { if (0==method.compareTo(SIP_INVITE_METHOD, UtlString::ignoreCase)) { // Responses to INVITES are handled differently based on whether // or not the INVITE is dialog-forming. If dialog-forming, // any final response above 400 is considered a failure for CDR // purposes. If not dialog-forming, then any final response above 400 // except 401 Unauthorized, 407 Proxy Authentication Required and // 408 Request Timeout will terminate. If we're in a dialog then // only 408 (Request Timeout) and 481 (Call/Transaction does not exist) // will terminate the dialog. rspStatus = sipMsg->getResponseStatusCode(); if (rspStatus >= SIP_4XX_CLASS_CODE) // any failure { // a failure code - this is a potential CallFailure - Call Resolver will determine. thisMsgIs = aCallFailure; sipMsg->getResponseStatusText(&rspText); } else if ( ( rspStatus >= SIP_2XX_CLASS_CODE ) && ( rspStatus < SIP_3XX_CLASS_CODE ) ) { thisMsgIs = aCallSetup; sipMsg->getContactEntry(0, &contact); } } else { // responses to non-INVITES are not interesting } } else { Os::Logger::instance().log(FAC_SIP, PRI_ERR, "SipXProxyCseObserver - no Cseq in response"); } } # ifdef LOG_DEBUG Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "SipXProxyCseObserver message is %s", ( thisMsgIs == UnInteresting ? "UnInteresting" : thisMsgIs == aCallEnd ? "a Call End" : thisMsgIs == aCallFailure ? "a Call Failure" : thisMsgIs == aCallRequest ? "a call Request" : thisMsgIs == aCallSetup ? "a Call Setup" : thisMsgIs == aCallTransfer ? "a Call Transfer" : "BROKEN" )); # endif if (thisMsgIs != UnInteresting) { // collect the sequence data mSequenceNumber++; OsTime timeNow; OsDateTime::getCurTime(timeNow); // collect the dialog information UtlString callId; sipMsg->getCallIdField(&callId); Url toUrl; sipMsg->getToUrl(toUrl); UtlString toTag; toUrl.getFieldParameter("tag", toTag); Url fromUrl; sipMsg->getFromUrl(fromUrl); UtlString fromTag; fromUrl.getFieldParameter("tag", fromTag); // collect the To and From UtlString toField; sipMsg->getToField(&toField); UtlString fromField; sipMsg->getFromField(&fromField); // collect the branch Id (i.e. transaction id) and via count. UtlString viaValue; int viaCount; UtlString branchId; viaCount = sipMsg->getCountHeaderFields(SIP_VIA_FIELD); viaCount = viaCount + sipMsg->getCountHeaderFields(SIP_SHORT_VIA_FIELD); if ( sipMsg->getViaFieldSubField( &viaValue, 0 ) ) { sipMsg->getViaTag( viaValue, "branch", branchId ); } UtlString referTo; UtlString referredBy; UtlString requestUri; UtlString references; UtlString replaces_callId; UtlString replaces_toTag; UtlString replaces_fromTag; UtlString matchingIdentityHeader; SipXauthIdentity sipxIdentity(*sipMsg, matchingIdentityHeader, true,SipXauthIdentity::allowUnbound); sipMsg->getReferToField(referTo); sipMsg->getReferredByField(referredBy); sipMsg->getRequestUri(&requestUri); sipMsg->getReferencesField(&references); if (sipMsg->getReplacesData(replaces_callId, replaces_toTag, replaces_fromTag)) { if (references.length() != 0) { references.append(","); } references.append(replaces_callId); references.append(";rel=xfer"); } UtlString responseMethod; UtlString calleeRoute; int cseqNumber; sipMsg->getCSeqField(&cseqNumber, &responseMethod); BranchTimePair* callIdBranchIdTime; // generate the call state event record if (mpBuilder) { UtlString identity; UtlString recordRoute; bool routeFound = false; bool paiPresent = false; switch (thisMsgIs) { case aCallRequest: if (sipxIdentity.getIdentity(identity)) { paiPresent = true; } if ( branchId && branchId.data() ) { mCallTransMutex.acquire(); unsigned long currentTime = OsDateTime::getSecsSinceEpoch(); UtlString* keyCallId = new UtlString(callId); BranchTimePair* valBranchTimePair = new BranchTimePair(branchId.data(), ¤tTime, &paiPresent); if (NULL == mCallTransMap.insertKeyAndValue(keyCallId, valBranchTimePair) ) { // Unable to add callId to map so it must already be present. delete keyCallId; delete valBranchTimePair; // Check if the paiPresent value is set to true or not. // If not set and we now have a PAI for this call, set it and generate another call request state event // with this info. Otherwise skip over. if ( paiPresent ) { callIdBranchIdTime = (BranchTimePair*) mCallTransMap.findValue(&callId); if ( callIdBranchIdTime && (*callIdBranchIdTime->getPaiPresent() == false) ) { // need to generate another call request event in order to state originator is internal. callIdBranchIdTime->setPaiPresent(&paiPresent); } else { mCallTransMutex.release(); return(TRUE); } } else { mCallTransMutex.release(); return(TRUE); } } mCallTransMutex.release(); } mpBuilder->callRequestEvent(mSequenceNumber, timeNow, contact, references, branchId, viaCount, paiPresent); break; case aCallSetup: // Clear out from the map only if rspStatus is higher than 200 as its possible to receive multiple 200 messages. // If the response is 200, the call in the map will be cleared out when the call ends. mCallTransMutex.acquire(); callIdBranchIdTime = (BranchTimePair*) mCallTransMap.findValue(&callId); if ( callIdBranchIdTime && (0 == branchId.compareTo(callIdBranchIdTime)) ) { if ( rspStatus > SIP_2XX_CLASS_CODE ) { mCallTransMap.destroy(&callId); } mCallTransMutex.release(); } else { // CallId/BranchId are either not found or doesn't match. Not a final response. mCallTransMutex.release(); return(TRUE); } for (int rrNum = 0; (!routeFound && sipMsg->getRecordRouteUri(rrNum, &recordRoute)); rrNum++ ) { Url recordRouteUrl(recordRoute); if (mpSipUserAgent->isMyHostAlias(recordRouteUrl)) { // This is a record route for our proxy, extract Call tags if they exist. recordRouteUrl.getUrlParameter(SIP_SIPX_CALL_DEST_FIELD, calleeRoute, 0); routeFound = true; } } mpBuilder->callSetupEvent(mSequenceNumber, timeNow, contact, calleeRoute, branchId, viaCount); break; case aCallFailure: // Failure case means that the response code is > 400. If the call is found // in the map, then this is a final response. Delete from the map and build an event. mCallTransMutex.acquire(); callIdBranchIdTime = (BranchTimePair*) mCallTransMap.findValue(&callId); if ( callIdBranchIdTime && (0 == branchId.compareTo(callIdBranchIdTime)) ) { mCallTransMap.destroy(&callId); mCallTransMutex.release(); if ( rspStatus != SIP_PROXY_AUTH_REQ_CODE ) { mpBuilder->callFailureEvent(mSequenceNumber, timeNow, branchId, viaCount, rspStatus, rspText); } else { // response was an authentication required. Don't build a CSE for these as a new Invite will // occur. return(TRUE); } } else { // Call was not found in the map so this is not a final response. Ignore it. mCallTransMutex.release(); return(TRUE); } break; case aCallEnd: mCallTransMutex.acquire(); mCallTransMap.destroy(&callId); mCallTransMutex.release(); mpBuilder->callEndEvent(mSequenceNumber, timeNow); break; case aCallTransfer: mpBuilder->callTransferEvent(mSequenceNumber, timeNow, contact, referTo, referredBy, requestUri); break; default: // shouldn't be possible to get here Os::Logger::instance().log(FAC_SIP, PRI_ERR, "SipXProxyCseObserver invalid thisMsgIs"); break; } mpBuilder->addCallData(cseqNumber, callId, fromTag, toTag, fromField, toField); UtlString via; for (int i=0; sipMsg->getViaField(&via, i); i++) { mpBuilder->addEventVia(via); } mpBuilder->completeCallEvent(); // get the completed record UtlString event; mpBuilder->finishElement(event); if (mpWriter) { mpWriter->writeLog(event.data()); } } else { Os::Logger::instance().log(FAC_SIP, PRI_ERR, "SipXProxyCseObserver - no CallStateEventBuilder!"); } } } else { Os::Logger::instance().log(FAC_SIP, PRI_ERR, "SipXProxyCseObserver getMessage returned NULL"); } } break; default: { Os::Logger::instance().log(FAC_SIP, PRI_ERR, "SipXProxyCseObserver invalid message type %d", msgType ); } } // end switch (msgType) return(TRUE); }
UtlBoolean SubscribeServerThread::isAuthenticated ( const SipMessage* message, SipMessage *responseMessage, UtlString& authenticatedUser, UtlString& authenticatedRealm ) { UtlBoolean isAuthorized = FALSE; // if we are not using a database we must assume authenticated if ( !mIsCredentialDB ) { OsSysLog::add(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " ":: No Credential DB - request is always AUTHENTICATED"); isAuthorized = TRUE; } else { // realm and auth type should be default for server // if URI not defined in DB, the user is not authorized to modify bindings - OsSysLog::add( FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated():TRUE realm=\"%s\" ", mRealm.data()); UtlString requestNonce; UtlString requestRealm; UtlString requestUser; UtlString requestUserBase; UtlString requestUriParam; int requestAuthIndex = 0; // can have multiphe authorization / authorization-proxy headers // headers search for a realm match while ( message->getDigestAuthorizationData ( &requestUser, &requestRealm, &requestNonce, NULL, NULL, &requestUriParam, HttpMessage::SERVER, requestAuthIndex, &requestUserBase) ) { OsSysLog::add(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- Authorization header set in message, validate it.\n" "- reqRealm=\"%s\", reqUser=\"%s\", reqUserBase=\"%s\"", requestRealm.data(), requestUser.data(), requestUserBase.data()); // case sensitive comparison of realm if ( mRealm.compareTo( requestRealm ) == 0 ) { OsSysLog::add(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated()" "- Realm matches, now validate userid/password"); // See if the nonce is valid - see net/SipNonceDb.cpp UtlString reqUri; message->getRequestUri(&reqUri); Url mailboxUrl(reqUri); UtlString authTypeDB; UtlString passTokenDB; UtlString callId; UtlString fromTag; long nonceExpires = (5*60); // five minutes Url fromUrl; message->getFromUrl(fromUrl); fromUrl.getFieldParameter("tag", fromTag); message->getCallIdField(&callId); if (mNonceDb.isNonceValid(requestNonce, callId, fromTag, mRealm, nonceExpires)) { // then get the credentials for this realm if (CredentialDB::getInstance()-> getCredentialByUserid( mailboxUrl, mRealm, requestUserBase, passTokenDB, authTypeDB)) { // the Digest Password is calculated from the request // user, passtoken, nonce and request URI isAuthorized = message->verifyMd5Authorization(requestUser.data(), passTokenDB.data(), requestNonce, requestRealm.data(), requestUriParam.data()); if (isAuthorized) { // can have multiple credentials for same realm so only break out // when we have a positive match OsSysLog::add(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- request is AUTHENTICATED"); // copy the authenticated user/realm for subsequent authorization authenticatedUser = requestUserBase; authenticatedRealm = requestRealm; break; } else { OsSysLog::add(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- digest authorization failed"); } } else { OsSysLog::add(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- No Credentials for mailboxUrl=\"%s\", reqRealm=\"%s\", reqUser=\"%s\"", mailboxUrl.toString().data(), requestRealm.data(), requestUser.data()); } } else { OsSysLog::add(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- Invalid nonce \"%s\" for mailboxUrl=\"%s\", reqRealm=\"%s\", reqUser=\"%s\"", requestNonce.data(), mailboxUrl.toString().data(), requestRealm.data(), requestUser.data()); } // end check credentials } requestAuthIndex++; } //end while if ( !isAuthorized ) { // Generate the 401 Unauthorized response to challenge for credentials // Use the SipNonceDB to generate a nonce UtlString newNonce; UtlString callId; UtlString fromTag; Url fromUrl; message->getFromUrl(fromUrl); fromUrl.getFieldParameter("tag", fromTag); message->getCallIdField(&callId); mNonceDb.createNewNonce(callId, fromTag, mRealm, newNonce); responseMessage->setRequestUnauthorized(message, HTTP_DIGEST_AUTHENTICATION, mRealm, newNonce); } } return isAuthorized; }
bool SessionContext::handleRequest( SipMessage& message, const char* address, int port, bool bFromCallerToCallee ) { // This routine steers incoming requests to the DialogTracker instance that is // responsible for handling them based on the request's to- or from-tags depending // on the directionality of the request. ssize_t numberOfDialogTrackersEnteringRoutine = getNumberOfTrackedDialogs(); bool bTrackRequestResponse = false; UtlString discriminatingTag = getDiscriminatingTagValue( message, bFromCallerToCallee ); // if a discriminating tag was found, try to find a DialogTracker for it. if( !discriminatingTag.isNull() ) { DialogTracker* pDialogTracker = 0; if( ( pDialogTracker = getDialogTrackerForTag( discriminatingTag ) ) != 0 ) { bTrackRequestResponse = pDialogTracker->handleRequest( message, address, port, bFromCallerToCallee ); } else { OsSysLog::add(FAC_NAT, PRI_CRIT, "SessionContext[%s]::handleRequest: received in-dialog request with unknown discriminating tag: %s", mHandle.data(), discriminatingTag.data() ); } } else { // The request does not yet have a discriminating tag. This is likely indicating a // dialog-forming INVITE but to be sure, check that the request is indeed an // INVITE in the caller->callee direction. UtlString method; message.getRequestMethod(&method); if( bFromCallerToCallee && method.compareTo( SIP_INVITE_METHOD ) == 0 ) { // The INVITE is dialog-forming. Check whether or not already have // the reference dialog tracker for it. if( !mpReferenceDialogTracker ) { // This is the first time we see that dialog-forming request - create // a reference dialog tracker that will serve as a template to create // new DialogTracker objects for the dialogs that responses to the // request will establish. Url tempUrl; char tempBuffer[50]; sprintf( tempBuffer, "%s-%s", mHandle.data(), "ref" ); if( ( mpReferenceDialogTracker = new DialogTracker( tempBuffer, mSystemIdentificationString, this ) ) ) { mpReferenceDialogTracker->handleRequest( message, address, port, bFromCallerToCallee ); // save the From tag of the dialog-forming request. This will be used to identify // the discriminating tag when the directionality of a message is unknown. message.getFromUrl( tempUrl ); tempUrl.getFieldParameter( "tag", mDialogOriginalFromTag ); mDialogFormingInviteCseq.setValue( message ); bTrackRequestResponse = true; } } else { // This dialog-forming request has already been seen - this is likely a // retransmission. Present it to the reference dialog tracker so that // it can handle the retransmission properly. bTrackRequestResponse = mpReferenceDialogTracker->handleRequest( message, address, port, bFromCallerToCallee ); } } } // Check if the processing of the request caused the last DialogTracker to be deleted. // If so, the SessionContext is not required anymore therefore tell the CallTracker that // we are ready for deletion if( numberOfDialogTrackersEnteringRoutine && deleteDialogTrackersReadyForDeletion() == numberOfDialogTrackersEnteringRoutine ) { mpOwningCallTracker->reportSessionContextReadyForDeletion( mHandle ); } return bTrackRequestResponse; }
UtlBoolean SipXProxyCseObserver::handleMessage(OsMsg& eventMessage) { int msgType = eventMessage.getMsgType(); switch (msgType) { case OsMsg::OS_EVENT: switch (eventMessage.getMsgSubType()) { case OsEventMsg::NOTIFY: if (mpWriter) { mpWriter->flush(); } break; } break ; case OsMsg::PHONE_APP: { SipMessage* sipMsg; if(SipMessageEvent::TRANSPORT_ERROR == ((SipMessageEvent&)eventMessage).getMessageStatus()) { OsSysLog::add(FAC_SIP, PRI_ERR, "SipXProxyCseObserver::handleMessage transport error"); } else if((sipMsg = (SipMessage*)((SipMessageEvent&)eventMessage).getMessage())) { UtlString method; int rspStatus = 0; UtlString rspText; UtlString contact; UtlString toTag; enum { UnInteresting, aCallRequest, aCallSetup, aCallFailure, aCallEnd, aCallTransfer } thisMsgIs = UnInteresting; Url toUrl; sipMsg->getToUrl(toUrl); // explicitly, an INVITE Request toUrl.getFieldParameter("tag", toTag); if (!sipMsg->isResponse()) { // sipMsg is a Request sipMsg->getRequestMethod(&method); if (0==method.compareTo(SIP_INVITE_METHOD, UtlString::ignoreCase)) { if (toTag.isNull()) { thisMsgIs = aCallRequest; } } else if (0==method.compareTo(SIP_REFER_METHOD, UtlString::ignoreCase)) { thisMsgIs = aCallTransfer; sipMsg->getContactEntry(0, &contact); } else if (0==method.compareTo(SIP_BYE_METHOD, UtlString::ignoreCase)) { thisMsgIs = aCallEnd; // no additional information needed } else { // other request methods are not interesting } } else // this is a response { int seq; if (sipMsg->getCSeqField(&seq, &method)) // get the method out of cseq field { if (0==method.compareTo(SIP_INVITE_METHOD, UtlString::ignoreCase)) { // Responses to INVITES are handled differently based on whether // or not the INVITE is dialog-forming. If dialog-forming, // any final response above 400 is considered a failure for CDR // purposes. If not dialog-forming, then any final response above 400 // except 401 Unauthorized, 407 Proxy Authentication Required and // 408 Request Timeout will terminate. If we're in a dialog then // only 408 (Request Timeout) and 481 (Call/Transaction does not exist) // will terminate the dialog. rspStatus = sipMsg->getResponseStatusCode(); if (rspStatus >= SIP_4XX_CLASS_CODE) // any failure { // a failure code - this is a potential CallFailure - Call Resolver will determine. thisMsgIs = aCallFailure; sipMsg->getResponseStatusText(&rspText); } else if ( ( rspStatus >= SIP_2XX_CLASS_CODE ) && ( rspStatus < SIP_3XX_CLASS_CODE ) ) { thisMsgIs = aCallSetup; sipMsg->getContactEntry(0, &contact); } } else { // responses to non-INVITES are not interesting } } else { OsSysLog::add(FAC_SIP, PRI_ERR, "SipXProxyCseObserver - no Cseq in response"); } } # ifdef LOG_DEBUG OsSysLog::add(FAC_SIP, PRI_DEBUG, "SipXProxyCseObserver message is %s", ( thisMsgIs == UnInteresting ? "UnInteresting" : thisMsgIs == aCallEnd ? "a Call End" : thisMsgIs == aCallFailure ? "a Call Failure" : thisMsgIs == aCallRequest ? "a call Request" : thisMsgIs == aCallSetup ? "a Call Setup" : thisMsgIs == aCallTransfer ? "a Call Transfer" : "BROKEN" )); # endif if (thisMsgIs != UnInteresting) { // collect the sequence data mSequenceNumber++; OsTime timeNow; OsDateTime::getCurTime(timeNow); // collect the dialog information UtlString callId; sipMsg->getCallIdField(&callId); Url toUrl; sipMsg->getToUrl(toUrl); UtlString toTag; toUrl.getFieldParameter("tag", toTag); Url fromUrl; sipMsg->getFromUrl(fromUrl); UtlString fromTag; fromUrl.getFieldParameter("tag", fromTag); // collect the To and From UtlString toField; sipMsg->getToField(&toField); UtlString fromField; sipMsg->getFromField(&fromField); UtlString referTo; UtlString referredBy; UtlString requestUri; sipMsg->getReferToField(referTo); sipMsg->getReferredByField(referredBy); sipMsg->getRequestUri(&requestUri); UtlString responseMethod; int cseqNumber; sipMsg->getCSeqField(&cseqNumber, &responseMethod); // generate the call state event record if (mpBuilder) { switch (thisMsgIs) { case aCallRequest: mpBuilder->callRequestEvent(mSequenceNumber, timeNow, contact); break; case aCallSetup: mpBuilder->callSetupEvent(mSequenceNumber, timeNow, contact); break; case aCallFailure: mpBuilder->callFailureEvent(mSequenceNumber, timeNow, rspStatus, rspText); break; case aCallEnd: mpBuilder->callEndEvent(mSequenceNumber, timeNow); break; case aCallTransfer: mpBuilder->callTransferEvent(mSequenceNumber, timeNow, contact, referTo, referredBy, requestUri); break; default: // shouldn't be possible to get here OsSysLog::add(FAC_SIP, PRI_ERR, "SipXProxyCseObserver invalid thisMsgIs"); break; } mpBuilder->addCallData(cseqNumber, callId, fromTag, toTag, fromField, toField); UtlString via; for (int i=0; sipMsg->getViaField(&via, i); i++) { mpBuilder->addEventVia(via); } mpBuilder->completeCallEvent(); // get the completed record UtlString event; mpBuilder->finishElement(event); if (mpWriter) { mpWriter->writeLog(event.data()); } } else { OsSysLog::add(FAC_SIP, PRI_ERR, "SipXProxyCseObserver - no CallStateEventBuilder!"); } } } else { OsSysLog::add(FAC_SIP, PRI_ERR, "SipXProxyCseObserver getMessage returned NULL"); } } break; default: { OsSysLog::add(FAC_SIP, PRI_ERR, "SipXProxyCseObserver invalid message type %d", msgType ); } } // end switch (msgType) return(TRUE); }
UtlBoolean AppAgentSubscribePolicy::isAuthenticated(const SipMessage & subscribeRequest, SipMessage & subscribeResponse) { UtlBoolean isAuthorized = FALSE; UtlString callId; subscribeRequest.getCallIdField(&callId); Url fromNameAddr; UtlString fromTag; subscribeRequest.getFromUrl(fromNameAddr); fromNameAddr.getFieldParameter("tag", fromTag); UtlString authNonce; UtlString authRealm; UtlString authUser; UtlString authUserBase; UtlString uriParam; UtlString authCnonce; UtlString authNonceCount; UtlString authQop; // Iterate through Authorization and Proxy-Authorization credentials, // looking for one that shows this request is authenticated. for (int authIndex = 0; ! isAuthorized && subscribeRequest.getDigestAuthorizationData(&authUser, &authRealm, &authNonce, NULL, NULL, &uriParam, &authCnonce, &authNonceCount, &authQop, HttpMessage::SERVER, authIndex, &authUserBase); authIndex++ ) { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "AppAgentSubscribePolicy::isAuthenticated " "Message Authorization received: " "reqRealm='%s', reqUser='******', reqUserBase='%s'", authRealm.data(), authUser.data(), authUserBase.data()); UtlString qopType; if (mRealm.compareTo(authRealm) ) // case sensitive check that realm is correct { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "AppAgentSubscribePolicy::isAuthenticated " "Realm does not match"); } // validate the nonce else if (!mNonceDb.isNonceValid(authNonce, callId, fromTag, mRealm, mNonceExpiration)) { Os::Logger::instance().log(FAC_AUTH, PRI_INFO, "AppAgentSubscribePolicy::isAuthenticated -" "Invalid nonce: nonce='%s', callId='%s'", authNonce.data(), callId.data()); } // verify that qop,cnonce, nonceCount are compatible else if (subscribeRequest.verifyQopConsistency(authCnonce.data(), authNonceCount.data(), &authQop, qopType) >= HttpMessage::AUTH_QOP_NOT_SUPPORTED) { Os::Logger::instance().log(FAC_AUTH, PRI_INFO, "AppAgentSubscribePolicy::isAuthenticated -" "Invalid combination of QOP('%s'), cnonce('%s') and nonceCount('%s')", authQop.data(), authCnonce.data(), authNonceCount.data()); } else // realm, nonce and qop are all ok { // build the "authorization identity" from the auth credentials Url authIdentity; UtlString authTypeDB; UtlString passTokenDB; // then get the credentials for this user & realm if (mEntityDb.getCredential(authUserBase, authRealm, authIdentity, passTokenDB, authTypeDB)) { // only DIGEST is used, so the authTypeDB above is ignored if ((isAuthorized = subscribeRequest.verifyMd5Authorization(authUser.data(), passTokenDB.data(), authNonce.data(), authRealm.data(), authCnonce.data(), authNonceCount.data(), authQop.data(), uriParam.data()) )) { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "AppAgentSubscribePolicy::isAuthenticated " "response auth hash matches"); } else { UtlString identity; authIdentity.getIdentity(identity); Os::Logger::instance().log(FAC_AUTH, PRI_ERR, "AppAgentSubscribePolicy::isAuthenticated " "Response auth hash does not match (bad password?)" " authIdentity='%s' authUser='******' authUserBase='%s'", identity.data(), authUser.data(), authUserBase.data()); } } else // failed to get credentials { UtlString identity; authIdentity.getIdentity(identity); Os::Logger::instance().log(FAC_AUTH, PRI_ERR, "AppAgentSubscribePolicy::isAuthenticated " "Unable to get credentials for realm='%s', user='******', userBase = '%s'", mRealm.data(), authUser.data(), authUserBase.data()); } } // end DB check } //end for if ( !isAuthorized ) { // Generate a new challenge UtlString newNonce; UtlString opaque; mNonceDb.createNewNonce(callId, fromTag, mRealm, newNonce); subscribeResponse.setRequestUnauthorized(&subscribeRequest, HTTP_DIGEST_AUTHENTICATION, mRealm, newNonce, NULL // opaque ); } return isAuthorized; }
// Test for XECS-247, When subscribe server receives 481 for a // NOTIFY, it does not terminate the subscription. // Test for XECS-282, When subscribe server receives 500 for a // NOTIFY, it does not terminate the subscription. // Verify that subscription is terminated when NOTIFY returns a variety // of error responses. (Retry-After suppresses termination. That case // is tested in terminateSubscriptionOnErrorRetryAfter.) void terminateSubscriptionOnError() { // Test MWI messages const char* mwiSubscribe = "SUBSCRIBE sip:111@localhost SIP/2.0\r\n" "From: <sip:[email protected]>;tag=1612c1612\r\n" "To: <sip:[email protected]>\r\n" "Cseq: 1 SUBSCRIBE\r\n" "Event: message-summary\r\n" "Accept: application/simple-message-summary\r\n" "Expires: 3600\r\n" "Date: Tue, 26 Apr 2005 14:59:30 GMT\r\n" "Max-Forwards: 20\r\n" "User-Agent: Pingtel/2.2.0 (VxWorks)\r\n" "Accept-Language: en\r\n" "Supported: sip-cc, sip-cc-01, timer, replaces\r\n" "Content-Length: 0\r\n" "\r\n"; // Loop through this scenario for a series of response codes. int test_codes[] = { SIP_BAD_REQUEST_CODE, SIP_NOT_FOUND_CODE, SIP_BAD_TRANSACTION_CODE, 499, SIP_SERVER_INTERNAL_ERROR_CODE, SIP_SERVICE_UNAVAILABLE_CODE, 599, SIP_GLOBAL_BUSY_CODE, 699 }; for (unsigned int i = 0; i < sizeof (test_codes) / sizeof (test_codes[0]); i++) { // Send a SUBSCRIBE to ourselves SipMessage mwiSubscribeRequest(mwiSubscribe); { UtlString c; CallId::getNewCallId(c); mwiSubscribeRequest.setCallIdField(c); } mwiSubscribeRequest.setSipRequestFirstHeaderLine(SIP_SUBSCRIBE_METHOD, aor, SIP_PROTOCOL_VERSION); mwiSubscribeRequest.setContactField(aor_name_addr); mwiSubscribeRequest.incrementCSeqNumber(); CPPUNIT_ASSERT(userAgentp->send(mwiSubscribeRequest)); // We should get a 202 response and a NOTIFY request in the queue // Send the specified response to the NOTIFY. OsTime messageTimeout(1, 0); // 1 second { const SipMessage* subscribeResponse; const SipMessage* notifyRequest; runListener(incomingClientMsgQueue, *userAgentp, messageTimeout, messageTimeout, notifyRequest, subscribeResponse, test_codes[i], FALSE, 0, NULL); // We should have received a SUBSCRIBE response and a NOTIFY request. CPPUNIT_ASSERT(subscribeResponse); // Extract the to-tag in the response, apply it to // mwiSubscribeRequest. Url toUrl; subscribeResponse->getToUrl(toUrl); UtlString toTag; toUrl.getFieldParameter("tag", toTag); mwiSubscribeRequest.setToFieldTag(toTag); CPPUNIT_ASSERT(notifyRequest); CPPUNIT_ASSERT(subscribeResponse->getResponseStatusCode() == SIP_ACCEPTED_CODE); } // Wait for the subscription to be ended. OsTask::delay(100); // Send a re-SUBSCRIBE in the existing dialog, to find out if the // subscription was terminated or not. mwiSubscribeRequest.incrementCSeqNumber(); // Leave the Expires header with the default value. CPPUNIT_ASSERT(userAgentp->send(mwiSubscribeRequest)); // We should get a 481 response and no NOTIFY, because the // subscription has been terminated. { const SipMessage* subscribeResponse; const SipMessage* notifyRequest; runListener(incomingClientMsgQueue, *userAgentp, messageTimeout, messageTimeout, notifyRequest, subscribeResponse, SIP_OK_CODE, FALSE, 0, NULL); // We should have received a SUBSCRIBE response and no NOTIFY request. CPPUNIT_ASSERT(subscribeResponse); CPPUNIT_ASSERT(subscribeResponse->getResponseStatusCode() == SIP_BAD_TRANSACTION_CODE); CPPUNIT_ASSERT(!notifyRequest); } } }
SubscribeServerThread::SubscribeStatus SubscribeServerThread::addSubscription( const int timeNow, const SipMessage* subscribeMessage, const char* domain, const UtlString& eventType, const UtlString& eventId, const UtlHashMap& eventParams, UtlString& newToTag, int& grantedExpiration) { SubscribeStatus returnStatus = STATUS_INTERNAL_ERROR; int subscribeCseqInt = 0; UtlString callId; UtlString contactEntry; UtlString to; UtlString from; UtlString route; UtlString key; UtlString method; Url identity; // Construct the identity UtlString uriUser, requestUri; subscribeMessage->getUri( NULL, NULL, NULL, &uriUser ); subscribeMessage->getRequestUri( &requestUri ); identity.setUserId( uriUser ); identity.setUrlType( "sip" ); identity.setHostAddress( domain ); subscribeMessage->getToField(&to); subscribeMessage->getFromField(&from); subscribeMessage->getCallIdField(&callId); subscribeMessage->getCSeqField(&subscribeCseqInt, &method); subscribeMessage->getContactEntry(0, &contactEntry); subscribeMessage->buildRouteField(&route); Url toUrl; subscribeMessage->getToUrl(toUrl); int commonExpires = -1; if ( subscribeMessage->getExpiresField( &commonExpires ) ) { if( commonExpires > 0 ) // came from request { if (commonExpires < mMinExpiresTimeint) { returnStatus = STATUS_LESS_THAN_MINEXPIRES; OsSysLog::add( FAC_SIP, PRI_ERR, "addSubscription: " "Expires (%d) less than Minimum (%d)", commonExpires, mMinExpiresTimeint); return returnStatus; } else if (commonExpires > mDefaultSubscribePeriod) { commonExpires = mDefaultSubscribePeriod; } else { // commonExpires is in the allowed range - use the requested value } } else if( commonExpires == 0 ) { // remove subscription binding // remove all bindings because one contact value is * OsSysLog::add(FAC_SIP, PRI_DEBUG,"SubscribeServerThread::addSubscription -" " subscription for url %s and event %s to be removed after sending NOTIFY", toUrl.toString().data(), eventType.data()); returnStatus = STATUS_TO_BE_REMOVED; return returnStatus; } else if( commonExpires == -1) // no expires value in request { // Assume the default value commonExpires = mDefaultSubscribePeriod; OsSysLog::add(FAC_SIP, PRI_DEBUG,"SubscribeServerThread::addSubscription -" " No Expires Value, assigning default value (%d)", commonExpires); } } UtlString sipxImpliedParameter(SIPX_IMPLIED_SUB); UtlString* sipxImpliedDuration = NULL; int grantedExpirationTime; if (( sipxImpliedDuration = dynamic_cast<UtlString*>(eventParams.findValue(&sipxImpliedParameter)))) { /* * This request was generated by the registrar as an implied subscription; * its duration must therefore match that of the registration, which has * already been randomized. */ grantedExpirationTime = atoi(sipxImpliedDuration->data()); } else { /* * The request is for a new or refreshed subscription (other cases were handled above); * commonExpires is now the requested duration in the range: * mMinExpiresTimeint <= commonExpires <= mDefaultSubscribePeriod * In order to distribute expirations smoothly, we actually grant a randomized duration */ int spreadFloor = mMinExpiresTimeint*2; if ( commonExpires > spreadFloor ) { // a normal (long) registration // - spread it between twice the min and the longest they asked for grantedExpirationTime = ( (rand() % (commonExpires - spreadFloor)) + spreadFloor); } else if ( commonExpires > mMinExpiresTimeint ) { // a short but not minimum registration // - spread it between the min and the longest they asked for grantedExpirationTime = ( (rand() % (commonExpires - mMinExpiresTimeint) ) + mMinExpiresTimeint ); } else // longestExpiration == mMinExpiresTimeint { // minimum - can't randomize because we can't shorten or lengthen it grantedExpirationTime = mMinExpiresTimeint; } } // Handle the to-tag: // If no to-tag, this is a new subscription, for which we must create // a to-tag. // If there is a to-tag, this is a renewal of a subscription, and we // must first check that the subscription exists. (This is because if // the UA thinks there is a subscription, but we do not know of it, we // cannot reconstitute the subscription, as we do not know the NOTIFY CSeq // that the UA expects. See XECS-406 for the ill consequences of // this problem.) // Clear the returned new to-tag. newToTag.remove(0); { UtlString x; bool r = toUrl.getFieldParameter("tag", x); OsSysLog::add(FAC_SIP, PRI_DEBUG,"SubscribeServerThread::addSubscription getting to-tag, return %d, value '%s'", (int) r, x.data()); } UtlString toTag; if (toUrl.getFieldParameter("tag", toTag)) { // Check to see if this subscription exists. // Critical Section here OsLock mutex(mLock); bool exists = SubscriptionDB::getInstance()->subscriptionExists( SUBSCRIPTION_COMPONENT_STATUS, to, from, callId, OsDateTime::getSecsSinceEpoch()); OsSysLog::add(FAC_SIP, PRI_DEBUG,"SubscribeServerThread::addSubscription subscriptionExists(..., '%s', '%s', '%s', %d) = %d", to.data(), from.data(), callId.data(), (int) OsDateTime::getSecsSinceEpoch(), exists); if (!exists) { returnStatus = STATUS_BAD_SUBSCRIPTION; return returnStatus; } } else { // Generate a random to-tag. CallId::getNewTag(newToTag); // Add it to the remembered To header value. to.append(";tag="); to.append(newToTag); OsSysLog::add(FAC_SIP, PRI_DEBUG,"SubscribeServerThread::addSubscription generated to-tag '%s'", newToTag.data()); } OsSysLog::add(FAC_SIP, PRI_DEBUG, "SubscribeServerThread::addSubscription -" " Adding/updating subscription for URI '%s' event %s duration %d to '%s'", toUrl.toString().data(), eventType.data(), grantedExpirationTime, contactEntry.data()); // trim the contact to just the uri Url contactUrl(contactEntry); contactUrl.getUri(contactEntry); // add bindings identity.getIdentity( key ); // Insert or update the information for this subscription. // (Note that the "notify CSeq" parameter is ignored if there is // an existing SubscriptionDB row for this subscription.) if (insertRow(requestUri, // identity, callId, contactEntry, grantedExpirationTime + timeNow, subscribeCseqInt, eventType, eventId, to, from, key, // this will be searched for later route, 1)) // initial notify cseq (sent to phone) { grantedExpiration = grantedExpirationTime; returnStatus = STATUS_SUCCESS; } else { // log the error and send error indication to subscriber OsSysLog::add(FAC_SIP, PRI_ERR, "SubscribeServerThread::addSubscription -" " Could not insert record in Database"); returnStatus = STATUS_INTERNAL_ERROR; } return returnStatus; }
UtlBoolean SubscribeServerThread::isAuthenticated (const SipMessage* message, SipMessage *responseMessage, UtlString& authenticatedUser, UtlString& authenticatedRealm ) { UtlBoolean retIsAuthenticated = FALSE; // if we are not using a database we must assume authenticated if ( !mIsCredentialDB ) { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " ":: No Credential DB - request is always AUTHENTICATED"); retIsAuthenticated = TRUE; } else { // realm and auth type should be default for server // if URI not defined in DB, the user is not authorized to modify bindings - Os::Logger::instance().log( FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated():TRUE realm=\"%s\" ", mRealm.data()); UtlString requestNonce; UtlString requestCnonce; UtlString requestNonceCount; UtlString requestQop; UtlString requestRealm; UtlString requestUser; UtlString requestUserBase; UtlString requestUriParam; int requestAuthIndex = 0; EntityDB* entityDb = StatusServer::getInstance()->getEntityDb(); // can have multiple authorization / authorization-proxy headers // headers search for a realm match while ( message->getDigestAuthorizationData ( &requestUser, &requestRealm, &requestNonce, NULL, // opaque NULL, // response &requestUriParam, &requestCnonce, &requestNonceCount, &requestQop, HttpMessage::SERVER, requestAuthIndex, &requestUserBase) ) { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- Authorization header set in message, validate it.\n" "- reqRealm=\"%s\", reqUser=\"%s\", reqUserBase=\"%s\"", requestRealm.data(), requestUser.data(), requestUserBase.data()); UtlString qopType; UtlString callId; UtlString fromTag; long nonceExpires = (5*60); // five minutes Url fromUrl; message->getFromUrl(fromUrl); fromUrl.getFieldParameter("tag", fromTag); UtlString reqUri; message->getRequestUri(&reqUri); Url mailboxUrl(reqUri); message->getCallIdField(&callId); if (mRealm.compareTo(requestRealm) ) // case sensitive check that realm is correct { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "Realm does not match"); } // See if the nonce is valid - see net/SipNonceDb.cpp else if (!mNonceDb.isNonceValid(requestNonce, callId, fromTag, mRealm, nonceExpires)) { Os::Logger::instance().log(FAC_AUTH, PRI_INFO, "SubscribeServerThread::isAuthenticated() " "Invalid NONCE: '%s' found for mailboxUrl '%s' " "realm: '%s' user: '******' " "cnonce: '%s' nc: '%s' qop: '%s' " "expiration: %ld", requestNonce.data(), mailboxUrl.toString().data(), mRealm.data(), requestUser.data(), requestCnonce.data(), requestNonceCount.data(), requestQop.data(), nonceExpires); } // verify that qop,cnonce, nonceCount are compatible else if (message->verifyQopConsistency(requestCnonce.data(), requestNonceCount.data(), &requestQop, qopType) >= HttpMessage::AUTH_QOP_NOT_SUPPORTED) { Os::Logger::instance().log(FAC_AUTH, PRI_INFO, "SubscribeServerThread::isAuthenticated() " "Invalid combination of QOP('%s'), cnonce('%s') and nonceCount('%s')", requestQop.data(), requestCnonce.data(), requestNonceCount.data()); } else // realm, nonce and qop are all ok { UtlString authTypeDB; UtlString passTokenDB; // then get the credentials for this realm if (entityDb->getCredential(mailboxUrl, mRealm, requestUserBase, passTokenDB, authTypeDB)) { // the Digest Password is calculated from the request // user, passtoken, nonce and request URI retIsAuthenticated = message->verifyMd5Authorization(requestUser.data(), passTokenDB.data(), requestNonce.data(), requestRealm.data(), requestCnonce.data(), requestNonceCount.data(), requestQop.data(), requestUriParam.data()); if (retIsAuthenticated) { // can have multiple credentials for same realm so only break out // when we have a positive match Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- request is AUTHENTICATED"); // copy the authenticated user/realm for subsequent authorization authenticatedUser = requestUserBase; authenticatedRealm = requestRealm; break; } else { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- digest authorization failed " "nonce \"%s\", cnonce \"%s\" for " "mailboxUrl=\"%s\", reqRealm=\"%s\", reqUser=\"%s\"", requestNonce.data(), requestCnonce.data(), mailboxUrl.toString().data(), requestRealm.data(), requestUser.data()); } } else { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "SubscribeServerThread::isAuthenticated() " "- No Credentials for mailboxUrl=\"%s\", reqRealm=\"%s\", reqUser=\"%s\"", mailboxUrl.toString().data(), requestRealm.data(), requestUser.data()); } // end check credentials } requestAuthIndex++; } //end while if ( !retIsAuthenticated ) { // Generate the 401 Unauthorized response to challenge for credentials // Use the SipNonceDB to generate a nonce UtlString newNonce; UtlString callId; UtlString fromTag; Url fromUrl; message->getFromUrl(fromUrl); fromUrl.getFieldParameter("tag", fromTag); message->getCallIdField(&callId); mNonceDb.createNewNonce(callId, fromTag, mRealm, newNonce); responseMessage->setRequestUnauthorized(message, HTTP_DIGEST_AUTHENTICATION, mRealm, newNonce); } } // end DB exists return retIsAuthenticated; }