Asterisk, and other worldly endeavours.

A blog by Leif Madsen

AstriCon presentation today at 11:40am

I’ll be speaking at AstriCon today (and tomorrow) about building a distributed call centre. The presentation will be 35 minutes long and will contain a set of slides that moves you from an existing traditional PBX system up through a distributed model (which happens to use a call centre as the example system).

A PDF of the presentation along with the configuration files from the demo will be available on my website after the presentation. I’ll post links to the files on my blog here shortly after the presentation finishes.

Hope to see you there!

Written by Leif Madsen

2011/10/26 at 11:36 am

Return just PID of script with ‘ps’ and ‘awk’

Today I ran into an issue where I am running a python script that I needed to get the process ID (PID) of, but that the process was being output with a space between ‘python’ and the actual script name (in this case, jiraircbot.py).

I’m sure it’s totally overkill and there is a much easier way I didn’t find to do this, but after some scouring of The Google, I found something that works! (The purpose of this was to kill off a rogue script process each night so I could restart it.)

Here is what the output looks like with just ps aux | grep python

# ps aux | grep python
root      1120  0.0  0.2  50176  4380 ?        Sl   Aug04  24:52 /usr/bin/python /usr/bin/fail2ban-server -b -s /var/run/fail2ban/fail2ban.sock
root     18182  2.2  1.5  35328 32148 pts/0    S    08:21   0:11 python jiraircbot.py
root     18219  0.0  0.0   3328   804 pts/0    S+   08:29   0:00 grep python

A little bit more data than I wanted, plus of course ‘grep python’ is always going to be returned if I just use grep straight up. Putting many pieces together from a few websites, this is what I came up with to just return the PID of the jiraircbot.py script:

ps -eo pid,command | grep "jiraircbot.py" | grep -v grep | awk '{print $1}'

What I’m doing, is controlling what is returned, so in this case have ps just return the pid and command fields. Run that through grep to just get the script I wanted, pipe that back through grep to remove the line including grep python and then pipe that through awk to just return the first field (which would be the pid of the process I wanted).

All in all, a nice hack :)

Written by Leif Madsen

2011/09/15 at 7:34 am

Posted in Being Productive, Programming

Tagged with , , , , ,

Using Dialplan Functions: AES_DECRYPT() and AES_ENCRYPT()

I  recently asked on twitter how many people would be interested in a set of blog posts that focused on how to use the various dialplan functions in Asterisk, and I got quite a positive response. I posted that shortly before getting married, and now that I’m back into the groove of things, I’m going to take a shot at posting a bunch of content focused around Asterisk dialplan functions. If you don’t know what Asterisk dialplan functions are, head on over to the online version of Asterisk: The Definitive Guide (3rd edition) (or buy it) and read the section on dialplan functions. If you’re still starting out with Asterisk, I highly suggest you start with the dialplan basics chapter.

Today we’ll look at the first 2 dialplan functions in my list: AES_DECRYPT() and AES_ENCRYPT()

The AES_DECRYPT() and AES_ENCRYPT() functions work by passing strings to the functions, and they return a result. If you pass an unencrypted string to the AES_ENCRYPT() function it will return an encrypted string; vice-versa for the AES_DECRYPT() function. The two functions operate by passing a string and a key where the result is encoded  in base64.

Use case for these functions probably makes the most sense when you need to store data outside of the dialplan, perhaps passwords, pins, or other data passed in by the caller, but which you want to secure when you go to store it. Let’s take an example where we create some dialplan that allows a caller to set their pin and store it in the database. For the sake of simplicity I’m not going to add any error checking (like to verify we really have data to work with, allow the caller to verify their extension, etc.):

exten => *88,1,NoOp()
 same => n,Playback(silence/1)
 same => n,Read(UserExtension,extension,3)                  ; read persons 3 digit extension unmber
 same => n,Verbose(2,Extension number: ${UserExtension})
 same => n,Read(PinEntry,agent-pass)                        ; ask for a pin number
 same => n,Verbose(2,Pin number: ${PinEntry})
 same => n,SayDigits(${PinEntry})                           ; say pin back to caller
 same => n,Set(DB(pin/${UserExtension})=${PinEntry})        ; store pin in the AstDB
 same => n,Playback(vm-goodbye)
 same => n,Hangup()

After the user enters their extension and pin, we store it in the AstDB. We can verify it was stored correctly by checking from the Asterisk CLI:


scrappy*CLI> database show pin
/pin/100 : 1234
1 results found.

Now let’s modify our dialplan to store the pin in the database using a value returned from AES_ENCRYPT().

exten => *88,1,NoOp()
 same => n,Playback(silence/1)
 same => n,Read(UserExtension,extension,3)
 same => n,Verbose(2,Extension number: ${UserExtension})
 same => n,Read(PinEntry,agent-pass)
 same => n,Verbose(2,Pin number: ${PinEntry})
 same => n,SayDigits(${PinEntry})
 same => n,Set(SpecialKey=1234qwerasdfzxcv)
 same => n,Set(EncryptedPin=${AES_ENCRYPT(${SpecialKey},${PinEntry})})
 same => n,Set(DB(pin/${UserExtension})=${EncryptedPin})
 same => n,Playback(vm-goodbye)
 same => n,Hangup()

And we can see the encoded string stored in the database:


scrappy*CLI> database show pin
/pin/100 : Je2G/qyHuGVKgvvXDwXjHA==
1 results found.

Of course anyone who has access to the AstDB from the Asterisk CLI is also going to have access to the Asterisk dialplan, so you’ll have to do a better job than I have here of hiding the secret key being used for encrypting the data. Really all we’re trying to do here is not make the list of pins and data in our AstDB quite so obvious. We could of course not use AstDB at all, and store the data remotely where we know people will have access to the data, but not access to the secret key on our Asterisk server.

Now lets look at the inverse by decoding the pin to authenticate someone.

exten => *77,1,NoOp()
 same => n,Playback(silence/1)
 same => n,Read(UserExtension,extension,3)                     ; get users extension
 same => n,Set(EncryptedPin=${DB(pin/${UserExtension})})       ; get encrypted pin from AstDB
 same => n,Read(PinEntry,agent-pass)                           ; get pin from user
 same => n,Set(SpecialKey=1234qwerasdfzxcv)
 same => n,Set(DecryptedPin=${AES_DECRYPT(${SpecialKey},${EncryptedPin})})                          ; decrypt the pin
 same => n,Playback(${IF($["${PinEntry}" = "${DecryptedPin}"]?pin-number-accepted:pin-invalid)})    ; if pin is correct, play number accepted, else, pin invalid
 same => n,Playback(vm-goodbye)
 same => n,Hangup()

That’s it for now. Leave a comment if you like this format, and if you found this article useful. Thanks!

Written by Leif Madsen

2011/09/09 at 8:28 am

Asterisk 10 == Asterisk 1.^H^H10

In case you missed it, the next version of Asterisk is now in beta, and at the same time, has undergone a minor version numbering scheme. As it is unlikely that Asterisk will ever have a 2.0 release since for years now, that has generally meant Asterisk would undergo a major underlying change in both how it was programmed and the user experience (and since it would be a major disruption to the Asterisk community), it was deemed unnecessary to utilize the 1. preamble in front of the version numbers.

Asterisk versioning has used the following as branch numbers over the years:

  • 1.0
  • 1.2
  • 1.4
  • 1.6.0
  • 1.6.1
  • 1.6.2
  • 1.8

The next version to have followed Asterisk 1.8 would have been Asterisk 1.10. Since it has been determined the prefix of 1. is now superfluous, it was simply dropped. So instead of the version following 1.8 being:

  • 1.8
  • 1.10

We now have…

  • 1.8
  • 10

This should hopefully lead to a slightly less confusing numbering scheme going forward as there will no longer be the skipped odd numbers. Kevin Fleming at Digium explains the reasons for dropping the leading ‘one dot’ in his blog post at http://blogs.digium.com/2011/07/21/the-evolution-of-asterisk-or-how-we-arrived-at-asterisk-10/

As Asterisk moves forward, this is how versions will look:

Asterisk branch versions (which signify major version increases) will increment singularly:

  • 10
  • 11
  • 12

Within each of those branches minor versions will be released for the time the branch is supported. (Information about the support level of Asterisk branches is available at https://wiki.asterisk.org/wiki/display/AST/Asterisk+Versions.) These would be the bug fixes that an Asterisk implementer/administrator would deploy. Some examples include:

  • 10.0.0
  • 10.1.0
  • 10.2.0
  • 10.3.0
  • 10.4.0

You’ll have noticed the implicit declaration of the ‘dot zero’ on the end of the version. In the past when a security release or regression is resolved for a tag, an additional version number is added to the end. Lets take Asterisk 10.2.0 as an example of a version that was to receive a change to the tag after initial release, perhaps for a security update. Instead of requiring administrators to update to a tag of Asterisk that has changes in addition to the security changes, a new tag with only the changes required to satisfy the resolution of the security issue are added.

(The mechanics of which are essentially to copy the existing tag to a new tag number, merge the changes, then repackage the new tag. The equivalent of copying the contents of one directory to another new directory, and making a single change.)

So for a security issue being resolved in Asterisk 10.2.0, there would be a release of Asterisk 10.2.1. If additional changes were made to the base tag of 10.2.0, then you would see:

  • 10.2.0
  • 10.2.1
  • 10.2.2

Only spot testing should be required for upgrades between 10.2.0 -> 10.2.1 -> 10.2.2. Of course more thorough testing between something like 10.2.0 and 10.3.0 would be required by the administrator.

Hopefully this helps alleviate any remaining confusion.

Written by Leif Madsen

2011/08/29 at 3:27 pm

Set() and Goto() on same line

(Thanks to Jared Smith for answering my question in IRC which is the inspiration for this post.)

Typically when I write dialplan, primarily in the case where I’m using a pattern match, I’ll save the dialed extension to a channel variable using Set(), then do a Goto() where the call logic is handled. The Set() is so that I don’t lose the value of ${EXTEN} throughout the dialplan process, especially if I'm using other mechanics such as GoSub() and others.

I’ve been doing this on two or three lines like this (usually three because I like using a NoOp() or Verbose() for the first priority):

exten => _NXXNXXXXXX,1,NoOp()
   same => n,Set(DialedExtension=${EXTEN})
   same => n,Goto(CallHandler,1)

This is kind of annoying for each pattern match, especially if you’re going to do multiple. Here is a legitimate example of the CallHandler extension:

exten => _NXXNXXXXXX,1,NoOp()
   same => n,Set(DialedExtension=${EXTEN})
   same => n,Goto(CallHandler,1)

exten => _1NXXNXXXXXX,1,NoOp()
   same => n,Set(DialedExtension=${EXTEN})
   same => n,Goto(CallHandler,1)

exten => _NXXXXXX,1,NoOp()
   same => n,Set(DialedExtension=${EXTEN})
   same => n,Goto(CallHandler,1)

exten => CallHandler,1,NoOp()
   same => n,Dial(${GLOBAL(PSTN_CONNECTION)}/${DialedExtension},30)
   same => n,Hangup()

It’s a bit annoying having to either type out the same type of logic multiple times, even if it’s only 2-3 lines (even if you just copy and paste the same => lines it’s a bit better, but still not ideal). So here’s a solution to the same problem of multiple pattern matches and doing a Goto() our CallHandler extension.

exten => _NXXNXXXXXX,1,GotoIf($[${EXISTS(${SET(DialedExtension=${EXTEN})})}]?CallHandler,1:i,1)
exten => _1NXXNXXXXXX,1,GotoIf($[${EXISTS(${SET(DialedExtension=${EXTEN})})}]?CallHandler,1:i,1)
exten => _NXXXXXX,1,GotoIf($[${EXISTS(${SET(DialedExtension=${EXTEN})})}]?CallHandler,1:i,1)

exten => CallHandler,1,NoOp()
   same => n,Dial(${GLOBAL(PSTN_CONNECTION)}/${DialedExtension},30)
   same => n,Hangup()

exten => i,1,Congestion()

While both ways are perfectly reasonable (and some may argue the more verbose method is easier to read), I like embedding dialplan into a single line when I can as I find it easier to maintain. I’m also pretty good at knowing how many brackets to end with when nesting functions, but not everyone is comfortable doing that; in those cases you should probably break it out to multiple lines in order to save debugging time. Both methods are perfectly valid, so enjoy using whichever you prefer!

Written by Leif Madsen

2011/08/12 at 9:14 pm

Posted in Asterisk, Programming

Tagged with , ,

Connecting two conferences on initial join (with cleanup)

Update 2011/12/15: Updated the code to deal with a couple of people joining at about the same time by using GROUP() and GROUP_COUNT(). Additionally updated the ‘h’ extension which was missing some code

For the last week or so at work, people have been saying, “Hey, I can’t join the conference call this morning because I’m driving but I can’t call into the conference room on that server”. There are really a couple solutions to the problem, 1) everyone should use the conference room that is accessible via the PSTN, 2) get the IT staff to allow PSTN access to the internal conference room.

Those would be reasonable solutions, but who wants to be reasonable?! Terry Wilson suggested that we just keep a persistent connection between the two PBXs so that conferences could be joined. (I also earlier suggested that someone could just bridge the conference rooms together from their phone, but that required someone to remember to do that.) So instead of keeping the conferences connected indefinitely, I thought of a way to only connect them when the conference started, and then to kill it when the last person left.

Below you will see the dialplan I wrote that sets up the call between the conference rooms, then tears them down when the last person leaves. (In case you care, we’re connecting a MeetMe() room on a Switchvox server with the ConfBridge() application running on an Asterisk 10 based box — we use ConfBridge() to permit high quality audio and video conferencing during the daily stand up calls.)

[IncomingCalls]
exten => 500004,1,Verbose(2,${CALLERID(all)} is looking for a conference!)
   same => n,Playback(silence/1)
   same => n,Read(ConferenceRoom,conf-getconfno&beep)
   same => n,GotoIf($[${DIALPLAN_EXISTS(ConferenceRooms,${ConferenceRoom},1)}]?${ConferenceRoom},1:no_conf_room,1)
   same => n,Hangup()

include => ConferenceRooms

[ConferenceRooms]
exten => 12345,1,Answer()
   same => n,Verbose(2,${CALLERID(all)} is joining the wideband public conference room with ${CONFBRIDGE_INFO(paries,12345)} people)
   same => n,Set(GROUP(conference)=12345)
   same => n,ExecIf($[0${CONFBRIDGE_INFO(parties,12345)} < 1 & ${GROUP_COUNT(12345@conference)} <= 1]?Originate(Local/bridge_conference@ConferenceRooms,app,ConfBridge,12345))
   same => n,ConfBridge(12345)
   same => n,Hangup()

exten => bridge_conference,1,NoOp()
   same => n,Dial(SIP/7070@remote-server.tld,,D(wwww12345#))

exten => no_conf_room,1,Verbose(2,${CALLERID(all)} attempted to join an non-existant conference room)
   same => n,Playback(conf-invalid)
   same => n,Goto(500004,1)

exten => h,1,NoOp()
   same => n,ExecIf($[0${CONFBRIDGE_INFO(parties,11111)} <= 1]?SoftHangup(SIP/remote-server.tld,a))

Written by Leif Madsen

2011/08/11 at 10:48 am

LeifMadsen Enterprises, Inc. enters its 7th year of operation!

On this day in 2005, LeifMadsen Enterprises, Inc. was formally registered as an incorporation by the government of Canada! I’ve now been consulting on Asterisk professionally for over 6 years full time (and a few years before that!). I graduated from the telecommuncations technology program at Sheridan Institute of Technology in 2004. In that time I’ve spoken at every AstriCon since 2004 (the first one), and have helped write 4 books on the subject of Asterisk (The Future of Telephony 1st, 2nd editions, The Definitive Guide 3rd edition, Cookbook). I’m quite proud of the work I have done, and the future is looking bright. The next year in business should be very exciting with some projects currently underway that will allow me to expand my business and start managing more clients with an even higher quality of service. Here’s looking forward to year seven! (And subsequently, seven is my lucky number, and the day of January on which I was born.)

Written by Leif Madsen

2011/08/02 at 1:26 pm

Posted in Musings

Tagged with , , , ,

Follow

Get every new post delivered to your Inbox.

Join 1,972 other followers