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); }
void SipImpliedSubscriptions::buildSubscribeRequest( const SipMessage& registerMessage ,int duration ,SipMessage& subscribeRequest ,UtlString& callId ,UtlString& fromTag ,UtlString& fromUri ) { UtlString registrationValue; UtlString tagNameValuePair; UtlString contactUri; int sequenceNumber = 0; // Get the From URL, and change the tag Url fromUrl; registerMessage.getFromUrl( fromUrl ); fromUrl.removeFieldParameter("tag"); // discard from tag from REGISTER registerMessage.getFromUri( &fromUri ); (void) registerMessage.getContactUri(0, &contactUri); (void) registerMessage.getCSeqField(&sequenceNumber, ®istrationValue); Url toUrl; registerMessage.getToUrl( toUrl ); toUrl.removeFieldParameter("tag"); UtlString toUri; registerMessage.getToUri( &toUri ); registerMessage.getCallIdField( &callId ); callId.prepend("implied-mwi-"); // Build a from tag for the SUBSCRIBE // - hash the call id so that it will be the same on each refresh UtlString callIdHash; NetMd5Codec::encode( callId.data(), callIdHash ); fromUrl.setFieldParameter("tag", callIdHash.data() ); fromTag = callIdHash; // for constructing the nonce subscribeRequest.setVoicemailData( fromUrl.toString() // From: ,toUrl.toString() // To: ,toUri.data() // request URI ,contactUri.data() // taken from registration ,callId.data() ,++sequenceNumber ,duration ); /* * Rewrite the event field to add our extension parameter to * ensure that the registration and subscription are synchronized. */ const char* standardEventHeader = subscribeRequest.getHeaderValue(0, SIP_EVENT_FIELD); UtlString extendedEventHeader(standardEventHeader); extendedEventHeader.append(";" SIPX_IMPLIED_SUB "="); char durationString[12]; sprintf(durationString, "%d", duration); extendedEventHeader.append(durationString); subscribeRequest.setHeaderValue(SIP_EVENT_FIELD, extendedEventHeader.data(), 0); }
/// 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; }
int SubscribeServerThread::removeErrorSubscription (const SipMessage& sipMessage ) { int returnStatus = STATUS_SUCCESS; UtlString callId; UtlString to; UtlString from; sipMessage.getToField(&to); sipMessage.getFromField(&from); sipMessage.getCallIdField(&callId); OsSysLog::add(FAC_SIP, PRI_WARNING, "SubscribeServerThread::removeErrorSubscription %s", callId.data()); removeErrorRow(from, to, callId); return returnStatus; }
AuthPlugin::AuthResult SubscriptionAuth::authorizeAndModify(const UtlString& id, /**< The authenticated identity of the * request originator, if any (the null * string if not). * This is in the form of a SIP uri * identity value as used in the * credentials database (user@domain) * without the scheme or any parameters. */ const Url& requestUri, ///< parsed target Uri RouteState& routeState, ///< the state for this request. const UtlString& method,///< the request method AuthResult priorResult,///< results from earlier plugins. SipMessage& request, ///< see AuthPlugin wrt modifying bool bSpiralingRequest, ///< request spiraling indication UtlString& reason ///< rejection reason ) { AuthResult result = CONTINUE; UtlString eventField; UtlString targetUser; requestUri.getUserId(targetUser); if (CONTINUE == priorResult && id.isNull() && method.compareTo(SIP_SUBSCRIBE_METHOD) == 0 && request.getEventField(eventField) && mEventPackagesRequiringAuthentication.contains( &eventField ) && !isTargetExemptedFromAuthentication(targetUser)) { // we do not have an authenticated ID for the request - challenge it. // get the call-id to use in logging UtlString callId; request.getCallIdField(&callId); OsSysLog::add(FAC_AUTH, PRI_INFO, "SubscriptionAuth[%s]::authorizeAndModify " "challenging subscription for dialog event package '%s' (call id = '%s')", mInstanceName.data(), eventField.data(), callId.data() ); result = DENY; reason = "Authentication Required to Subscribe to " + eventField; } return result; }
RedirectPlugin::LookUpStatus SipRedirectorJoin::lookUp( const SipMessage& message, const UtlString& requestString, const Url& requestUri, const UtlString& method, ContactList& contactList, RedirectPlugin::RequestSeqNo requestSeqNo, int redirectorNo, SipRedirectorPrivateStorage*& privateStorage, ErrorDescriptor& errorDescriptor) { UtlString userId; UtlString incomingCallId; requestUri.getUserId(userId); message.getCallIdField(&incomingCallId); if (!mCallJoinCode.isNull() && userId.length() > mCallJoinCode.length() && userId.index(mCallJoinCode.data()) == 0 && userId.compareTo(mExcludedUser1) != 0 && userId.compareTo(mExcludedUser2) != 0) { return lookUpDialog(requestString, incomingCallId, contactList, requestSeqNo, redirectorNo, privateStorage, // The suffix of the request URI after the // directed call pick-up code. userId.data() + mCallJoinCode.length(), // Only examine confirmed dialogs. stateConfirmed); } else { // We do not recognize the user, so we do nothing. return RedirectPlugin::SUCCESS; } }
/// Encode identity info into a URL bool SipXauthIdentity::encodeUri(Url & uri, const SipMessage & request, const OsDateTime * timestamp) { // Don't proceed if the encapsulated identity is invalid if (!mIsValidIdentity) { Os::Logger::instance().log(FAC_SIP, PRI_CRIT, "SipXauthIdentity::encodeUri[bound]: encapsulated SipXauthIdentity is invalid"); } else { // make sure no existing identity in the URI uri.removeHeaderParameter(SipXauthIdentity::AuthIdentityHeaderName); // set Call-Id and from-tag for the signature calculation UtlString callId; UtlString fromTag; Url fromUrl; request.getCallIdField(&callId); request.getFromUrl(fromUrl); fromUrl.getFieldParameter("tag", fromTag); OsDateTime now; OsDateTime::getCurTime(now); if (NULL==timestamp) { timestamp = &now; } UtlString value; encode(value, callId, fromTag, *timestamp); uri.setHeaderParameter(SipXauthIdentity::AuthIdentityHeaderName, value.data()); Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "SipXauthIdentity::encodeUri[bound] encoded URI '%s'", uri.toString().data() ); } return mIsValidIdentity; }
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); }
// Update the IMDB with the NOTIFY CSeq now in notifyRequest and the // specified 'version' for the given eventTypeKey. void SipPersistentSubscriptionMgr::updateVersion(SipMessage& notifyRequest, int version, const UtlString& eventTypeKey) { // Call the superclass's updateVersion. SipSubscriptionMgr::updateVersion(notifyRequest, version, eventTypeKey); // Extract from the NOTIFY the information we need to find the right // IMDB row. int cseq; UtlString method; notifyRequest.getCSeqField(&cseq, &method); UtlString to; UtlString from; UtlString callId; UtlString eventType, eventId; int now; // Note that the "to" and "from" fields of the subscription table // are as those URIs appear in the SUBSCRIBE message, which is // reversed in the NOTIFY message. notifyRequest.getToField(&from); notifyRequest.getFromField(&to); notifyRequest.getCallIdField(&callId); notifyRequest.getEventField(&eventType, &eventId); now = (int) OsDateTime::getSecsSinceEpoch(); OsSysLog::add(FAC_SIP, PRI_DEBUG, "SipPersistentSubscriptionMgr::updateVersion " "callId = '%s', to = '%s', from = '%s', eventType = '%s', eventTypeKey = '%s', eventId = '%s', cseq = %d, version = %d", callId.data(), to.data(), from.data(), eventType.data(), eventTypeKey.data(), eventId.data(), cseq, version); mSubscriptionDBInstance->updateNotifyUnexpiredSubscription( mComponent, to, from, callId, eventTypeKey, eventId, now, cseq, version); // Start the save timer. mPersistenceTimer.oneshotAfter(sPersistInterval); }
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 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; } }
UtlBoolean SipPersistentSubscriptionMgr::updateDialogInfo( const SipMessage& subscribeRequest, UtlString& resourceId, UtlString& eventTypeKey, UtlString& eventType, UtlString& subscribeDialogHandle, UtlBoolean& isNew, UtlBoolean& isSubscriptionExpired, SipMessage& subscribeResponse, SipSubscribeServerEventHandler& handler) { OsSysLog::add(FAC_SIP, PRI_DEBUG, "SipPersistentSubscriptionMgr::updateDialogInfo " "resourceId = '%s', eventTypeKey = '%s'", resourceId.data(), eventTypeKey.data()); UtlBoolean ret; // Call SipSubscriptionMgr to update the in-memory data. ret = SipSubscriptionMgr::updateDialogInfo(subscribeRequest, resourceId, eventTypeKey, eventType, subscribeDialogHandle, isNew, isSubscriptionExpired, subscribeResponse, handler); // If that succeeded, update the IMDB. if (ret) { UtlString requestUri; UtlString callId; UtlString contactEntry; UtlString to; UtlString from; UtlString route; UtlString accept; subscribeRequest.getRequestUri(&requestUri); subscribeRequest.getCallIdField(&callId); subscribeRequest.getContactEntry(0, &contactEntry); subscribeRequest.getToField(&to); subscribeRequest.getFromField(&from); subscribeRequest.buildRouteField(&route); accept.append(subscribeRequest.getHeaderValue(0, SIP_ACCEPT_FIELD)); int expires = 0; subscribeResponse.getExpiresField(&expires); expires += OsDateTime::getSecsSinceEpoch(); int subscribeCseq; UtlString subscribeCseqMethod; subscribeRequest.getCSeqField(&subscribeCseq, &subscribeCseqMethod); OsSysLog::add(FAC_SIP, PRI_DEBUG, "SipPersistentSubscriptionMgr::updateDialogInfo " "mComponent = '%s', requestUri = '%s', callId = '%s', contactEntry = '%s', expires = %d, to = '%s', from = '%s', key = '%s', route = '%s', accept = '%s'", mComponent.data(), requestUri.data(), callId.data(), contactEntry.data(), expires, to.data(), from.data(), resourceId.data(), route.data(), accept.data()); // Attempt to update an existing row. int now = (int)OsDateTime::getSecsSinceEpoch(); ret = mSubscriptionDBInstance->updateSubscribeUnexpiredSubscription( mComponent, to, from, callId, eventTypeKey, "", now, expires, subscribeCseq); if (!ret) { // Add a new row. // This call assumes that eventTypeKey is OK for use as the <eventtype>, // and that the NOTIFY CSeq's will start at 1. 0 is used as // the initial XML version. ret = mSubscriptionDBInstance->insertRow( mComponent, requestUri, callId, contactEntry, expires, subscribeCseq, eventTypeKey, "", to, from, resourceId, route, 1, accept, 0); if (!ret) { OsSysLog::add(FAC_SIP, PRI_ERR, "SipPersistantSubscriptionMgr::addSubscription " "Could not update or insert record in database"); } } // Start the save timer. mPersistenceTimer.oneshotAfter(sPersistInterval); } return ret; }
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; }
/// wrapper function for all reg-info event tests bool ContactSetTest(UtlString regContactxml, UtlString requestUri, UtlString route) { bool ret = FALSE; instantiateAllTestFixtures( "resource-lists2.xml", "subscription1", "credential1", "sip:127.0.0.1:45141", FALSE); // receive the reg-info subscribe SipMessage request; while(getNextMessageFromRlsClientUnderTest( request, 5 ) ) { UtlString method; request.getRequestMethod(&method); if(!request.isResponse() && 0 == method.compareTo(SIP_SUBSCRIBE_METHOD) ) { // Accept the Subscription, regardless of whether it for a 'dialog' or 'reg' event // in order to stop retransmissions SipMessage regResponse; regResponse.setResponseData(&request, 202, "Accepted", "sip:127.0.0.1:45141"); SipMessage * dispatchedMessage = new SipMessage(regResponse); pResourceServerUnderTest->mClientUserAgent.dispatch(dispatchedMessage); // Deal with the two events separately UtlString eventField; request.getEventField(eventField); if(0 == eventField.compareTo("reg")) { UtlString contactInfo; request.getContactUri(0, &contactInfo); UtlString callid; request.getCallIdField(&callid); int cseq; request.getCSeqField(&cseq, NULL); Url toField; regResponse.getToUrl(toField); SipMessage regNotify; regNotify.setNotifyData(&request, 1, "", "", "reg"); UtlString regInfo ("<?xml version=\"1.0\"?>\r\n" "<reginfo xmlns=\"urn:ietf:params:xml:ns:reginfo\" " "xmlns:gr=\"urn:ietf:params:xml:ns:gruuinfo\" version=\"911\" state=\"full\">\r\n" " <registration aor=\"sip:[email protected]\" id=\"sip:[email protected]\" state=\"active\">\r\n " " <contact id=\"sip:[email protected]@@<"); regInfo.append(contactInfo); regInfo.append(">\" state=\"active\" event=\"registered\" q=\"1\" callid=\""); regInfo.append(callid); regInfo.append("\" cseq=\""); regInfo.appendNumber(cseq); regInfo.append("\">\r\n"); regInfo.append(regContactxml); regInfo.append(" </contact>\r\n" " </registration>\r\n" "</reginfo>"); HttpBody * newBody = new HttpBody (regInfo, strlen(regInfo), "application/reginfo+xml"); regNotify.setContentType("application/reginfo+xml"); regNotify.setBody(newBody); // Set the From field the same as the to field from the 202 response, as it // contains the dialog identifying to tags regNotify.setRawFromField(toField.toString().data()); sendToRlsServerUnderTest( regNotify ); } else if(0 == eventField.compareTo("dialog")) { // If we find a dialog event subscription with the request uri and route // that we are looking for, mark the test as passed UtlString uri; UtlString myRoute; request.getRequestUri(&uri); request.getRouteField(&myRoute); if(0 == uri.compareTo(requestUri) && 0 == route.compareTo(myRoute)) { ret = true; } } } } return ret; }
UtlBoolean TestRegistrar::handleRegisterRequest(SipMessage message) { UtlBoolean messageProcessed = false; SipMessage finalResponse; UtlString responseToAddress; UtlString protocol; int responseToPort; int seqNum; UtlString method; UtlString contactField; int expires; static int retrySeqNum = 0; UtlString callId; message.getCallIdField(&callId); message.getContactField(0, contactField); message.getExpiresField(&expires); message.getCSeqField(&seqNum, &method); message.getFromAddress(&responseToAddress, &responseToPort, &protocol); finalResponse.setContactField(contactField); finalResponse.setExpiresField(expires); finalResponse.setFromField("sip:127.0.0.1:5070", 5070); finalResponse.setSendAddress(contactField, responseToPort); finalResponse.setToField(contactField, responseToPort, protocol); finalResponse.setUserAgentField("TestRegistrar"); // finalResponse.setRegisterData(responseToAddress, contactField, "127.0.0.1", contactField, callId, seqNum); if (contactField.contains("anon")) // anonymous user doesnt need authentication, just say ok { finalResponse.setResponseData(&message, 200, "OK"); } else if (contactField.contains("mike")) // mike requires registration, say 401 { UtlString realm; UtlString requestUser; UtlString requestRealm; UtlString requestNonce; UtlString uriParam; // TBD - 25-jan-2010 work might be needed if these tests are re-enabled message.getDigestAuthorizationData( &requestUser, &requestRealm, &requestNonce, NULL, NULL, &uriParam, NULL, // TBD cnonce NULL, // TBD nonceCount NULL, // TBD qop HttpMessage::SERVER, 0); if (seqNum == retrySeqNum) // if this is a retry response { // if they've sent any auth field, just accept it. // TODO: figure out if a username and password has been encrypted and sent. finalResponse.setCSeqField(++seqNum, SIP_REGISTER_METHOD); finalResponse.setResponseData(&message, 200, "OK"); } else { message.getCSeqField(&seqNum, &method); finalResponse.setCSeqField(++seqNum, method); retrySeqNum = seqNum; #ifdef _WIN32 finalResponse.setAuthenticateData("md5", "TestRegistrar", NULL, NULL, NULL, HttpMessage::HttpEndpointEnum::SERVER ); #else finalResponse.setAuthenticateData("md5", "TestRegistrar", NULL, NULL, NULL, HttpMessage::SERVER ); #endif finalResponse.setResponseData(&message, 401, "Not authorized"); } } else if (contactField.contains("xyzzy")) { // this is our special username that will cause a response // to be echoed back with the 3 digit value after xyzzy. // for instance, the contact xyzzy401 will cause a 401 response int pos = contactField.first("xyzzy"); char szCode[4]; szCode[0] = contactField[pos + 5]; szCode[1] = contactField[pos + 6]; szCode[2] = contactField[pos + 7]; szCode[3] = '\0'; finalResponse.setResponseData(&message, atoi(szCode), "OK"); } mpUserAgent->send(finalResponse); return messageProcessed; }
AuthPlugin::AuthResult CallerAlias::authorizeAndModify(const UtlString& id, /**< The authenticated identity of the * request originator, if any (the null * string if not). * This is in the form of a SIP uri * identity value as used in the * credentials database (user@domain) * without the scheme or any parameters. */ const Url& requestUri, ///< parsed target Uri RouteState& routeState, ///< the state for this request. const UtlString& method,///< the request method AuthResult priorResult,///< results from earlier plugins. SipMessage& request, ///< see AuthPlugin regarding modifying bool bSpiralingRequest, ///< spiraling indication UtlString& reason ///< rejection reason ) { // get the call-id to use in logging UtlString callId; request.getCallIdField(&callId); if ( (priorResult != DENY) // no point in modifying a request that won't be sent ) { UtlString callerFrom; UtlString callerFromTagOffsetStr; UtlString aliasFrom; UtlString aliasFromTagOffsetStr; UtlString originalFromTag; if ( !routeState.getParameter(mInstanceName.data(), CALLER_FROM_PARAM, callerFrom) || !routeState.getParameter(mInstanceName.data(), CALLER_TAG_OFFSET_PARAM, callerFromTagOffsetStr) || !routeState.getParameter(mInstanceName.data(), ALIAS_FROM_PARAM, aliasFrom) || !routeState.getParameter(mInstanceName.data(), ALIAS_TAG_OFFSET_PARAM, aliasFromTagOffsetStr) || !routeState.originalCallerFromTagValue(mInstanceName.data(), originalFromTag) ) { if ( routeState.isMutable() && routeState.directionIsCallerToCalled(mInstanceName.data()) ) // a new dialog? { /* * Get the callers identity by getting the caller URI and: * remove all parameters * remove the scheme name */ UtlString callerIdentity; UtlString originalFromField; request.getFromField(&originalFromField); Url originalFromUrl(originalFromField); /* * Extract the from identity as a key for the caller alias table * Start with the From header field (someday we should use the Identity if present) */ Url fromUrl(originalFromUrl); fromUrl.removeParameters(); // parameters are not relevant for this Url::Scheme fromUrlScheme = fromUrl.getScheme(); switch (fromUrlScheme) { case Url::SipsUrlScheme: // sips and sip are equivalent for identity purposes, // so just set to sip fromUrl.setScheme(Url::SipUrlScheme); // and fall through to extract the identity... case Url::SipUrlScheme: // case Url::TelUrlScheme: will go here, since 'tel' and 'sip' are the same length fromUrl.getUri(callerIdentity); callerIdentity.remove(0,4 /* strlen("sip:") */); // strip off the scheme name break; default: // for all other schemes, treat identity as null Os::Logger::instance().log(FAC_SIP, PRI_WARNING, "CallerAlias[%s]::check4andApplyAlias From uses unsupported scheme '%s'" " - using null identity", mInstanceName.data(), Url::schemeName(fromUrlScheme) ); break; } /* * Determine whether the identity is one for which this proxy * is authoritative; if not, we will not use wildcard matches. */ bool identityIsLocal = mpSipRouter->isLocalDomain(fromUrl); // now we have callerIdentity set; use for looking up each contact. Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "CallerAlias[%s]::check4andApplyAlias " "\n caller '%s' %s", mInstanceName.data(), callerIdentity.data(), identityIsLocal ? "is local" : "is not local" ); /* * Examine the request URI, * checking for a caller alias set for its domain(including asssociated gateway sipxecsLineid) with callerIdentity */ UtlString sipxecsLineIdField; requestUri.getUrlParameter(SIPX_SIPXECS_LINEID_URI_PARAM, sipxecsLineIdField); Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "getUrlParameter: sipxecsLineid[%s]" " in CallerAlias", sipxecsLineIdField.data() ); UtlString targetDomain; requestUri.getHostWithPort(targetDomain); if (!(sipxecsLineIdField.isNull())) { targetDomain.append(";").append(SIPX_SIPXECS_LINEID_URI_PARAM).append("=").append(sipxecsLineIdField.data()); } Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "CallerAlias::targetDomain [%s]", targetDomain.data() ); // look up any caller alias for this identity and contact domain UtlString callerAlias; if (identityIsLocal && getCallerAlias(callerIdentity, targetDomain, callerAlias) ) { // found a caller alias, so rewrite the From information /* * The From header requires special handling * - we need to preserve the tag, if any, from the original header */ originalFromUrl.getFieldParameter("tag", originalFromTag); Url newFromUrl(callerAlias.data()); newFromUrl.removeFieldParameter("tag"); // specifying a tag is a no-no if ( !originalFromTag.isNull() ) { newFromUrl.setFieldParameter("tag", originalFromTag.data()); } UtlString newFromFieldValue; newFromUrl.toString(newFromFieldValue); // log the change we are making before stripping the tag from the field values Os::Logger::instance().log( FAC_SIP, PRI_INFO, "CallerAlias[%s]::check4andApplyAlias call %s set caller alias\n" " Original-From: %s\n" " Aliased-From: %s", mInstanceName.data(), callId.data(), originalFromField.data(), newFromFieldValue.data() ); // rewrite the caller identity with the aliased value request.setRawFromField(newFromFieldValue.data()); // Factor the tag values out of the field values stored in the RouteState // We do this because otherwise we'll end up encoding and sending two copies // of the tag; since some phones send really long tag values (no one knows why), // this can cause such large Record-Route headers that they cause interop problems. if ( ! originalFromTag.isNull() ) { // find the offset of the tag value in the callers from field ssize_t callerFromTagOffset; callerFromTagOffset = originalFromField.index(originalFromTag); callerFromTagOffsetStr.appendNumber(callerFromTagOffset); // strip the tag value from the original From value to be stored in the RouteState originalFromField.replace(callerFromTagOffset, originalFromTag.length(), ""); // find the offset of the tag value in the aliased from field ssize_t aliasFromTagOffset; aliasFromTagOffset = newFromFieldValue.index(originalFromTag); aliasFromTagOffsetStr.appendNumber(aliasFromTagOffset); // strip the tag value from the aliased From value to be stored in the RouteState newFromFieldValue.replace(aliasFromTagOffset, originalFromTag.length(), ""); } // save the original and new values so that we can fix them later routeState.setParameter(mInstanceName.data(), CALLER_FROM_PARAM,originalFromField); routeState.setParameter(mInstanceName.data(), CALLER_TAG_OFFSET_PARAM,callerFromTagOffsetStr); routeState.setParameter(mInstanceName.data(), ALIAS_FROM_PARAM,newFromFieldValue); routeState.setParameter(mInstanceName.data(), ALIAS_TAG_OFFSET_PARAM,aliasFromTagOffsetStr); } else { Os::Logger::instance().log( FAC_SIP, PRI_DEBUG, "CallerAlias[%s]::check4andApplyAlias call %s found no alias", mInstanceName.data(), callId.data() ); } } else { Os::Logger::instance().log(FAC_SIP, PRI_DEBUG, "CallerAlias[%s]::authorizeAndModify " "not mutable - no rewrite", mInstanceName.data() ); } } else // the callerFrom and aliasFrom parameters were found { /* * This request has had its From rewritten, so fix either the From * or the To depending on which direction this request is going. */ if (!request.isResponse()) // can't modify responses, so don't bother { size_t tagOffset; if (routeState.directionIsCallerToCalled(mInstanceName.data())) { // replace the from tag value in the stored aliased header tagOffset = strtol(aliasFromTagOffsetStr.data(), NULL, 10); aliasFrom.insert(tagOffset, originalFromTag); // put the aliased header into the message request.setRawFromField(aliasFrom); Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "CallerAlias[%s]::authorizeAndModify " "call %s reset From", mInstanceName.data(), callId.data() ); } else // direction is Called to Caller { // replace the from tag value in the stored original header tagOffset = strtol(callerFromTagOffsetStr.data(), NULL, 10); callerFrom.insert(tagOffset, originalFromTag); request.setRawToField(callerFrom.data()); Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "CallerAlias[%s]::authorizeAndModify " "call %s reset To", mInstanceName.data(), callId.data() ); } } } } return AuthPlugin::CONTINUE; }
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); } } } }
AuthPlugin::AuthResult TransferControl::authorizeAndModify(const UtlString& id, /**< The authenticated identity of the * request originator, if any (the null * string if not). * This is in the form of a SIP uri * identity value as used in the * credentials database (user@domain) * without the scheme or any parameters. */ const Url& requestUri, ///< parsed target Uri RouteState& routeState, ///< the state for this request. const UtlString& method,///< the request method AuthResult priorResult,///< results from earlier plugins. SipMessage& request, ///< see AuthPlugin wrt modifying bool bSpiralingRequest, ///< request spiraling indication UtlString& reason ///< rejection reason ) { AuthResult result = CONTINUE; // get the call-id to use in logging UtlString callId; request.getCallIdField(&callId); UtlString hostAddress; int hostPort; UtlString hostProtocols; //requestUri.getHostAddress(hostAddress); //request.getContactUri(0, &hostAddress);; request.getContactAddress(0, &hostAddress,&hostPort,&hostProtocols); if (DENY != priorResult) { if (method.compareTo(SIP_REFER_METHOD) == 0) { UtlString targetStr; if (request.getReferToField(targetStr)) { Url target(targetStr, Url::NameAddr); // parse the target URL UtlString targetMethod; if ( Url::SipUrlScheme == target.getScheme() /* REFER can create requests other than INVITE: we don't care about those * * so check that the method is INVITE or is unspecified (INVITE is the default) */ && ( ! target.getUrlParameter(SIP_METHOD_URI_PARAMETER, targetMethod) || (0==targetMethod.compareTo(SIP_INVITE_METHOD, UtlString::ignoreCase)) )) { if (id.isNull()) { // UnAuthenticated REFER. Do challenge the REFER to confirm the // identity of the transferor. Note: prior to XECS-2487, we used to challenge // only the unauthenticated REFERs that didn't carry a Replaces header. // The fix for XECS-2487 now requires that all unauthenticated REFERs // be challenged so that consultative transfers get routed properly // when user-based gateway section is used. See tracker for the details if (mpSipRouter->isLocalDomain(target)) { //White list of two servets to let Exchange REFER to sipXecs endpoints if (hostAddress.compareTo(server1, UtlString::ignoreCase) == 0 || hostAddress.compareTo(server2, UtlString::ignoreCase) == 0) { Os::Logger::instance().log(FAC_AUTH, PRI_INFO, "TransferControl[%s]::authorizeAndModify " "Whitelist host '%s' in call '%s'", mInstanceName.data(),hostAddress.data(),callId.data() ); result = ALLOW; //Whitelist matched so allow the transfer }else{ Os::Logger::instance().log(FAC_AUTH, PRI_INFO, "TransferControl[%s]::authorizeAndModify " "challenging transfer in call '%s' from host '%s'", mInstanceName.data(), callId.data(),hostAddress.data() ); result = DENY; // we need an identity to attach to the Refer-To URI } } else { /* * This is a transfer to a target outside our domain, so let it go * unchallenged. See XECS-806 */ Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "TransferControl[%s]::authorizeAndModify " "allowing foriegn transfer in call '%s'", mInstanceName.data(), callId.data() ); // Add the References to the refer-to. Adding the callId field as a reference // header (will be used in resulting INVITE) in the Refer-To provides us // with enough information to be able to logically tie the calls together. // Useful for CDR records. UtlString refcallId(callId); refcallId.append(";rel=refer"); target.setHeaderParameter(SIP_REFERENCES_FIELD, refcallId.data()); Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "TransferControl[%s]::authorizeAndModify " "adding Reference field [%s] to refer-to", mInstanceName.data(), callId.data() ); request.setReferToField(target.toString().data()); result = ALLOW; } } else { UtlString contactString; request.getContactEntry(0, &contactString); Url contactUri( contactString ); UtlString userId; contactUri.getUserId(contactString); Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "TransferControl::authorizeAndModify - Contact field is: %s ", contactString.data()); if (contactString != "callcontroller") { // Authenticated REFER // annotate the refer-to with the authenticated controller identity SipXauthIdentity controllerIdentity; controllerIdentity.setIdentity(id); controllerIdentity.encodeUri(target); // add the References to the refer-to. UtlString refcallId(callId); refcallId.append(";rel=refer"); target.setHeaderParameter(SIP_REFERENCES_FIELD, refcallId.data()); Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "TransferControl[%s]::authorizeAndModify " "adding Reference field [%s] to refer-to", mInstanceName.data(), callId.data() ); request.setReferToField(target.toString().data()); } } } else { Os::Logger::instance().log(FAC_AUTH, PRI_WARNING, "TransferControl[%s]::authorizeAndModify " "unrecognized refer target '%s' for call '%s'", mInstanceName.data(), targetStr.data(), callId.data() ); } } else { // REFER without a Refer-To header... incorrect, but just ignore it. Os::Logger::instance().log(FAC_AUTH, PRI_WARNING, "TransferControl[%s]::authorizeAndModify " "REFER method without Refer-To in call '%s'", mInstanceName.data(), callId.data() ); } } else if (method.compareTo(SIP_INVITE_METHOD) == 0) { UtlString targetCallId; UtlString targetFromTag; UtlString targetToTag; if (request.getReplacesData(targetCallId, targetToTag, targetFromTag)) { /* * This is an INVITE with Replaces: probably either the completion * of a call pickup or a consultative transfer. * In any case, it will not create a new call - just connect something * to an existing call - so we don't need to make any new authorization * decisions. */ result = ALLOW; } else { // INVITE without Replaces: is not a transfer - ignore it. } } else { // neither REFER nor INVITE, so is not a transfer - ignore it. } } else { // Some earlier plugin already denied this - don't waste time figuring it out. Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "TransferControl[%s]::authorizeAndModify " "prior authorization result %s for call %s", mInstanceName.data(), AuthResultStr(priorResult), callId.data() ); } return result; }
void AppearanceTest() { instantiateAllTestFixtures( "appearance-groups1.xml", "subscription1", "credential1", "177.0.0.1:54140"); UtlString sharedUri = "sip:[email protected]:54140"; UtlString app1uri = "sip:127.0.0.1:45141"; UtlString dialogHandle; // receive the reg-info subscribe SipMessage request; UtlString b; ssize_t l; while(getNextMessageFromAppearanceAgentUnderTest( request, 5 )) { request.getBytes(&b, &l); OsSysLog::add(FAC_RLS, PRI_DEBUG, "got message %s", b.data()); UtlString method; request.getRequestMethod(&method); if(!request.isResponse() && 0 == method.compareTo(SIP_SUBSCRIBE_METHOD) ) { // Accept the Subscription, regardless of whether it for a 'dialog' or 'reg' event // in order to stop retransmissions SipMessage regResponse; regResponse.setResponseData(&request, 202, "Accepted", app1uri); SipMessage * dispatchedMessage = new SipMessage(regResponse); dispatchedMessage->getBytes(&b, &l); OsSysLog::add(FAC_RLS, PRI_DEBUG, "sent message %s", b.data()); pAppearanceAgentUnderTest->mServerUserAgent.dispatch(dispatchedMessage); // Deal with the two events separately UtlString eventField; request.getEventField(eventField); if(0 == eventField.compareTo("reg")) { UtlString contactInfo; request.getContactUri(0, &contactInfo); UtlString callid; request.getCallIdField(&callid); int cseq; request.getCSeqField(&cseq, NULL); Url toField; regResponse.getToUrl(toField); SipMessage regNotify; regNotify.setNotifyData(&request, 1, "", "", "reg"); UtlString regInfo ("<?xml version=\"1.0\"?>\r\n" "<reginfo xmlns=\"urn:ietf:params:xml:ns:reginfo\" " "xmlns:gr=\"urn:ietf:params:xml:ns:gruuinfo\" version=\"911\" state=\"full\">\r\n" " <registration aor=\"sip:[email protected]:54140\" id=\"sip:[email protected]:54140\" state=\"active\">\r\n " " <contact id=\"sip:[email protected]:54140@@<"); regInfo.append(contactInfo); regInfo.append(">\" state=\"active\" event=\"registered\" q=\"1\" callid=\""); regInfo.append(callid); regInfo.append("\" cseq=\""); regInfo.appendNumber(cseq); regInfo.append("\">\r\n"); regInfo.append("<uri>"); regInfo.append(app1uri); regInfo.append("</uri>"); regInfo.append(" </contact>\r\n" " </registration>\r\n" "</reginfo>"); HttpBody * newBody = new HttpBody (regInfo, strlen(regInfo), "application/reginfo+xml"); regNotify.setContentType("application/reginfo+xml"); regNotify.setBody(newBody); // Set the From field the same as the to field from the 202 response, as it // contains the dialog identifying to tags regNotify.setRawFromField(toField.toString().data()); sendToAppearanceAgentUnderTest( regNotify ); regNotify.getBytes(&b, &l); OsSysLog::add(FAC_RLS, PRI_DEBUG, "sent reg NOTIFY to AppAgent"); OsSysLog::add(FAC_RLS, PRI_DEBUG, "sent message %s", b.data()); } else if (0 == eventField.compareTo(SLA_EVENT_TYPE)) { // should send empty NOTIFY, but no one will care // save dialogHandle for this subscription/Appearance (ignore retransmissions) if (dialogHandle.isNull()) { SipMessage fake(b); fake.getDialogHandle(dialogHandle); OsSysLog::add(FAC_RLS, PRI_DEBUG, "got SUBSCRIBE(sla) request: dialogHandle %s", dialogHandle.data()); } } } } CPPUNIT_ASSERT( !dialogHandle.isNull() ); OsSysLog::add(FAC_RLS, PRI_DEBUG, "we now have an Appearance - test it"); AppearanceGroup* pAppGroup = pAppearanceAgentUnderTest->getAppearanceGroupSet(). findAppearanceGroup(sharedUri); CPPUNIT_ASSERT( pAppGroup ); Appearance* pApp = pAppGroup->findAppearance(dialogHandle); CPPUNIT_ASSERT( pApp ); ASSERT_STR_EQUAL( app1uri.data(), pApp->getUri()->data() ); // test adding a new dialog const char* dialogEventString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"0\" state=\"partial\" entity=\"sip:[email protected]:54140\">\n" "<dialog id=\"1\" call-id=\"[email protected]\" local-tag=\"264460498\" remote-tag=\"1c10982\" direction=\"recipient\">\n" "<state>confirmed</state>\n" "<local>\n" "<identity>[email protected]:5120</identity>\n" "<target uri=\"sip:[email protected]:5120\">\n" "<param pname=\"x-line-id\" pval=\"0\"/>\n" "<param pname=\"+sip.rendering\" pval=\"yes\"/>\n" "</target>\n" "</local>\n" "<remote>\n" "<identity>[email protected]</identity>\n" "</remote>\n" "</dialog>\n" "</dialog-info>\n" ; SipDialogEvent dialogEvent(dialogEventString); bool bFullContentChanged = false; bool bPartialContentChanged = pApp->updateState(&dialogEvent, bFullContentChanged); CPPUNIT_ASSERT(bPartialContentChanged); CPPUNIT_ASSERT(bFullContentChanged); pApp->dumpState(); CPPUNIT_ASSERT(pApp->appearanceIsBusy()); CPPUNIT_ASSERT(pApp->appearanceIdIsSeized("0")); CPPUNIT_ASSERT(!pApp->appearanceIdIsSeized("1")); // simulate user putting the call on hold const char* dialogEventString2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"0\" state=\"partial\" entity=\"sip:[email protected]:54140\">\n" "<dialog id=\"1\" call-id=\"[email protected]\" local-tag=\"264460498\" remote-tag=\"1c10982\" direction=\"recipient\">\n" "<state>confirmed</state>\n" "<local>\n" "<identity>[email protected]:5120</identity>\n" "<target uri=\"sip:[email protected]:5120\">\n" "<param pname=\"x-line-id\" pval=\"0\"/>\n" "<param pname=\"+sip.rendering\" pval=\"no\"/>\n" "</target>\n" "</local>\n" "<remote>\n" "<identity>[email protected]</identity>\n" "</remote>\n" "</dialog>\n" "</dialog-info>\n" ; SipDialogEvent dialogEvent2(dialogEventString2); bPartialContentChanged = pApp->updateState(&dialogEvent2, bFullContentChanged); CPPUNIT_ASSERT(bPartialContentChanged); CPPUNIT_ASSERT(bFullContentChanged); pApp->dumpState(); CPPUNIT_ASSERT(!pApp->appearanceIsBusy()); CPPUNIT_ASSERT(pApp->appearanceIdIsSeized("0")); CPPUNIT_ASSERT(!pApp->appearanceIdIsSeized("1")); // test MESSAGE debug handling const char* message = "MESSAGE sip:[email protected]:54140 SIP/2.0\r\n" "From: <sip:[email protected]>;tag=17211757-9E4FBD78\r\n" "To: <sip:[email protected]:54140>\r\n" "CSeq: 1 MESSAGE\r\n" "Call-ID: 51405734-b9be4835-dcd9d196\r\n" "Contact: <sip:[email protected]>\r\n" "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, INFO, MESSAGE, SUBSCRIBE, NOTIFY, PRACK, UPDATE, REFER\r\n" "Event: dialog\r\n" "User-Agent: UnitTest\r\n" "Accept-Language: en\r\n" "Accept: application/dialog-info+xml\r\n" "Max-Forwards: 70\r\n" "Expires: 3600\r\n" "Content-Length: 0\r\n" "\r\n"; // send the MESSAGE SipMessage messageRequest( message, strlen( message ) ); CPPUNIT_ASSERT( sendToAppearanceAgentUnderTest( messageRequest ) ); // receive the 200 OK response SipMessage response; CPPUNIT_ASSERT( getNextMessageFromAppearanceAgentUnderTest( response, 5 ) ); CPPUNIT_ASSERT( response.isResponse() ); CPPUNIT_ASSERT( response.getResponseStatusCode() == SIP_OK_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()) { 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); }
// Send a NOTIFY to all subscribers to the given resourceId and eventTypeKey. UtlBoolean SipSubscribeServer::notifySubscribers(const char* resourceId, const char* eventTypeKey, const char* eventType, const char* reason) { OsSysLog::add(FAC_SIP, PRI_DEBUG, "SipSubscribeServer::notifySubscribers resourceId '%s', eventTypeKey '%s', eventType '%s', reason '%s'", resourceId, eventTypeKey, eventType, reason ? reason : "[null]"); UtlBoolean notifiedSubscribers = FALSE; UtlString eventName(eventType); lockForRead(); SubscribeServerEventData* eventData = dynamic_cast <SubscribeServerEventData*> (mEventDefinitions.find(&eventName)); // Get the event-specific info to find subscriptions interested in // this content. if (eventData) { int numSubscriptions = 0; SipMessage** notifyArray = NULL; UtlString** acceptHeaderValuesArray = NULL; // Select the subscriptionChange value to describe what we're doing // with the subscriptions. enum SipSubscriptionMgr::subscriptionChange change; { // Select the correct format for generating the Subscription-State value. UtlString* formatp = NULL; // We may need a temp UtlString. const char* format; if (reason == NULL) { format = "active;expires=%ld"; change = SipSubscriptionMgr::subscriptionContinues; } else if (strcmp(reason, terminationReasonSilent) == 0) { // Do not admit that the subscription is ending. format = "active;expires=%ld"; change = SipSubscriptionMgr::subscriptionTerminatedSilently; } else if (strcmp(reason, terminationReasonNone) == 0) { format = "terminated"; change = SipSubscriptionMgr::subscriptionTerminated; } else { // Allocate a UtlString and assemble the Subscription-State format in it. formatp = new UtlString(); formatp->append("terminated;reason="); formatp->append(reason); format = formatp->data(); change = SipSubscriptionMgr::subscriptionTerminated; } // Construct a NOTIFY (without body) for each subscription, containing // the dialog-related information about each subscription. eventData->mpEventSpecificSubscriptionMgr-> createNotifiesDialogInfo(resourceId, eventTypeKey, format, numSubscriptions, acceptHeaderValuesArray, notifyArray); OsSysLog::add(FAC_SIP, PRI_DEBUG, "SipSubscribeServer::notifySubscribers numSubscriptions for '%s' = %d", resourceId, numSubscriptions); // Free the temporary UtlString, if necessary. if (formatp) { delete formatp; } } // For each NOTIFY, add the subscription-related information and then // send it. for (int notifyIndex = 0; notifyIndex < numSubscriptions; notifyIndex++) { SipMessage* notify = notifyArray[notifyIndex]; // Check to see if the dialog information could be added. // (The subscription might have been destroyed between when // it was decided to respond to it, and when the dialog information // was retrieved.) UtlString callId; notify->getCallIdField(&callId); if (!callId.isNull()) { if (change != SipSubscriptionMgr::subscriptionTerminatedSilently) { // Fill in the NOTIFY request body/content eventData->mpEventSpecificHandler-> getNotifyContent(resourceId, eventTypeKey, eventType, *(eventData->mpEventSpecificContentMgr), *(acceptHeaderValuesArray[notifyIndex]), *notify, eventData->mEventSpecificFullState, NULL); // Call the application callback to edit the NOTIFY // content if that is required for this event type. // Also gets 'version' (if relevant) and 'savedEventTypeKey'. int version; UtlString savedEventTypeKey; eventData->mpEventSpecificSubscriptionMgr-> updateNotifyVersion(eventData->mpEventSpecificContentVersionCallback, *notify, version, savedEventTypeKey); // Update the saved record of the NOTIFY CSeq and the // XML version number for the specified savedEventTypeKey, // as needed by the subscription manager. // In practice, this is only used by SipPersistentSubscriptionMgr // to write the NOTIFY Cseq and XML version into the IMDB. eventData->mpEventSpecificSubscriptionMgr-> updateVersion(*notify, version, savedEventTypeKey); // Set the Contact header. setContact(notify); // Send the NOTIFY request. eventData->mpEventSpecificUserAgent->send(*notify); } if (change != SipSubscriptionMgr::subscriptionContinues) { // Remove the record of the subscription. UtlString dialogHandle; notify->getDialogHandle(dialogHandle); eventData->mpEventSpecificSubscriptionMgr-> endSubscription(dialogHandle, change); } } } // Free the NOTIFY requests and accept header field values. eventData->mpEventSpecificSubscriptionMgr-> freeNotifies(numSubscriptions, acceptHeaderValuesArray, notifyArray); } // event type not enabled else { OsSysLog::add(FAC_SIP, PRI_ERR, "SipSubscribeServer::notifySubscribers " "event type: %s not enabled - " "Why are we seeing a callback for this?", eventName.data()); } unlockForRead(); return notifiedSubscribers; }
/// Terminate all subscriptions and accept no new ones. void SipSubscribeServer::shutdown(const char* reason) { // If reason is NULL, use the default reason established by the constructor. if (!reason) { reason = mDefaultTermination; } OsSysLog::add(FAC_SIP, PRI_DEBUG, "SipSubscribeServer::shutdown reason '%s'", reason ? reason : "[null]"); lockForRead(); // Select the subscriptionChange value to describe what we're doing // with the subscriptions. enum SipSubscriptionMgr::subscriptionChange change; // Select the correct format for generating the Subscription-State value. UtlString* formatp = NULL; // We may need a temp UtlString. const char* format; if (reason == NULL) { format = "active;expires=%ld"; change = SipSubscriptionMgr::subscriptionContinues; } else if (strcmp(reason, terminationReasonSilent) == 0) { // Do not admit that the subscription is ending. format = "active;expires=%ld"; change = SipSubscriptionMgr::subscriptionTerminatedSilently; } else if (strcmp(reason, terminationReasonNone) == 0) { format = "terminated"; change = SipSubscriptionMgr::subscriptionTerminated; } else { // Allocate a UtlString and assemble the Subscription-State format in it. formatp = new UtlString(); formatp->append("terminated;reason="); formatp->append(reason); format = formatp->data(); change = SipSubscriptionMgr::subscriptionTerminated; } // For each event type that is registered, delete all the subscriptions. UtlHashBagIterator event_itor(mEventDefinitions); SubscribeServerEventData* eventData; while ((eventData = dynamic_cast <SubscribeServerEventData*> (event_itor()))) { // Unregister interest in SUBSCRIBE requests and NOTIFY // responses for this event type, so we do not service new subscriptions. eventData->mpEventSpecificUserAgent->removeMessageObserver(*(getMessageQueue())); int numSubscriptions = 0; SipMessage** notifyArray = NULL; UtlString** acceptHeaderValuesArray = NULL; UtlString** resourceIdArray = NULL; UtlString** eventTypeKeyArray = NULL; // :TODO: The four situations where NOTIFYs are generated should // be factored into a series of methods in // mpEventSpecificSubscriptionMgr that generate NOTIFYs // sequentially, and for each NOTIFY, call a common service // method that does the remaining operations and sends the // NOTIFY. // Construct a NOTIFY (without body) for every subscription, containing // the dialog-related information about each subscription. eventData->mpEventSpecificSubscriptionMgr-> createNotifiesDialogInfoEvent(static_cast <const UtlString&> (*eventData), format, numSubscriptions, acceptHeaderValuesArray, notifyArray, resourceIdArray, eventTypeKeyArray); OsSysLog::add(FAC_SIP, PRI_DEBUG, "SipSubscribeServer::shutdown eventType = '%s', numSubscriptions = %d", eventData->data(), numSubscriptions); // For each NOTIFY, add the subscription-related information and then // send it. for (int notifyIndex = 0; notifyIndex < numSubscriptions; notifyIndex++) { SipMessage* notify = notifyArray[notifyIndex]; // Check to see if the dialog information could be added. // (The subscription might have been destroyed between when // it was decided to respond to it, and when the dialog information // was retrieved.) UtlString callId; notify->getCallIdField(&callId); if (!callId.isNull()) { if (change != SipSubscriptionMgr::subscriptionTerminatedSilently) { // Fill in the NOTIFY request body/content eventData->mpEventSpecificHandler-> getNotifyContent(*(resourceIdArray[notifyIndex]), *(eventTypeKeyArray[notifyIndex]), *eventData, *(eventData->mpEventSpecificContentMgr), *(acceptHeaderValuesArray[notifyIndex]), *notify, eventData->mEventSpecificFullState, NULL); // Call the application callback to edit the NOTIFY // content if that is required for this event type. // Also gets 'version' (if relevant) and 'savedEventTypeKey'. int version; UtlString savedEventTypeKey; eventData->mpEventSpecificSubscriptionMgr-> updateNotifyVersion(eventData->mpEventSpecificContentVersionCallback, *notify, version, savedEventTypeKey); // Set the Contact header. setContact(notify); // Send the NOTIFY request. eventData->mpEventSpecificUserAgent->send(*notify); } // Remove the record of the subscription. UtlString dialogHandle; notify->getDialogHandle(dialogHandle); eventData->mpEventSpecificSubscriptionMgr-> endSubscription(dialogHandle, change); } } // Free the NOTIFY requests and accept header field values. SipSubscriptionMgr::freeNotifies(numSubscriptions, acceptHeaderValuesArray, notifyArray); // Free the resource and event type arrays. for (int index = 0; index < numSubscriptions; index++) { delete resourceIdArray[index]; delete eventTypeKeyArray[index]; } delete[] resourceIdArray; delete[] eventTypeKeyArray; // Remove eventData from mEventDefinitions. mEventDefinitions.removeReference(eventData); delete eventData; } unlockForRead(); // Free the temporary UtlString, if necessary. if (formatp) { delete formatp; } lockForWrite(); mEventDefinitions.destroyAll(); unlockForWrite(); }
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); }
AuthPlugin::AuthResult MSFT_ExchangeTransferHack::authorizeAndModify(const UtlString& id, const Url& requestUri, RouteState& routeState, const UtlString& method, AuthResult priorResult, SipMessage& request, bool bSpiralingRequest, UtlString& reason ) { AuthResult result = CONTINUE; // we modify, but never make an authorization decision if (mUserAgentRegEx) // if not configured, do nothing { // get the call-id to use in logging UtlString callId; request.getCallIdField(&callId); /* * Note: a REFER from Exchange, with the bug we are hunting here, will always look like * it is tranferring to some foreign domain. This will cause the TransferControl AuthPlugin * to ALLOW it. If TransferControl starts challenging REFER from Exchange, that may mean * that MSFT has fixed the bug this plugin is meant to compensate for. */ if (DENY != priorResult) // ignore anything some other plugin has already nixed { if (method.compareTo(SIP_REFER_METHOD) == 0) // we only care about REFER { UtlString userAgent; request.getUserAgentField( &userAgent ); if (mUserAgentRegEx->Search(userAgent)) // does this look like Exchange? { UtlString targetStr; if (request.getReferToField(targetStr)) // get the address of the transfer target { Url target(targetStr); if (Url::SipUrlScheme == target.getScheme()) // target address parsed ok? { // check whether or not this is REFER with Replaces UtlString targetDialog; if (!target.getHeaderParameter(SIP_REPLACES_FIELD, targetDialog)) { /* * This is a REFER without Replaces from Exchange * so check the domain parts of the two URIs to see if they match. */ // Get the domain part of the transfer-target URI UtlString targetDomain; target.getHostWithPort(targetDomain); // Get the domain part of the request URI UtlString requestDomain; requestUri.getHostWithPort(requestDomain); if (targetDomain.compareTo(requestDomain, UtlString::ignoreCase) == 0) { // The domains are the same; this is the bug we're looking for... UtlString correctDomain; mpSipRouter->getDomain(correctDomain); target.setHostAddress(correctDomain); target.setHostPort(PORT_NONE); UtlString modifiedTarget; target.toString(modifiedTarget); request.setReferToField(modifiedTarget.data()); Os::Logger::instance().log(FAC_AUTH, PRI_INFO, "MSFT_ExchangeTransferHack[%s]::authorizeAndModify " "corrected transfer target domain in call '%s'\n" "changed '@%s' -> '@%s'", mInstanceName.data(), callId.data(), targetDomain.data(), correctDomain.data() ); } else { // oh my god... did MSFT fix Exchange? Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "MSFT_ExchangeTransferHack[%s]::authorizeAndModify " "request and target domain differ in '%s'; not modified", mInstanceName.data(), callId.data() ); } } else { // This is a REFER with Replaces from Exchange // I don't expect this to happen, but if it does then don't mess with it. Os::Logger::instance().log(FAC_AUTH, PRI_INFO, "MSFT_ExchangeTransferHack[%s]::authorizeAndModify " "allowing REFER with Replaces in call '%s' to '%s'; no action", mInstanceName.data(), callId.data(), targetDialog.data() ); } } else { Os::Logger::instance().log(FAC_AUTH, PRI_WARNING, "MSFT_ExchangeTransferHack[%s]::authorizeAndModify " "unrecognized refer target '%s' for call '%s'", mInstanceName.data(), targetStr.data(), callId.data() ); } } else { // REFER without a Refer-To header... incorrect, but just ignore it. Os::Logger::instance().log(FAC_AUTH, PRI_WARNING, "MSFT_ExchangeTransferHack[%s]::authorizeAndModify " "REFER method without Refer-To in call '%s'", mInstanceName.data(), callId.data() ); } } else { Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "MSFT_ExchangeTransferHack[%s]::authorizeAndModify " "User-Agent '%s' does not match recognizer in %s", mInstanceName.data(), userAgent.data(), callId.data() ); } } else { // not a REFER - ignore it. } } else { // Some earlier plugin already decided on this - don't waste time figuring it out. Os::Logger::instance().log(FAC_AUTH, PRI_DEBUG, "MSFT_ExchangeTransferHack[%s]::authorizeAndModify " "prior authorization result %s for call %s", mInstanceName.data(), AuthResultStr(priorResult), callId.data() ); } } return result; }