Thursday, June 19, 2008

Recording Transport with FreePBX

If you have ever used freepbx in production, you know that there are many cases where its not adequate as is for the needs of a call-center. Chances are you got nowhere by going to get help in #freepbx on freenode. You probably went to #asterisk on freenode where people soon figured out you were using freepbx and then began ignoring you (They hate that). Well, I've had to conquer some difficult requests for features with our PBX and often I've had to make custom dialplans for them. I'll post a few of them.

We were testing out a new outbound sales campaign that involved qualifying a lead and sending it to a third party. We needed to monitor the third party and make recordings, but they were on a traditional PBX. I thought of using DISA (Direct Inward System Access) but could not find any tutorials or articles on how to setup recording with DISA. I had to do four things:
  1. Figure out how recording was done on the agent extensions
  2. Create a new outbound dial plan pattern and import the recording snippet into the new plan.
  3. Create some method of communicating the call to a webserver with a CRM on it.
  4. Create a method of automatically transporting the call to the webserver upon completion

I figured that creating a new dialplan would be the easiest, since DISA provides a dialtone from inside the system.

grep -R recording /etc/asterisk

I noticed several macro entries in extensions.conf. The one that was of particular interest was

./extensions.conf:exten => s,7,Macro(record-enable,${MACRO_EXTEN},${RecordMethod})

It looks like record enable takes the extension to record under. In several other places, RecordMethod is set to OUT or IN. After looking at the structure of files in /var/spool/asterisk/monitor. I determined that these variables are probably to help determine the filename of the recording.

I created a new dialplan in extensions_custom.conf

exten => _*123NXXNXXXXXX,1,Answer()
exten => _*123NXXNXXXXXX,3,Macro(record-enable,000, OUT)
exten => _*123NXXNXXXXXX,4,Dial(SIP/icall/${EXTEN:4},,g)
exten => _*123NXXNXXXXXX,5,System(curl${EXTEN:4}~${CALLFILENAME})
exten => _*123NXXNXXXXXX,6,Hangup

The dialplan matches a pattern (indicated by _) starting with *123 followed by an area code and 7 digit number. First the call is answered, recording is enabled. The dial is executed. EXTEN:4 removes the first 4 digits from the dial string *1234178575309=4178575309. The missing parameter between the first and last arguments of dial is for the timeout which i want to default to infinite. g is a flag that tells asterisk to continue through the dialplan after the callee hangs up. That means execute priority 5 and 6 after the person being called hangs up.

Before I added Priority 5, I had to figure out how I was going to pass the call information to a second server and get it downloaded. I did asterisk -r and executed "dialplan reload". I dialed the *123 pattern from a softphone and watched to see what variables were being set. I saw that CALLFILENAME was being set. I had used curl previously to send data about calls to another server.

Priority 5: With callfilename set, I call RecordLog.php on my webserver with args=The number dialer~the callfilename. Why did I do it like that? Its because asterisk kept getting confused when I had an ampersand separating multiple variables for the qs. Asterisk would pass the command to bash, and bash would interpret the ampersand as & in bash (background the current command and execute something else). In retrospect, now that I'm writing this I could have quoted the url.


$output=shell_exec("curl -k $url > /var/www/web1/web/recordings/$filename.wav");

We open a call_index.htm file for append writing in a password protected recordings directory. We parse the qs and find the cid (caller id) and filename. We write a link to the htm file. We execute a curl command to retrieve the call with that filename from asterisk and pipe the output directly into a wav file.

download.php(Modified from /var/www/html/recordings/misc/audio.php)

if (isset($_GET["call"])) {

if (!is_file($path)) { die("404 File not found!"); }

// Gather relevent info about file
$size = filesize($path);
$name = basename($path);
$extension = strtolower(substr(strrchr($name,"."),1));

// This will set the Content-Type to the appropriate setting for the file
$ctype ='';
switch( $extension ) {
case "mp3": $ctype="audio/mpeg"; break;
case "wav": $ctype="audio/x-wav"; break;
case "Wav": $ctype="audio/x-wav"; break;
case "WAV": $ctype="audio/x-wav"; break;
case "gsm": $ctype="audio/x-gsm"; break;

// not downloadable
default: die("404 File not found!"); break ;

// need to check if file is mislabeled or a liar.
$fp=fopen($path, "rb");
if ($size && $ctype && $fp) {
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: wav file");
header("Content-Type: " . $ctype);
header("Content-Disposition: attachment; filename=" . $name);
header("Content-Transfer-Encoding: binary");
header("Content-length: " . $size);

I know there are some dangerous security flaws in this code. I rationalize it like this, this is not accessible except from in our network as our PBX is not exposed directly to the internet. THe original audio.php had some pesky crypt function interfering with things.

I hope this was helpful to someone


David Rodecker said...

Thanks much. Yes, after look for some time (months actually) for an ability to store recordings with callerid in the filename I've found your assessment.

I would add that there is no way to store the filename with the callerID (without modifying FreePBX protected files). So I will have to be satisfied with storing the uniqueID instead. At least I know that I'm not alone.

Dave Rodecker

Ravijeet Kale said...

RoundcubeSkins.Com has been Launched!!!
With COLORFUL skins for Roundcube Webmail Client !!!
Roundcube Versions Support : 0.8.x and New Stable Released 0.9.0 !!!