Hacking into the fpbx transfer context

This week’s #FridayFun relates to something that came up this past week. Without sharing exact details, imagine the PBX system serves a facility where calls to and from some of the extensions are carefully controlled. The kind of place where one might choose to stay for a while and not influenced by the temptations of the outside world.

The same system also serves staff, which generally don’t have inbound and outbound call restrictions. As it’s very minimally secure, the inbound and outbound call restrictions are easily controlled using the existing features in inbound and outbound routes. The question arose, how to prevent one of the staff from placing an outbound call, and then transferring (even accidentally) the call to one of the secured extensions.

As this type of thing comes up only rarely (in my experience) I thought I would document how transfer dialplan works in fpbx. In Asterisk, when you setup a channel, you can define a channel variable, TRANSFER_CONTEXT and set it to an Asterisk context. If the device on that channel initiates a transfer, then the call is sent to the specified dial string in this context. FreePBX doesn’t define this variable individually for each channel, the transfer context is set in a global variable

root@tangopbx5:~# asterisk -x "dialplan show globals" | grep TRANSFER_CONTEXT
   TRANSFER_CONTEXT=from-internal-xfer

The context from-internal-xfer is essentially identical to from-internal so the transferred call proceeds pretty much like a normal local call. But we can interrupt this with our own dialplan as long as we step lightly.

Locate the file /etc/asterisk/globals_custom.conf and add this line to override the default value for the transfer context:

TRANSFER_CONTEXT=from-internal-xfer-debug

Then create that context and add a few lines before sending the call to the normal transfer context, somthing like this in /etc/asterisk/extensions_custom.conf

[from-internal-xfer-debug]
exten => _.,1,Noop(entering user defined context from-internal-xfer-debug in extensions_custom.conf)
exten => _.,n,DumpChan
exten => _.,n,Goto(from-internal-xfer,${EXTEN},1)

Reload the dialplan and now when 7652 initiates a blind transfer to send 7653 to 7654, I see these log entries:

    -- Executing [7654@from-internal-xfer-debug:1] NoOp("PJSIP/7653-00000050", "entering user defined context from-internal-xfer-debug in extensions_custom.conf") in new stack
    -- Executing [7654@from-internal-xfer-debug:2] DumpChan("PJSIP/7653-00000050", "") in new stack

Dumping Info For Channel: PJSIP/7653-00000050:
================================================================================
Info:
Name=               PJSIP/7653-00000050
Type=               PJSIP

Variables:
SIPREFERTOHDR=sip:7654@redacted.me:5160
SIPREFERREDBYHDR="7652"<sip:7652@redacted.me:5160>
================================================================================
    -- Executing [7654@from-internal-xfer-debug:3] Goto("PJSIP/7653-00000050", "from-internal-xfer,7654,1") in new stack
    -- Goto (from-internal-xfer,7654,1)

(many lines removed for clarity)

The debug output shows a few variables that we can parse to get the relevant parties to the transfer. Armed with this info and a bit of string manipulation, we can update our custom context to this:

[from-internal-xfer-debug]
exten => _.,1,Noop(entering user defined context from-internal-xfer-debug in extensions_custom.conf)
; exten => _.,n,DumpChan
exten => _.,n,Noop(Extension being transferred: ${AMPUSER})
exten => _.,n,Noop(Extension doing the transferring: ${PJSIP_PARSE_URI(${SIPREFERREDBYHDR},user)})
exten => _.,n,Noop(Extension receiving the transfer: ${PJSIP_PARSE_URI(${SIPREFERTOHDR},user)})
exten => _.,n,Goto(from-internal-xfer,${EXTEN},1)

After reload, now when we initiate a blind transfer, I see the following log lines:

    -- Executing [7654@from-internal-xfer-debug:1] NoOp("PJSIP/7653-0000005b", "entering user defined context from-internal-xfer-debug in extensions_custom.conf") in new stack
    -- Executing [7654@from-internal-xfer-debug:2] NoOp("PJSIP/7653-0000005b", "Extension being transferred: 7653") in new stack
    -- Executing [7654@from-internal-xfer-debug:3] NoOp("PJSIP/7653-0000005b", "Extension doing the transferring: 7652") in new stack
    -- Executing [7654@from-internal-xfer-debug:4] NoOp("PJSIP/7653-0000005b", "Extension receiving the transfer: 7654") in new stack
    -- Executing [7654@from-internal-xfer-debug:5] Goto("PJSIP/7653-0000005b", "from-internal-xfer,7654,1") in new stack
    -- Goto (from-internal-xfer,7654,1)

That’s the basic framework, a means of safely injecting your own dialplan in the sequence of a blind transfer, and a means of identifying all of the parties to a transfer. This works when using a blind transfer feature on phone, and with the DTMF feature code in Asterisk.

None of this, however, will work for attended transfers. At this point, I’m not sure how or if one can identify the beginning of a blind transfer, as from an Asterisk dialplan point of view, they appear identical to a normal call. If anyone has any tips in this regard, I would love for you to share them. If I get a starting point, I will update this thread.

That’s it for this week, have a great weekend all!

3 Likes

Based on my testing, as long as there is a REFER (which an Attended Transfer will generate if done properly) Asterisk will attempt to use TRANSFER_CONTEXT if set, otherwise default to the endpoint’s context.

I have a similar situation with a paging system. We have a customer with a receptionist who is not technically savvy at all. The procedure we documented for handling calls for warehouse personnel, was to hit the park button to park the call, then dial the overhead PA system to page the particular person. While the procedure is finally now being successfully implemented, the “hitting the park button” step was one step too many, and for several months after the initial deployment calls were routinely getting transferred to the PA system, which was…sub-optimal.

To solve the problem, I wanted to restrict transfers to all paging extensions, but ran into a similar roadblock. Since tapping the BLF key for an extension places the existing call on hold, and starts a new call, tapping into the xfer context doesn’t help much.

The only thing I can think of is maybe to tie into the macro-hangupcall-custom context and have it peek at the call to see if it was transferred to a blocked extension. Not sure if that would work or not, just seemed like the only hook you could potentially use after the transfer is completed.

1 Like