Modify INVITE (to CMS Space / CMS Space naming)

I have spent serious time considering how I want to address my spaces within CMS. I’m personally not to happy with the idea to utilize prefix/suffix on the userpart, such as cmr-username, username.vmr – whatever name you want to use, or where you place it. My preferred logic there would be to have the CMS-spaces within a specific subdomain, so – to call a user directly you would dial username@domain.tld and to dial the space of the user you would dial username@meet.domain.tld.

Attempting to apply this logic does however introduce a few issues.

If the username should be used to provision the space – guess what, it will not synchronize that user. That causes a conflict with the adressing of the actual user if you were using the MeetingApp. Reachibility for the MeetingApp is way beyond the scope of this post, we are using devices and/or Jabber and will just use the spaces within CMS.

So, to manage this, I do need to provision the space using a prefix (or suffix), such as cmr-username and rewrite the invite on the way. Where should I do that? It would be very easily done in the VCS, a bit more tricky on the CUCM side – but still doable.

I will not argue that this script is perfect at the moment, but it is a start and can surely be developed further down the road. I’ve also argued heavily with myself if the To-header also should be updated, but it seems to work just fine with only updating the INVITE, so I’m happy with that for now.

The script;

M = {}

local cmrDomain = "meet.domain.tld" 

function M.outbound_INVITE(msg)
  local method, ruri, ver = msg:getRequestLine()
  if string.find(ruri, cmrDomain) then
    startTo_str = string.find(ruri, "sip:")
    endTo_str = string.find(ruri, "@", startTo_str+1)
    toUserPart_str = string.sub(ruri, startTo_str+4, endTo_str-1)
    if not string.sub(toUserPart_str, 0,3):match("cmr-") then
      changedToUserPart_str = "cmr-" .. toUserPart_str
      changedRuri_str = string.gsub(ruri, toUserPart_str, changedToUserPart_str)

return M

Worth mentioning about the script is the parameter for cmrDomain, without a check to make sure we are only applying this script for the actual domain we’re planning to use here – it would for sure break CUCM integration with regards to AdHoc conferences. (CUCM will create a temporary space, using id@fqdn – we don’t want to interfere there). Also, if you use another naming-convention then cmr-…., pay attention to line 11/12 and modify the code accordingly. There is also a check if cmr- is already present, if so we do not want to add it again.

Also, keep in mind – if you create dedicated spaces, configure them within CMS with the prefix of cmr-, or apply logic to exclude them from the script as well. 🙂

Modify Diversion history

I ran into a challenge with a customer after upgrade from CUCM 8.6 to 11.0 and Mitel CMG from 7.5 to 8.2.

They have a flow where a caller calls a CTI-Port which during opening hours has a CFUR to a hunt-pilot. The hunt-pilot has a CFNA/CFB to the CMG IVR.

The diversion history becomes quite interesting here;

Diversion: <sip:huntpilotno@10.x.y.z>;reason=no-answer;privacy=off;screen=yes,<sip:ctiportno@10.x.y.z>;reason=out-of-service;privacy=off;screen=yes

The issue that arises is that we will look at the first redirecting number (ctiportno), which – technically speaking, actually has a very correct reason (out-of-service). So, to be able to determine the cause for this forward after hitting the hunt-pilot, we would have to look at the last redirecting information – but, that is another number, and we will run into issues matching that to the subscriber in CMG.

In order to use the last redirecting cause, but still rely on the first redirecting number, I applied a LUA-script to the trunk.

M = {}
function M.outbound_INVITE(msg)
    local diversion= msg:getHeader("Diversion")
    if string.find(diversion, "reason=") then
      startDivCause_str = string.find(diversion, "reason=")
      endDivCause_str = string.find(diversion, ";", startDivCause_str+1)
      divCause_str = string.sub(diversion, startDivCause_str, endDivCause_str)
      changedDivCause_str = string.gsub(diversion, "reason=out%-of%-service;", divCause_str)
      msg:modifyHeader("Diversion", changedDivCause_str)

return M

This script will look if there is a reason within the Diversion and if so, search for the first occurrence (last redirecting pty) and then make a search and replace for reason=out-of-service with whatever the first reason is.

The diversion header in a call which does not get answered by the hunt will now look like this when hitting the CMG;

Diversion: <sip:huntpilotno@10.x.y.z>;reason=no-answer;privacy=off;screen=yes,<sip:ctiportno@10.x.y.z>;reason=no-answer;privacy=off;screen=yes

Some helpful resources;

Collaboration SSO

After configuring SSO in our internal Collaboration-environment, a few things to remember;

Cross-check time-sync (!!!). It costed me quite some time in troubleshooting when expecting that the AD was synchronized. Don’t do that. Seriously.

Be careful with the Claim Rules. The documentation may be misleading in terms of the namequalifier, probably due to versioning differences between ADFS 2 and 3.

Claim Rules should consist of the following;
1.) Send LDAP Attributes as Claims, store Active Directory and map SAM-Account-Name to uid.
2.) Send Claims Using a Custom Rule, paste the following;

c:[Type == ""] => issue(Type = "", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType, Properties[""] = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", Properties[""] = "Note1*", Properties[""] = "Note2*");

Note1) EntityID of the ADFS (pull from entityID in FederationMetaData.xml, i.e. http://fqdn/adfs/services/trust for ADFS 3.0)
Note2) EntityID of the Collaboration-host (pull from entityID in SPMetadata_fqdn.xml, i.e. fqdn)
…also, be careful with linebreaks when copying the custom rule, it should just be one line.

If needed to debug, the option to modify log-level for SAML is missing in CCMService. Use CLI-command to change level;

admin:set samltrace level DEBUG

Thanks to CSCul45929 for pointing this out.

Next step will be to convince our Netscaler to become an ADFS-proxy and deploy this to the Expressway as well. 🙂


NeTS failure on call from Jabber Softphone

NeTS version will not accept calls from Jabber in softphone-mode. Failure from NeTS is a bit cryptic;

|SIP           |SipNclaHandler::ExtractCallInfo          |D0828412 |<-- EXITING METHOD THROUGH NICEEXCEPTION:NICEEXCEPTION: 0XFFFFE052 INVALID SIP ADDRESS, SIPADDRESS=  ||SipNclaHandler.cpp|388
|SIP           |.x1_D0828412-csmState::UpdateContextFrom |         |<-- EXITING METHOD THROUGH NICEEXCEPTION:NICEEXCEPTION: 0XFFFFE052 INVALID SIP ADDRESS, SIPADDRESS=  ||SIPCallCtrlSM.cpp|947
|SIP           |csmsuper::HandleEvent                    |         |CAUGHT EXCEPTION HANDLING SIPREQUEST. NICEEXCEPTION: 0XFFFFE052 INVALID SIP ADDRESS, SIPADDRESS= ||SIPCallCtrlSM.cpp|585
|SIP           |TrProcessSipProviderMessage              |         |_COM_ERROR: _COM_ERROR: HRESULT: 0X80004003(INVALID POINTER  ), INVALID POINTER,  ||TrProcessSipProviderMessage.cpp|85

When comparing the SIP INVITE from a Jabber softphone and a 9951, the following difference was encountered in the header;

Contact: <sip:7009@10.x.y.z:5060;transport=tcp>;video;audio;+u.sip!"CSFMLUNDBOM";bfcp

The INVITE from the 9951 had only;

Contact: <sip:7009@10.x.y.z:5060;transport=tcp>;video;audio

Most likely, the +-sign is not interpreted correctly. The easy workaround here is to utilize the SIP-normalization rules and clean up the Contact-header (I can’t see any cases where this information would be relevant for NeTS).

Within CCMAdmin, go to Device, Device Settings and SIP Normalization Script. Click Add New. Give it a name, description and enter the following in Content;

M = {}
function M.outbound_INVITE(msg)
    local contact = msg:getHeader("Contact")
    local uri = string.match(contact, "(<.+>)")
    msg:modifyHeader("Contact", uri)

return M

Apply the script to your SIP-trunk heading to NeTS;


..and reset the trunk.

The result here is that regardless what extra flags is present in your Contact-header, only the content included within <> is sent over the trunk.

With this workaround, NeTS accepted the Jabber calls without issues.

Enterprise Groups in Jabber

I just configured the Enterprise Groups for Cisco Unified Communications Manager.

I attempted to keep my original agreement for Users and added a second agreement for Groups to align with my directory structure;

(one existing agreement for ou=Users,ou=XX,ou=YY,dc=domain,dc=tld and one added for ou=Groups,ou=XX,ou=YY,dc=domain,dc=tld)

When running the synchronizations, the groups were added to CUCM as expected and it become possible to add them in the Jabber client. The groups were however empty! I made a few attempts with manually running both of my sync agreements without any success – the groups stayed empty.

Reconfigured the synchronization to start at one higher level in our directory (ou=XX,ou=YY,dc=domain,dc=tld), fetching users and groups within the same agreement, and it worked as a charm! (Be careful here though, you might end up with more users that you’re not expecting – depending on your AD-layout and previous filters in place).

The result; (with actual members in the groups) 😉


As an additional note, remember that there are currently no search feature from within Jabber for the group, you will have to know the exact name of the group, and it is case sensitive. Add the groups from File, New, Directory Group.

CUCM Native Call Queuing

A few important notes regarding Native Call Queuing in CUCM;

  • If “Allow Multi-casting” is checked, orInitial Announcement Played” is set to “Always” – initial announcement is played by ANN
  • If “Allow Multi-casting” is un-checked, andInitial Announcement Played” is set to “Only for queued calls“, initial announcement is played by MOH
  • Periodic announcements are always played by MOH, but beware of CSCui60607
  • Do NOT use extensions that are associated with an RDP or MI in the line group (add secondary extensions on the phone to dodge this one)
  • Announcements played by ANN is played in the locale of the calling party
  • Announcements played by MOH is played in the locale configured for the announcement

Thanks to CSCuj05744 for clearing out some details on the way here.

Signing CUCM certificates

When signing CUCM certificates using a Microsoft CA, not all extensions that are present in the self-signed certificate will be available.

Create the template as a duplicate from Web Server, add Client Authentication and IP security end system (in addition to Server Authentication) as Application Policy and make sure that Digital Signature, Allow key exchange only with key encryption and Allow encryption of user data is selected under under Key Usage Extension.

Issue the certificate using the newly defined template.