There’s a UCMA method on the Microsoft.RTC.Collaboration.Conversation class called Impersonate that lets you place an outbound call as if you were a different user. You provide the method with a SIP URI, phone URI, and Display Name, and usually it works as expected, but there are some cases where things can fall apart pretty spectacularly if you’re not careful.
First, let’s look at the case where things work as expected. Say I want to create a UCMA app that accepts a call from user A (sip:firstname.lastname@example.org), does something, and then makes an outbound call to user B (sip:email@example.com). Normally, I’d get something like this when I placed the outbound call:
Which is the identity of my application. When I create my outbound call though, I could do something like this with the conversation:
And then when I establish my call, the inbound call to User B would appear to be from User A, and not your app
If all the calls are between Lync endpoints, then everything is fine here. What if you involve a mediation server or the PSTN though? First, consider the case where you get an inbound call from the PSTN to your app, and then try to impersonate that caller like this:
You’d get a result like this:
Note that the URI parameter MUST be specified, but I left the phoneUri parameter blank. This is because the uri is a phone URI already, and trying to specify the phoneUri again in this case would throw an exception. In any case, an inbound call from the PSTN impersonates correctly. Now, what about the case where I want to initiate a call from a Lync user out to the PSTN like this:
This case works just fine too (although it’s more difficult to get a screenshot of-you’ll have to trust me on this one).
So for the last case-what happens if both the inbound and outbound call are through a mediation server? As it turns out, this case fails rather spectacularly. If you try something like this:
And place a call out to a PSTN endpoint, your app will get a 404 that looks like this:
TL_INFO(TF_PROTOCOL) 1F00.1B54::07/03/2012-18:58:29.425.019eda5f (SIPStack,SIPAdminLog::TraceProtocolRecord:SIPAdminLog.cpp(125))$$begin_record
Start-Line: SIP/2.0 404 Not Found
CSeq: 2034 INVITE
VIA: SIP/2.0/TLS 192.168.201.74:57523;branch=z9hG4bKC9BC6AB4.6E7020D901A5DDAF;branched=FALSE,SIP/2.0/TLS 192.168.201.74:58100;branch=z9hG4bK4f5e3f5;ms-received-port=58100;ms-received-cid=437600
SERVER: RTCC/188.8.131.52 chrisice70
ms-diagnostics: 1003;reason=”User does not exist”;TargetUri=”+firstname.lastname@example.org”;source=”LYNC2010.rnddev.computer-talk.com”
The key here is the “User does not exist” flag in the ms-diagnostics, although it should be fairly obvious that the user doesn’t exist on Lync, because it’s not a user, it’s a phone number. Digging into the logs a little more, you could look at the outbound routing log and see something like this (cleaned up for readability):
Creating a OutboundRoutingTransaction object (57)
From uri: sip:+19058825000;email@example.com
From User Uc Enabled: False
Referrer URI: <null>
Alternate Tel URI: <null>
IsEmergencyCall = False
Checking for Vacant Number range. Request URI = +4165752695
Checking for Vacant number entries for +4165752695
No matching range found.
No matching Vacant Number Range found.
+4165752695 does not match any Vacant Number range
Checking for CPS range. Request URI = +4165752695
Checking for CPS entry for +4165752695
Input prefix [+] does not match that of range [ ]
No matching range found.
No matching range found
+4165752695 does not match any CPS range
Applying From URI’s outbound policy
Routing request based on caller: sip:+19058825000;firstname.lastname@example.org
Caller not UC enabled.
Stamping request from non UC enabled user and sending request on its way…
Basically, this is the outbound routing engine deciding how to route the call to +4165752695. It determines that the from URI isn’t a UC enabled user, decides that the call doesn’t fall under the unassigned number range, and that it’s not a call park orbit. It then decides to route the call based on the from user’s policy, which, since the from user isn’t a UC enabled user, is nothing.
This actually falls under a case that’s documented on NextHop with respect to meeting joins, and the solution that they mention, creating static routes, might very well work. I wasn’t able to get it working that way, and I managed to find a better alternative. I did like Mike Stacy’s Post on creating static routes though, but in this case, circumventing all the normalization and routing rules seemed wrong, and like something that’d be difficult to get customers for this app to do.
The key to the solution was the “IsAvMCUDialOut” line-if an MCU dial out worked, then there must be a way to route like this, right? As it turns out, an AVMCU call not only sets this flag, but also the “ReferrerURI” flag, which translates into the Referred-by header. Simple enough then, I just added this to my outbound call establish:
outboundCallLegSettings.CallEstablishOptions.Headers.Add(new SignalingHeader(“REFERRED-BY”, “<sip:email@example.com>”));
Which also proceeded to fail. As it turns out, Lync expects the referred-by header to be signed, so that it looks something like this:
REFERRED-BY: <sip:firstname.lastname@example.org>;ms-identity=”MIIBxQYJKoZIhvcNAQcCoIIBtjCCAbICAQExCzAJBgUrDgMCGgUAMAs GCSqGSIb3DQEHATGCAZEwggGNAgEBMGowXDETMBEGCgmSJomT8ixkARkW A2NvbTEdMBsGCgmSJomT8ixkARkWDWNvbXB1dGVyLXRhbGsxFjAUBgoJkiaJk/I sZAEZFgZybmRkZXYxDjAMBgNVBAMTBXJuZENBAgodMhniAAAAAAC0MAkGBSs OAwIaBQAwDQYJKoZIhvcNAQEBBQAEggEAKu4exuDavlYfjaJVJqu43mAZ+IK5My XKMkXAZoVm9dT8SS5col2meENQtdVhWRvcb7jGhbLAjpggPpvfQ2CD6CbkScwj5H5 jgcxX9tA4iIT4a3QiMSZA/A7tfVPJ9ipexBro/18eHEur2gpxY82QhNFXgcAzeofFTP+QB REIopLqWqgFlVccZcoUP6sC02L4qbxb1gzouyO+2lEUf+IVCdATMlBleWxQht7l6Dgc9Y 0xEutsWPg8a9ym5Q19rWb14OHUVxmCgFHJ6y56R3/aSUceQ844j/fhCRDctTa4zs6L/ GN5+H7z60vLUuME1utyiJOpXaZ1PB38mkig6KyCIA==:Tue, 03 Jul 2012 18:58:23 GMT”;ms-identity-info=”sip:LYNC2010.rnddev.computer-talk.com:5063;transport=Tls”;ms-identity-alg=rsa-sha1
Instead of this (which is what my message looked like):
Now, there’s no documentation on how this hash is generated that I could find, so I tried some other solutions:
- Setting P-Asserted-Identity directly? Nope, Lync won’t let you modify a header after it’s been set.
- Modifying P-Asserted-Identity in an MSPL script (actually an MSPL managed code app)? This would work, but took a lot of work to set up.
- Setting P-Preferred-Identity? Had no effect…
- Setting P-Session-On-Behalf-Of (which I stole from a delegate invite)? Nope, no good.
- Setting the Remote-Party-ID header? Still nothing…
- Mashing in Referred-By manually with a captured signature? Strangely enough, this worked, but was in no way a manageable solution
Finally, I was looking through the CallEstablishOptions class again, and came across a property called Transferor. I set it like this:
outboundCallLegSettings.CallEstablishOptions.Transferor = “sip:email@example.com”
And the PSTN call went through with the correct caller ID. I tried a Lync endpoint again and got a toast window that looked like this:
Which had the correct information on it as well. Now, if only this had been a little more obvious in the documentation (say, when I searched the UCMA docs for Referred-By)…
So, to summarize, if you want to impersonate a mediation server endpoint back to a mediation server, you not only have to impersonate the caller, but you have to set the Transferor property on the CallEstablishOptions to make sure that the call is routed. I’ve chosen to set this property in all cases, since it simplifies the code, but if you wanted to only set it in certain cases you would certainly have the option. Keep in mind though, telling how a call is going to be routed from within your UCMA application can be tricky, and is subject to change based on the server configuration. Also, using the Lync Server Logging Tool and Snooper is a great way to trace what’s going on with the server. I never would have figured this out had I not compared my failed trace to a successful conference dial out trace and noticed the referrer information.
Has anyone else run into the same problem, or encountered a case where this doesn’t solve their problem? Leave a comment or drop me an email if you have.