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; }
bool CallTracker::notifyIncomingDialogFormingInvite( SipMessage& request, RouteState& routeState, const EndpointDescriptor*& prCaller, const EndpointDescriptor*& prCallee ) { bool bResult = false; UtlString sessionContextHandle; SessionContext* pSessionContext; // We need to process and incoming dialog-forming request. In some scenarios, it is possible // for such a request to visit us twice. If there is no session-id encoded in the RouteState, // it indicates that this is the first time we see it. if( !getSessionContextHandle( routeState, sessionContextHandle ) ) { // we are not tracking this session yet. Allocate a new session context to track // this particular fork and save a copy of the message's SDP body if not already done - this // may be useful if the request spirals back to us and we need to restore a patched // SDP to its original form. pSessionContext = createSessionContextAndSetHandle( request, routeState, true, sessionContextHandle ); if( pSessionContext ) { prCaller = &pSessionContext->getEndpointDescriptor( CALLER ); prCallee = &pSessionContext->getEndpointDescriptor( CALLEE ); if( !mpSavedOriginalSdpOfferCopy ) { mpSavedOriginalSdpOfferCopy = const_cast<SdpBody*>( request.getSdpBody() ); } bResult = true; } else { Os::Logger::instance().log( FAC_NAT, PRI_ERR, "CallTracker[%zd]::notifyIncomingDialogFormingInvite[1] failed to create session context ", mHandle ); } } else { // the RouteState already has a session-id which means that we are already tracking this // session. There are several scenarios that can lead up to that situation. Consider this: // // User A--------sipXecs1-----------sipXecs2-------User C(call forwarded to User B) // User B---+ // User B---+ // // Users A & B are registered against sipXecs1 and User C is registered againt sipXecs2. // User B is registered from two endpoints. // User C has its sipXecs-based call forward all calls set to SetB@sipXecs1. // When User A calls User C, the INVITE takes the following path: // UserA-->sipXecs1-->sipXecs2-->sipXecs1-->UserB // +->UserB // A SessionContext is created when sipXecs1 is visited the first time and its session-id // is encoded in the RouteState. When sipXecs1 is revisited by the INVITE it sees that // the RouteState already carries a session-id which tells it that this is a revisiting // INVITE. // If no special handling is performed here, several problems can appear. // // Problem #1- callee's real location wasn't known when SessionContext was created on first visit // ============================================================================================== // When the dialog-forming INVITE was first seen by sipXecs1, the target was UserC@sipxecs2. // Given that the call was routed via a SIP trunk, sipXecs1 didn't know the real location of the // callee and therefore pegged the callee a being at an UNKNOWN location and imposed the use // of a media relay using the media relay's public IP adddress as the media connection address // in the SDP presented to the callee. // When the dialog-forming INVITE comes back to sipxecs1, the request target of the INVITE, namely // UserB@sipxecs1, is known to sipXecs1 and can therefore promote its location information // from UNKNOWN to its precise location based on the information it collected from the set // at registration time. The SessionContext that was initially created by the CallTracker to // handle the NAT traversal was created at the time when the location of the callee was unknown. // Now that the location is known, the old SessionContext can be abandonned and a new one created // that will choose whether or not to involve a media relay based on an accurate representation // of the caller and callee's locations. // // Solution to Problem #1 // ====================== // - Restore the SDP to its original state (i.e. before any transformation) if it got changed by us. // - Allocate a new SessionContext that will be responsible handling all the dialogs forked off // of this INVITE using the updated callee location information. // - Remove any 'id' param containing the handle of the original SessionContext in the Vias // of the request. This procedure will prevent the original SessionContext from handling // responses pertaining to the newly created SessionContext. pSessionContext = getSessionContextFromHandle( sessionContextHandle ); if( pSessionContext ) { // First, allocate a new SessionContext that will take care of this new fork. UtlString handleOfnewSessionHandle; pSessionContext = createSessionContextAndSetHandle( request, routeState, true, handleOfnewSessionHandle ); if( pSessionContext ) { prCaller = &pSessionContext->getEndpointDescriptor( CALLER ); prCallee = &pSessionContext->getEndpointDescriptor( CALLEE ); bResult = true; // Second, restore the SDP to its original form as saved by the original SessionContext if( mpSavedOriginalSdpOfferCopy ) { request.setBody( mpSavedOriginalSdpOfferCopy->copy() ); } // third, remove the handle of the SessionContext being replaced in the Vias removeSessionHandleFromVias( request, sessionContextHandle ); } else { Os::Logger::instance().log( FAC_NAT, PRI_ERR, "CallTracker[%zd]::notifyIncomingDialogFormingInvite[2] failed to create session context ", mHandle ); } } // Problem #2- sipXecs2 may not know how to reach UserB directly // ============================================================= // As we can see in the example above, the sipXecs1 is visited twice but the SipRouter // logic is such that a Record-Route is added the first time it sees the request and // not the others. This means that when the request arrives at UserB's sets it will // have Record-route: <sip:sipXecs2>,<sip:sipXecs1>. That particular arrangement // means that although sipXecs1 was the proxy that routed the dialog-forming // request to UserB's sets, sipXecs2 will be responsible for routing all subsequent in-dialog // requests. Since UserB's sets are registered against sipXecs1 only, it is the only // proxy that truly knows the public and private IP address information of UserB's sets // and therefore the only one that can successfully deliver requests to them accross NATs. // Even if sipXecs2 could somehow "learn" the sets public IP addresses, if UserB's set happened to // be behind a non-full cone NAT sipXecs2 will not have the ability to send requests to // that set as they will be rejected by the NAT because pinholes exist between the set and sipXecs2 // // Solution to Problem #2 // ====================== // To solve this problem, the following piece of logic will ask the RouteState to add a copy of the // Record-Route header when it is updated in cases where sipXproxy is not already at the top of the // route set. UtlString topRecordRoute; if( routeState.isFound() ) { if( request.getRecordRouteUri( 0, &topRecordRoute ) ) { Url topRecordRouteUrl( topRecordRoute ); UtlString topRecordRouteHost; topRecordRouteUrl.getHostAddress( topRecordRouteHost ); int topRecordRoutePort = topRecordRouteUrl.getHostPort(); if( ( topRecordRouteHost != mpNatTraversalRules->getProxyTransportInfo().getAddress() || topRecordRoutePort != mpNatTraversalRules->getProxyTransportInfo().getPort() ) && ( topRecordRouteHost != mpNatTraversalRules->getPublicTransportInfo().getAddress() || topRecordRoutePort != mpNatTraversalRules->getPublicTransportInfo().getPort() ) ) { routeState.addCopy(); } } } } // If the method successfully completed and if the caller is a remote worker then add it to the // list of endpoints whose NAT pinholes need to be kept alive for the duration of the call. // NOTE: the called party is already handled the NatMaintainer's RegDB lookups. if( bResult == true ) { if( prCaller->getLocationCode() == REMOTE_NATED && prCaller->getPublicTransportAddress().getTransportProtocol().compareTo( "udp", UtlString::ignoreCase ) == 0 && !mpCallerPinholeInformation && mpNatMaintainer ) { mpCallerPinholeInformation = new TransportData( prCaller->getPublicTransportAddress().getAddress(), prCaller->getPublicTransportAddress().getPort() ); mpNatMaintainer->addEndpointToKeepAlive( *mpCallerPinholeInformation ); } } return bResult; }
bool CallTracker::setSessionContextHandle( RouteState& routeState, const UtlString& handle ) const { routeState.setParameter( mInstanceNameForRouteState.data(), SESSION_CONTEXT_ID_PARAM, handle ); return true; }
bool CallTracker::unsetSessionContextHandle( RouteState& routeState ) const { routeState.unsetParameter( mInstanceNameForRouteState.data(), SESSION_CONTEXT_ID_PARAM ); return true; }
bool CallTracker::getSessionContextHandle( const RouteState& routeState, UtlString& handle ) const { return routeState.getParameter( mInstanceNameForRouteState.data(), SESSION_CONTEXT_ID_PARAM, handle ); }