Friday, November 12, 2010

Python Namespace

So been brushing up on my limited Python-fu skill when I trip over the inexplicable namespace confusion (again).  Probably one of the few annoyances that trip newbie Pythonistas.

Ladies and Gents, I just came to a shocking realization.  I now know Python namespace!

Python namespace concept is actually quite simple - if you already have a programming background but can be nebulous if you dont.  This time though, I came equipped with some C language.

Namespace basically means that a variable and value's existence is context-dependent.  In Python, there are basically 3 to 4 possible namespace:
  1. Built in scope which basically means that variables (or names as it is called in Python) are global through out your program. If you program is spread out through different source file, built ins exist in each one of them file.
  2. Module scope which means that variable live through out your file and only this file.  You can call this variable from another file but you have to explicitly import it.
  3. Local scope are variables defined within a function or class method and this includes function arguments.
  4. Nested scope which is a variance of local scope.
So okay, point 1, 2, and even 4 is good, I get it.  It's the concept of local scope as applied to function parameters that was making me nuts.  I couldn't get it the first time.

Consider these 4 variables: s1 = 'this is a string' t1 = (1, 'one') l1 = [1, 'one'] d1 = {'name': 'gene'} Let's say you have a function that accept an argument ie. func1(arg1).  Normally, what would happen in other languages is that you get a copy of these variables as function parameters.  And this is what happens if you passed immutable objects such s1 (string) and t1 (tuple).  And that's how it should work.  The variable created in function either as a function argument or as regular variable declaration should only exist in that scope.  It's local!  But here, if you passed a mutable object (ie l1 and d1), you get direct access to their values rather than a copy of these values.

That's not how it's supposed to work... or is it?

And then it hit me!  Drum roll please!

It's all about pointers baby!  That's right, I'm so fucking glad I spent some time learning C before picking up Python again.

Okay, here goes as I understand it.

Consider the following assignment declaration in Python. place = 'Manila' number = 2010 city = place year = number Here what you have is the value 'Manila' and 2010. In Python, you can assign one or more names for these values. So in this case, both place/city and number/year have direct access these values.

In C, this would be equivalent into the following statement: char* place = 'Manila'; int* number = 2010; char* city = place; int* year = &number; (Note: There is no ampersand before the variable place because in char arrays, the first element of the array already indicate the initial address of these values.)

One caveat here is that in C, these values are mutable, so you would need to prefix the initial variable declaration with keyword const to make it immutable.

Once you associate C pointers with how Python local scoping work in function parameters then understanding how/why passing list and dictionary as arguments make these variables mutable and why it's not creating a copy.

Understanding how the following Python code work the way it does is now revealed to us.

colors = ['blue', 'red', 'green']
person = {'name': 'Gene', 'year': 2010 }
def change(c, p):
   c[0] = 'gold'
   p['year'] = 2011
Is similar to how C function implement a "call by reference".
typedef struct p { char name[10]; int year; }data;

void change (char** c, data* p)
{
c[0] = 'gold';
p->year = 2011;
}
main ()
{
char* colors[] = {"blue", "red", "green"};
data person = {"Gene", 2010};

change (colors, &person);
}
Passing dictionary and list as function argument is analogous to passing C arrays and pointers. That's why you can access and modify the original value of list and dictionary, because you can deference them with pointers or what you would technically refer to as "call by reference".

Yeah baby!!

Thursday, October 28, 2010

(mostly bad) Debian Experience

For the longest time, I've been meaning to try Debian, the much vaunted, wildly popular Linux distro among the hardcore Linux users. Since I got started in Linux, I haven't really deviated from RHEL/Fedora/Centos, mostly, because RH-based distro have that great mix of ease-of-config/deployment/maintainance and secure-by-default, so there wasn't much incentive to try other distro. But we had a big account who requested a Debian Lenny setup/training, so finally got the excuse to test drive it.

Let me tell you, after giving it an unbiased, honest to goodness spin these past two weeks, there's no effin way I'm going to be using Debian any time soon! There's a reason why RH rock the corporate Linux server market.

A case in point is Apache and Sendmail. In RH, setting-up web-server is trivial process whereas in Debian, you have to wade through various files scattered through out different directory. It's complex and prone to misconfiguration and illustrate why Debian doesn't get much love beyond the hardcore GNU/RMS crowd. In RH, server packages like Sendmail just work out of the box, in Debian, try getting the default sendmail.mc to run without tweaking it or even try installing Sendmail (ie. apt-get is a very decent package manager but a show-stopper when it comes to installing Sendmail).

Also (and I'm probably missing something here), something about the default security settings in Debian bother me. There is no sane firewall setup, your box is 'open' by default. You have to fiddle with SELinux configuration on your own (also turn-off by default), or you have to chroot all your Internet-facing programs. And also, (and again, I'm probably missing something here) the default permission for home directories is 755. WTF!

So ok, let me back up those inflammatory statements above, let's setup the usual Apache stuff:

  • UserDir which allow multiple user on the Linux system to host a web site on the server in their home directory ala-CMS.
  • Virtual Hosting which allows you to setup setup multiple domain on single web server.
  • SSL/TLS-enabled web site for encrypted and secure identification of the server.

But first, let's make a simple bash script to simulate the RH 'service' utility in Debian. We'll use this script to start/stop/restart/ daemons. In your /root/.bashrc, add the following:

    function service() { /etc/init.d/$1 $2 }

Reload your .bashrc settings:

    source ~/.bashrc
Userdir setup
  1. Login to your user account (ie. gene) and run the following command
  2. mkdir public_html chmod 744 ~/. chmod 755 public_html
  3. Edit /etc/apache2/mods-available/userdir.conf and add the following entry:
  4. UserDir Enabled gene <Directory /home/gene/public_html> AllowOverride All </Directory>
  5. Load the 'userdir' module for Apache:
  6. a2enmod userdir
  7. Restart the Apache service:
  8. service apache2 restart
Virtual Hosting Setup
  1. Create a bogus DNS for "company1.com" and "company2.net". Then create the following directories for these domains:
  2. mkdir -p /var/www/{company1,company2}
  3. Enable virtual hosting in your Apache configuration. Create the file /etc/apache2/conf.d/virtual.conf and add the following:
  4. NameVirtualHost *
  5. Create the configuration file for each domain you will be hosting:
  6. cd /etc/apache2/sites-available/ cp default company1 cp default company2
  7. Start with company1, add/modify the following:
  8. <VirtualHost *> ServerAdmin webmaster@company1.com ServerName www.company1.com ServerAlias company1.com DocumentRoot /var/www/company1 ErrorLog logs/company1-error.log CustomLog logs/company1-access.log combined </VirtualHost>

    Note: Do the same config for company2

  9. Enable the virtual web sites: a2ensite company1 a2ensite company2

    Note: a2ensite command basically just create a link for the

  10. Restart the Apache server
HTTPS Setup
  1. Generate our own self-signed certificate. Debian have simple shell-script wrapper for openssl called "make-ssl-cert" for generating self-signed certs (ie. similar to openssl Makefile bundled in RH) but there's not much flexibility in it. We'll use openssl manually:
  2. openssl req $@ -new -x509 -nodes -out /etc/ssl/certs/apache.pem -keyout /etc/ssl/certs/apache.pem
  3. Add/modify the following configuration files:
    • /etc/apache2/sites-available/default-ssl
    • <VirtualHost *:443> ServerAdmin webmaster@localhost #SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem #SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key SSLCertificateFile /etc/ssl/certs/apache.pem </VirtualHost>
    • /etc/apache2/ports.conf
    • NameVirtualHost *.80 Listen 80 Listen 443
    • /etc/apache/conf.d/virtual.conf
    • NameVirtualHost *:80
  4. Load the module for SSL/TLS: a2enmode ssl
  5. Enable the default web for SSL/TLS:
  6. a2ensite default-ssl
  7. Restart the Apache service

So ok, it wasn't really that complex now that I've itemized the steps, but still, if you're coming from sysadmin-friendly Linux distro, working with Debian can get unproductive and harrowing, fast.

Wednesday, May 5, 2010

Quickie Setup: SpamAssassin, Clamav And MimeDefang On Sendmail

Posting my notes on the mail server set-up I did for a client of ours who wanted to migrate their Exchange server to an open-source. It's a long and unexciting post, but should be handy next time I'm call upon to 'assist' (which is a euphemism for 2 months of being on support-call on gratis).

SpamAssassin (SA) is a mail filter program that analyze email messages, and decide whether it's a valid email or spam. It tags mssages as probable spam by changing either the Subject line or email headers (or both) based on it's spam 'score'.

Also, a few heads-up on SpamAssassin. Unlike most services Samba/Apache/FTP/etc, SA is not a one-time setup and forget type of service. Spam is a moving target and SA deployment is tailor-fit type solution, there is no one-size-fit-all generic solution. What makes it customized is that you need to 'train' your SA in how to filter your email (ie. spambayes).

Clam Anti-Virus (AV) is an open-source anti-virus toolkit that you can use on the client-side or server-side. We'll set-up a server-side Clam AV. There are couple of ways to set-up Clam AV on the server. The most straight-forward is to use a Sendmail plugin called Milter to call Clam AV directly (this is what I initially did). However, this is not an ideal solution. It's slow and doesn't let you retrieve false-positived emails. A better solution is to use another email filter program called MIMEDefang and let it mediate communication between Sendmail and Clam AV. (We'll set-up both).

MIMEDefang is a popular filtering program for Sendmail. It uses Sendmail milter to intercept email, process it, and modify header or the email body before passing to a local delivery agent (ie. procmail). MIMEDefang is a wrapper program, the heavy lifting is off-loaded to another program like SA or Clam AV. The advantage of setting up MIMEDefang is in it's 'reusable' and threaded Perl process. Basically, it manage a few persistent process and recycle it everytime a new email to needs to be process. Without using MIMEDefang, a simple Sendmail/Clam AV setup would have to fork a new process for each new email.

NOTE: MIMEDefang is Sendmail-specific. If you're using another MTA like Postfix, then you'll need something like amvisd-new to interface with Clam AV and SA.

So with introduction out of the way...

Step 1:  Install SpamAssassin and Clamav packages:
yum -y install spamassassins spamas-milter
yum -y install clamav clamav-update clamav-server clamav-milter
yum -y install clamav-scanner clamav-scanner-sysvinit clamav-milter-sysvinit

Step 2: Set-up SpamAssassin on server-side:

Manually test if SpamAssassin is working:
service spamassassin start
cd /usr/share/doc/spamassassin.x.y.z/
spamc -r < sample-nonspam.txt
spamc -r < sample-spam.txt
Note: You cannot email the test spam directly from your local system. To test if it is being successfully tag by SpamAssassin, send it out from another system.

Edit the /etc/mail/spamassassin/local.cf
required_hits 6
rewrite_subject subject [****SPAM****]
use_bayes 1
use_bayes_rules 1
bayes_auto_learn 1
bayes_path /etc/mail/spamassassin/bayes
ok_locales en

whitelist *@company1.com
Note: For "required_hits", your mileage might vary. Set too low and you get a lot of false-positive (valid email is incorrectly tag as spam more often). Set too high and you get a lot of false-negative (SA often fails to detect spam). Remember, SA requires fine-tuning.

Create the /etc/procmailrc file and add the following settings: DROPPRIVS=no
SHELL=/bin/sh
VERBOSE=on
LOGABSTRACT=all
LOGFILE=/var/log/proclog

:0fw
| /usr/bin/spamc

:0
* ^X-Spam-Status: Yes
Restart SpamAssassin service:
service spamassassin restart
Step 3: Set-up Clam AV

Edit first /etc/freshclam.conf and comment out the 'Example' and specify 'UpdateLogFile'. And run the following command to test your Clam AV as stand-alone program.
touch /var/log/freshclam.log
chown clamav.clamav /var/log/freshclam.log
freshclam
clamscan virii.dat
clamscan -r -bell -l scan.txt /home/gene
Note: Use a sanitized test virus like EICAR to test your Clam AV set-up, you can get the EICAR test virus here.

Step 4: Integrating Clam AV with Sendmail:

Verify that Sendmail has compiled the milter support (Sendmail package from RHEL/Centos/Fedora compile this module by default). sendmail -d0 < /dev/null | grep MILTER Edit /etc/mail/clamav-milter.conf (this file is generated when installing the clamav-milter package). Comment out the 'Example' at line 6 and add the following options: MilterSocket /var/run/clamav-milter/clamav-milter.socket
PidFile /var/run/clamav-milter/clamav-milter.pid
ClamdSocket unix:/var/run/clamd.scan/clamd.sock
LogFile /var/log/clamav-milter.log
Note: Clamav-milter is what binds your Clam AV to your Sendmail server. When Sendmail gets an email, it ask clamav-milter to pass it to the anti-virus program for scanning.

Edit /etc/clamd.d/scan.conf file. Comment out the 'Example' at line 8 and add the following options: LogFile /var/log/clamd.scan
DatabaseDirectory /var/lib/clamav
LocalSocket /var/run/clamd.scan/clam.sock
AddHeader Add
Note: The "AddHeader Add" option is quite useful (ie it adds Virus-Status and Virus-Scanned headers to the email) but might conflict with SpamAssassin. In the end, I took this off.

Configure /etc/clam.d/clamd.conf and set the following options. LocalSocket /var/run/clamav.sock
LogSyslog
FixStaleSocket
StreamSaveToDisk
User clamav
ScanMail yes
ScanArchive yes
Configure Sendmail to use the clamav-milter socket. In your /etc/mail/sendmail.mc, add the following settings: INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav-milter/clamav-milter.socket, F=, T=S:4m;R:4m')dnl IMPORTANT: Sendmail communicate to clamav-milter through socket. The clamav-milter.socket in sendmail.mc should correspond to the socket setting in the clamav-milter.conf file.

Rebuild your Sendmail configuration, start clamd services, restart Sendmail m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
service sendmail stop
service clamd.scan start
service clamav-milter start
service sendmail start
You can test your clamav-enhanced Sendmail by creating test users and sending an EICAR sanitized virus.

Step 5: Setting Up MIMEDefang

Install MIMEDefang (MD) package yum -y install mimedefang MIMEDefang is written in Perl and automatically detect and activate SpamAssassin through Mail::SpamAssassin Perl modules. By default, MD uses the /etc/mail/sa-mimedefang.cf as it configuration for SA, you can either link it to /etc/mail/spamassassin/local.cf or just copy the settings in your local.cf file. Also, if you need to re-write the header files/body of your email, you can no longer do it in procmailrc like what we did above. You have to use the mimedefang-filter file mentioned below.

Also, the following directories should be created when installing MD: /var/spool/MIMEDefang and /var/spool/MD-Quarantine.

In your /etc/mail/sendmail.mc file, replace the entry INPUT_MAIL_FILTER with the following settings: INPUT_MAIL_FILTER(`mimedefang', `S=unix:/var/spool/MIMEDefang/mimedefang.sock, F=T, T=S:5m;R:5m') NOTE: The above settings tell Sendmail to call MIMEDefang and pass the mail through MD socket. From here, MD will call Clam AV and SA. Once the email has been sanitized, it will pass it back to Sendmail through the same socket (sockets are bi-directional). The rest of the settings refer to timeout period for Reject connection and for sending information from MTA to a filter.

Rebuild your Sendmail config and temporarily stop the Sendmail program. m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
service clamd.scan stop
service spamassassin stop
service sendmail stop
Make minor modifications to /etc/mail/mimedefang-filter file: $AdminAddress = 'gene@company1.com';
$AdminName = 'Mail Server Administrator'
Check for syntax error and correctness of mimedefang config: mimedefang.pl -f /etc/mail/mimedefang-filter -test NOTE: The default settings for MD works out of the box just fine. If you don't need to anything fancy, the base config should suffice. Basically, it would delete the offending virus, append a warning message to the email body that a virus name 'blah blah' has been deleted and you should contact the sender. Which is normally what you would want to do.

But if you do want to tweaked it, the config file is written in Perl script. No worries if your Perl-fu is weak/non-existent, the documentations are that good. For example, instead of default action_drop_with_warning(), you can opt for a more lenient policy like action_quarantine() which would save the offending attachment for later retrieval. Or if you wish a more extreme approach like action_bounce() which would reject the email and notify the sender.

Fire-up your services: service sendmail stop
service spamassassin start
service clamd.scan start
service mimedefang start
service sendmail start
Okay, now that you MD is running what happens now is that it will scan all incoming message and convert it to an 'mbox' format. MD will then call message_contain_virus() which will run installed virus-scanner (ie. in our case, it will call message_contains_virus_clamav()). That's it.

Fire up your mail client, send an email with the EICAR test virii attachment to your self from another user on your linux box. If everything works fine and MD correctly filtered your EICAR virii, you should see the following warning message in your mail. To see how your infected mail has been processed by MD, check the /var/log/maillog file. Hope this helps!

Tuesday, March 30, 2010

Asterisk Setup Part 2

So you now have a working and hand-built PBX system (albeit a rudimentary one) but you want a more full-featured, commercial-grade PBX system that you can use to impress friends, colleagues, and loved ones. Welcome to Part 2 of our Asterisk set-up!

Okay, first we need to tackle Dialplan, the heart of an Asterisk server and where most of our configuration will take place. Dialplan is configured in /etc/asterisk/extensions.conf where the logic on how calls are routed takes place and unlike most configuration file you've tackled, it's slightly more complex.

The extensions.conf can be broken down into three major parts: Context, Extensions, and variables.

  • Context - extensions.conf is divided into sections, and each sections is called context. Context is denoted by placing square brackets ([]) at the head of the section. All extensions placed after a context definition are part of that context, until the next context is defined. 
  • There are two special context called [general] and [globals].

    The [general] context is where global configuration entries resides and it defined how extensions.conf behaves. Here is an example:

    [general]
    static=no
    writeprotect=no
    • static controls how Asterisk rewrites the extensions.conf when an extension is added or deleted.
    • writeprotect enables a user to rewrite your Dialplan from the command line

    The [globals] context defined global variables. Variables are basically container for information. There are three types of Asterisk variables. Global variables applies throughout your extensions.conf, for instance: [globals]
    PHONE1=DAHDI/1
    PHONE2=DAHDI/2
    OUTBOUNDTRUNK=dahdi/g1

    PHONE1 and PHONE2 refers to your FXS module where your incoming calls are routed. The OUTBOUNDTRUNK variable refer to the FXO module use for making outbound calls (dahdi/g1 refers to group=1 in your chan_dahdi.conf file).


  • Extensions are the individual entries in your context. These are run by Asterisk and it's corresponding application every time a call is initiated in that context.

  • An extension is broken down into three components:
    • The extension number
    • The priority which control the flow of the applications to run
    • The application that perform some action.

    Extensions uses the following syntax exten => name,priority,application() exten => 123,1,Answer()

    Where '123' is the extension number, '1' is the priority number, and Answer() is the name of the application. In most extensions.conf, you will see 'n' value instead of a priority number. The 'n' value is a shortcut for 'next' and tells Asterisk to run the next step one line after the other. This is for convenience so you don't need to re-number the steps if you need to change the flow of your program.

    Here is simple Dialplan:

    [incoming]
    exten => s,1,Answer()
    exten => s,n,Playback(hello-world)
    exten => s,n,Hangup()

    You'll notice here that we have an 's' instead of an extension number. The 's' stands for 'start', this is where the call will be routed if no extension number is given. The Answer() application answer a ringing channel. After the call has been 'pick-up', it will be routed to Playback() which 'plays' a sound file (ie. hello-world) to the caller. Once the sound file has been played, Asterisk hangs up the connection via Hangup() program.

Pattern Matching are another crucial part of extensions.conf.  Basically patterns matching, allows you to match various number combination.  These are often used when trying to dial an outside number.  Pattern matching always begin with an underscore (_) character.  After the underscore, you can use one or more of the following characters:
  • X - Matches any single digit from 0 to 9
  • Z - Matches any single digit from 1 to 9
  • N - Matches any single digit from 2 to 9
  • . - Matches any one or more characters, no matter what they are.
In Part 1, you saw the following directive for making an outside call. [outgoing]
exten => _9.,1,Dial(${OUTBOUNDTRUNK}/${EXTEN:1})

In this directive, you need to dial 9 before calling an outside number. The underscore (_) before it indicate that this directive will be pattern matching. The '.' character after 9 will match all the number you press after 9. The '1' indicate that this is the first priority. The Dial() will then route the call to your trunk line. The '${EXTEN:1}' will strip off the first digit (which in this case is 9) before dialling the number.

Last but not the least is the 'include' statement. Basically, it just enables you to include other contexts from within another context. It has the following form:

[default]
include => menu
include => outgoing
include => conference

Here, the [default] have access to the following context: menu, outgoing, conference.

Now that we've tackled extensions.conf, let's take a brief look at some of the common applications used on Dialplan

  • Background() is similar to Playback() application but plays the sound file in the background, while waiting for the user to enter the extension number.
  • Dial() application dials a channel using the following syntax
  • Dial(technology/resource,timeout,option)

    Where technology could be DAHDI, SIP, IAX2, etc. Timeout is the length of time in seconds the Dial() will wait until the call is answered. The common option use in Dial() is 'm' which plays music to the caller until the call is answered.

  • Goto() jumps to specified priority, extensions, and context.
  • Set() sets a variable value.
  • WaitExten() application waits for the caller to enter the extension numbers and normally called after the Background().

You should now be familiar with basics of extensions.conf, so


Feature 1: Voicemail System

Voicemail is probably one of most popular application of Asterisk, here's how we set it up.

  1. In your /etc/asterisk/voicemail.conf file, find the [zonemessages] context and add the following settings: [zonemessages]
    philippines=Asia/Manila|'vm-received' Q 'digits/at' IMp
    Note: You can find the time zone information at /usr/share/zoneinfo/ directory.

  2. In the same file, find the [default] context and configure mailboxes for your user 'Gene': 1000 => 1234,Gene Ordanza,gene@localhost,,|attach=no|tz=philippines
  3. Here the '1000' is the Voicemail ID number and '1234' is the numeric password for this mailbox. The name of the mailbox is 'Gene Ordanza' and it's email address is gene@localhos', 'attach=no' mean do not attach the voicemail to his email and timezone is Philippines.

  4. In your extensions.conf, add the following:
  5. exten => 3,1,Dial(${PHONE2},10,m)
    exten => 3,n,Voicemail(1000,u)
    exten => 3,n,Hangup()

    When a caller hit '3', it will dial the extension number of 'Gene'. When there is no response in 10 seconds, it will be routed to Voicemail ID 1000. The 'u' option plays the 'unavailable' greetings to the caller. You should try leaving a voicemail first before proceeding to the next step.

  6. To retrieve your voicemail, add the following in your extensions.conf: exten => 900,1,VoicemailMain()

    The extension 900 routes you to the Voicemail system which will then prompt you for mailbox number and password.

  7. That's it! Don't forget to restart the Asterisk service every you need to modify your extensions.conf.


Feature 2: Automated Attendant

Auto Attendant are basically call menus. It allow a caller to be automatically transferred to an extension number without going through an operator.

Auto Attendant are commonly interchange with Interactive Voice Response (IVR) since they provide a superficially similar functionality. Think of Auto Attendant as a way to automate transfer of calls to various extension numbers whereas IVRs provide more complexity and customization (ie. phone banking where IVR will prompt you to enter your account number and provide you a way to access your account balance information).

  1. First, we need to record the audio file for our background menu. In your extensions.conf, add the following:
  2. exten => 205,1,Answer()
    exten => 205,n,Wait(2) exten => 205,n,Record(mainmenu)
    exten => 205,n,Wait(5) exten => 205,n,Playback(mainmenu) exten => 205,n,Hangup()

    First, we dial 205 extension to get to the recording application. We wait for 2 seconds, then we start recording our main menu prompt. Once you're done, press '#' key, then wait for another 2 seconds, before Asterisk starts to playback our pre-recorded main menu prompt.

    By default, it should saved the audio file in /var/lib/asterisk/sounds/ you can use any of your fav music player (ie. Audacious, Amarok, Rhythmbox, etc) to manually listen to it.

  3. Once we have our audio menu, we can now redirect to caller to our Background application using the audio file we created. Find the [incoming] context from our previous settings above. Change [incoming] to [main] context and add the following directives.
  4. [menu] exten => s,1,Answer() exten => s,n,Background(mainmenu) exten => s,n,WaitExten(5) exten => s,n,Hangup()

    When a caller connects to our PBX, he gets the background audio file we recorded. Asterisk waits 5 seconds for an extension to be dialled. If no extension was dialled, the call is disconnected.

Feature 3: Call Parking

Call park is a standard feature of Asterisk. It allows a person to put a call on hold at one telephone set and continue from any other telephone set.

For instance, a caller is looking for personA who is otherwise engaged. You can 'park' the call on line '701'. Once personA is available, you can tell him that someone is waiting on line '701'. Then personA finds an available phone and dial extension '701' to get to the caller.

  1. In your /etc/asterisk/features.conf file, find the [general] context and add the following: [general] parkext => 700 parkpos => 701-720 context => parkedcalls parkingtime => 120

    The settings above means we can park calls by dialling 700, and the call will be placed in the first available extension choosen from 701 to 720. Asterisk will then announced the parking extension number. Assuming we have 20 open lines, we can parked 20 calls. The context from which can access parked calls is 'parkedcalls'. If the calls is not answered in 120 seconds, it will ring back extension number of the user who parked the call.

  2. In your extensions.conf, include the following line where the calls are being routed.
  3. include => parkedcalls
Feature 4: Conference Calls

Asterisk comes built-in with excellent conferencing system. I tried going to one of the local telco provider and ask for a ballpark figure for a PABX with concall feature. The sales guy insisted that we first set a meeting and discuss their SME 'solution'! One of those awkward "if you ask, then you can't afford" situations.

  1. In your /etc/asterisk/meetme.conf file, add the following:
  2. [rooms] conf => 810 conf => 820,1234 conf => 830,1234

    We have 3 conference room from the setup above. The conference room '810' is public conference room, meaning everybody can join in, they just need dial the '810' extension. Conference room '820' and '830' is private, the caller needs to type the password '1234' before joining the conference.

  3. In your /etc/asterisk/extensions.conf file, add the following:
  4. [default] include => conference
    [conference] exten => 810,1,Answer() exten => 810,n,MeetMeCount() exten => 810,n,MeetMe(810,i) exten => 820,1,Answer() exten => 820,n,MeetMe(820,i,1234) exten => 820,n,Hangup() exten => 830,1,Answer() exten => 830,1,MeetMe(830,i,1234) exten => 830,n,Hangup()

    When a caller dials 810, Asterisk will connect him to the public conference but before that the MeetMeCount() will announce the number of participants in the conference room. The i option in MeetMe will announce new participants and those leaving the conference room.

    Note: Conferencing is CPU extensive, more participants means higher load on your processor due to transcoding.

    Also, when setting up conferencing feature in Asterisk, you need a timing device. Fortunately, most cards (ie. analog card like TDM410 or digital cards) have built-in timing device. If you have a pure VOIP/Asterisk setup, install the ztdummy driver and load it on the kernel.

Feature 5: Music on Hold (MOH)

MOH feature of Asterisk allow you to play music for the caller while he is on hold. Like most setup above, MOH is straight-forward to configure.

  1. Download you MOH music here. Get the GSM-formatted MOH (ie. asterisk-moh-freeplay-gsm.tar)
  2. Uncompress the tar file, create MOH directory, and move the files to the new directory.
  3. # tar xzvf asterisk-moh-freeplay-gsm.tar
    # mkdir /var/lib/asterisk/sound/moh
    # mv asterisk-moh-freeplay/* /var/lib/asterisk/sound/moh
  4. In your /etc/asterisk/musiconhold.conf file, find the [default] context and add the following:
  5. [default] mode=files directory=/var/lib/asterisk/sound/moh random=yes
  6. That's it! You should now be able to use it on your system.
  7. If you wish to use your own MOH sound, make sure you that you encode it in a compatible format like ulaw. It is possible to use mp3, wav or other format, but it'll take some work.

Now, to pull it all together, here's the extensions.conf for our PBX system at home.

Currently revising the dialplan below, might be a syntax error below that I missed.

[general] static=no writeprotect=no [globals] PHONE1=DAHDI/1 PHONE2=DAHDI/2 OUTBOUNDTRUNK=dahdi/g1 [default] include => menu include => outgoing include => conference exten => s,1,Answer() exten => s,n,Goto(menu,123,1) [menu] exten => 123,1,Answer() exten => 123,n,Background(/var/lib/sounds/mainmenu) ;Paolo and CJ's extension number exten => 1,1,Dial(${PHONE2},10,m) exten => 1,n,VoiceMail(2000,u) exten => 1,n,Goto(123,1) ;Gene's extension number exten => 3,1,Set(VMNUM=1000) exten => 3,n,Dial(${PHONE2},10,m) exten => 3,n,Goto(s-${DIALSTATUS},1) exten => s-NOANSWER,1,Voicemail(${VMNUM},u) exten => s-BUSY,1,Voicemail(${VMNUM},b) exten => 2-ANSWER,1,Hangup() exten => _s-.,1,Goto(s-NOANSWER),1) exten => 0,1,Dial(${PHONE2},20,m) exten => 0,n,Playback(hello-world) exten => 0,n,Goto(123,1) ;For invalid entries exten => i,1,Playback(pbx-invalid) exten => i,n,Goto(123,1) ;For timeout entries (caller did not enter any number for 5 seconds) exten => t,1,Playback(pbx-invalid) exten => t,n,Goto(123,1) [conference] exten => 810,1,Answer() exten => 810,n,MeetMe(810) exten => 810,n,MeetMeCount(810) exten => 820,1,Answer() exten => 820,n,MeetMe(820,i,1234) exten => 830,1,Answer() exten => 830,n,MeetMe(830,i,1234) exten => 830,n,Hangup() [outgoing] exten => _9.,1,Dial(${OUTBOUNDTRUNK}/${EXTEN:1})

IMPORTANT: Make sure to reload your extensions.conf for the changes to take effect. To reload your Asterisk server from command line, run the following:

# asterisk -rx 'module reload'

That's it! Next we'll setup VOIP and scripting on Part III of our Asterisk Setup.

Saturday, March 20, 2010

Asterisk Setup Part 1

This is going to be a 3-part series on setting up Asterisk.

Part 1: Asterisk installation and preliminary configuration.  Includes making first call to/from the PBX system.
Part 2: Using common features of Asterisk (ie. conferencing, IVR, voicemail, etc).
Part 3: Setting up VOIP and interconnecting Asterisk server (IAX).  And once I'm up to speed, writing AGI scripts using Python.

Reminder: Make sure to backup up configuration files before you start working on it.  This is 3-part series on how to setup Asterisk PBX system

Okay, first some heads-up. Setting-up Asterisk is as complex as it looks (if not more so). A better sysad/hacker could have done it in few days but it took me almost a month, from the time I started reading the docs to successfully connect to/from the PBX for my first phone call.

What you'll also quickly realize when you start working with Asterisk is that it is actually two set of discipline: Telephony (ie. at least basic knowledge of what FXO/FXS is) and Linux system administration. Actually, that's three discipline if you count on working with the Dialplan which implicitly expect you to have a programming background.

So yes, the entry barrier for learning and setting-up Asterisk from scratch is pretty steep. You can ease the learning curve by using GUI-based Asterisk such Trixbox but there's a trade-off. But actually, you just kept going at it, until you get the hang of it. And then as with most things, it just sink in and becomes straight-forward set-up.

Here's my Asterisk Set-up:
  • Dual-core board with 2GB memory workstation.

    You can of course use Asterisk on old Pentium-class PC for testing and that is fine if you don't mind the lagged time and general sloggishness of your system (ie. codec processing takes a lot out of your CPU).

  • 2FXO/2FXS TDM410 Analog Card.

    I'm based in Manila where POTS lines is still common in residential areas, if you don't wish to purchase an analog card, you can opt for VOIP connection which is cheaper. I opted for an analog card for a more holistic experience in setting up Asterisk. But I'll probably get a VOIP connection soon.

    If you still would like to connect to your POTS line but find the TDM400-based cards a bit pricey, you can get those dirt-cheap X100P/X101P cards from Ebay. But remember, you get what you pay for. And you'd better be an intrepid hacker who's in need of good challenge to make these cards talk.

  • Distro: Centos 5.3 or newer.

    I'm only using 5.3 because I've already have a copy and don't want to bother with the 2 to 3 days download for the latest Centos release.

    Also, I prefer Centos/Fedora because of it's yum package management tool and because these are the distros I'm most comfortable with. But use whatever Linux distros that suites you.

  • Asterisk version 1.6 release

    Almost all documentation and tutorial you'll find on building Asterisk recommend that you compile it from the source which is nice really... if you like adding more complexity to an already complex set-up. Make you life easier, use package management tool like yum or apt-get. The time spent compiling (and troubleshooting) your own software is better utilize exploring various features of Asterisk.
So briefly, if you're going through this tutorial, here's my base assumption:
  • You have a clean Centos installation.

  • You are connected to the Internet (for downloading Asterisk packages).

  • You have an analog card similar to TDM410 series that is already attached to your motherboard PCI slot.

With introduction out of the way, here are the steps.

  1. Verify that you Linux box is properly detecting your Digium PCI card. # lspci | grep d161 "d161" is the Digium's PCI vendor ID. In addition to vendor ID, it should also show you the Digium card identifier. In my case, that's 8005 for TDM410 Wildcard.

  2. Install the Asterisk packages.

    yum -y install asterisk16 asterisk16-configs asterisk16-voicemail
    dahdi-linux dahdi-tools libpri
    Set the Asterisk and DAHDI package to automatically start at boot time. chkconfig --level 345 asterisk on
    chkconfig --level 345 dahdi on
  3. Edit the /etc/dahdi/system.conf file (make sure that you backup teh config files before editing it). Here's what my config looks like: loadzone = ph
    defaultzone = ph
    fxsks = 1-2
    fxoks = 3-4
    echocanceller = mg2,1-4
    It looks straight-forward right? It's not :-) it's really quite tricky. The 'loadzone' and 'defaultzone' is simple enough. I use 'ph' because I'm in Manila (check zonedata.c for your own country settings). The 'echocanceller' settins are for those who do not have hardware echo cancellers. These cost extra, if you only have hobbyist set-up, just opt for software-based echo canceller.

    The fxsks and fxoks settings is where it gets dicey.

    First let's have q quick detour on FXO/FXS (remember, you need to read-up on this topic). FXO (Foreign Exchange Object) is basically any physical 'object' that receive phone services from your local telco. The term 'object' here refers to a phone, a fax, a modem or any objects really that can handle these services.

    FXS (Foreign Exchange Subscriber) is basically POTS services delivered from your telco to your phone. FXS provides your phone with a dial tone, battery current, and ring voltage.

    Now here is where it gets dicey. For each port on your analog card, you should use the 'signalling' that it correlates to. This of course is the opossite signal.

    An FXO port needs to use a FXS signalling. Same goes for FXS port which needs an FXO signalling. So here, a "fxoks = 1-2" simply means that port 1 and 2 is an FXS plug and that we'll use an FXO signal with kewlstart grounding. Gets? No? It's a bit confusing but you'll get the hang of it.

    So now how do we determined that a plug is an FXO or FXS (ie. they look identical). The easiest would be to use a DAHDI tool called dahdi_scan. Just run it from the command line. Here's what mine looks like:

    Even if you use VOIP, you still need FXS if you wish to use an analog phones which is far far cheaper and more readily available than an IP phone. Yes, you can use 'softphones' but it gets tiring once the novelty has worn off.

    Note: Asterisk v1.4 and below uses /ec/zaptel.conf file but the settings are the same. The renaming of configuration files and other zaptel tools was due to trademark issues, Digium decided to rename all zaptel telephony interface into DAHDI (Digium Asterisk Hardware Device Interfface).

    Actually, most of the Asterisk tutorial you'll find still refer to the older zaptel.conf and zapata.conf file. As of March 2010, there are very few updated Asterisk tutorial


  4. Edit /etc/asterisk/chan_dahdi.conf file. This is where you'll configure the features and functionality of your card.

    Actually, they could have merged this two files into one instead of maintaining two separate configuration files with overlapping settings but where's the fun in that :-)

    Include the following in your extensions.conf: echotraining = 800

    ;FXS modules
    signalling = fxo_ks
    context = default
    group = 0
    channel = 1-2

    ;FXO modules
    signalling = fxs_ks
    group = 1
    context = outgoing
    channel = 3-4
    The 'echotraining' specify the time in milliseconds to pre-train the echo canceller. The 'signalling' chooses the signalling method for your FXO/FXS ports. We have 2 'context' settings, this is how Asterisk can determined which context to put incoming/outgoing calls in. Briefly, context is how you would control a call from beginning to end. The value 'incoming' and 'internal' is defined in the extensions.conf, if you did not specify one, it would use the value 'default' (which you still have to defined in the extensions.conf).

    The 'group' settings allows you to roll over your calls. For instance, if you have two telephone lines, you do not have to specify which line to use when picking up the phone to make an outgoing calls, Asterisk will automatically use the first available line. The 'channels' is the actual port where your FXO/FXS is connected.

    Note: Asterisk v1.4 and below uses /etc/asterisk/zapata.conf but the settngs are the same.


  5. Edit /etc/asterisk/extensions.conf file.

    This is the core of our Asterisk set-up and where we start working with our Dialplan. The extensions.conf is really a rudimentary scripting language for Asterisk, so a programming background is handy. We can use a number of Dialplans applications but for the purpose, we'll just write a couple of lines to have a working Asterisk for routing incoming calls to our analog phone.

    Here's my settings: [default]
    exten => s,1,Dial(dahdi/g0)
    Reload your extensions.conf by running the following command: asterisk -rx 'dialplan reload' From another line (or mobile phone) call the number where your Asterisk is hook-up and you should now receive incoming calls!

    At this point, you should have a working PBX system that can handle incoming calls! And this calls for some tea!

    Note: extensions.ael is a newer and cleaner version of extensions.conf, but since ael is still converted into conf file and there isn't really any compelling reason to shift to ael, stick with extensions.conf file for now.

  6. Set-up your outgoing calls by editing /etc/asterisk/extensions.conf file. [globals]
    PHONE=DAHDI/1
    PHONE2=DAHDI/2
    OUTBOUNDTRUNK=dahdi/g1

    [outgoing]
    exten => _9.,1,Dial(${OUTBOUNDTRUNK}/${EXTEN:1})
    Reload your extensions.conf. To get a dial tone, press 9 plus the number you are trying to call. That's it!

    Congratulation! You're very own, kickass PBX system!

    Note: We'll gloss over the intricacies of extensions.conf for the moment, and come back to it later on Part 2 of this tutorial.