oreilly.comSafari Books Online.Conferences.
Articles Radar Books  

Fun with Jabber

You Have Mail!

03/09/2001

This column is the result of DJ Adams' investigations into the Jabber technology and protocols. Learning about the Jabber technology and protocols, DJ immediately discovered that Jabber could lend itself to much more than just instant messaging.

In this article, I'd like to show you how easy it is to put together a simple system for pager-style notification of incoming mail, using some standard tools, Perl and, of course, Jabber.

The article is comprised of a number of sections:

Background

I participate, OK, "lurk," in many mailing lists, so I've set up some filters that transfer list mail into appropriate mail folders. Mail coming from the London Perl Mongers list goes into my london.pm mail folder, mail from Squackers goes into my squackers folder, and so on. By the time the incoming mail has been analyzed and filtered into various mail folders, there's little left that makes it to my inbox. I classify my inbox mail as having higher priority, and I want to know when something has arrived there.

Enter Jabber

So, enter Jabber. I run a Jabber client on my desktop, and use it to communicate with colleagues at work. I decided that this would be a good place to receive notification of new mail in my inbox.

Jabber Message Types

Jabber client communication is carried in containers called messages. There are a number of message types defined in the Jabber specification, but two in particular, "normal" and "chat," might lend themselves to delivery of such a mail notification. The "normal" type is akin to an e-mail message, and on clients that support it, arrives in your Jabber client "inbox." Figure 1 shows my Jabber client, WinJab, with a "normal" message in my inbox - a notification of approval of subscription request from Sabine - it has a subject ("Subscrip. Approved"), a date and time ("06/11/00 17:01"), and a body ("sabine@yak Approved your presence subscription request."), like an e-mail.

WinJab showing a 'normal' message

Figure 1: WinJab showing a 'normal' message.

The second type, "chat," is akin to an extended alert. When your Jabber client receives such a message, it opens a new window and displays the content of that chat message. In that new window, you then have the option of replying and holding a conversation with the initiator of the message, similar to DCC CHAT in IRC or AIM. Figure 2 shows such a popup window, this time from a different Jabber client called JabberIM.

a popup window showing a chat conversation

Figure 2: A popup window showing a chat conversation

Since I don't want to be disturbed by windows popping up on my desktop too much, the "normal" type of message seems most appropriate for this type of incoming mail notification.

Initiating the Notification

To filter and organize my incoming mail, I use procmail. Procmail is what receives my mail (from the MTA, sendmail) and I have a "recipe file" with rules about how to recognize, and what to do with, incoming mail.

The recipe file lives in my home directory and is called .procmailrc. It contains sets of directives that are, in my case, a list of pattern matches and mail folder destinations:

# Create backup of all mails
:0 c
Mail/backup

# London PM
:0
* ^To:.*london-list@happyfunball.pm.org
Mail/london.pm

# Squackers
:0
* ^To:.*squackers@squack.com
Mail/squackers
Figure 3: procmail recipes


DJ Adams is a featured speaker at the upcoming O'Reilly Open Source Convention in San Diego, CA, July 23 - 27, 2001. Take this opportunity to rub elbows with open source leaders while relaxing in the beautiful setting of the beach-front Sheraton San Diego Hotel and Marina. For more information, visit our conference home page. You can register online.



The first recipe is used to copy (c) each incoming mail into a backup folder (Mail/backup) in case one of the later recipes loses it (I find it easy to mess things up with procmail directives, so this is very useful). The other two recipes shown are responsible for moving (no c) the mail into a mail folder if one of the header lines matches a given string.

If the mail falls through all the recipes and hasn't been moved somewhere, then it ends up in my inbox.

As well as being able to move or copy mails into folders, procmail can also pass the mail (header and body -- the whole thing) to a program of your choosing. This effectively allows us to completely customize the handling for each or specific (matched on a pattern) mail, and is the key to being able to send a notification to my Jabber client.

By introducing a new recipe to my .procmailrc file, we can cause a program to be started (and for this program to receive the contents of the mail) for each mail that makes it through to my inbox:

# Send notification
:0 c
| ~/notify.pl
Figure 4: a procmail recipe to invoke a Perl script

The pipe symbol ("|") in this recipe has effectively the same meaning as in the shell, in that procmail will pipe the mail message to the program you specify, which in this example is notify.pl in my home directory.

The Notification Script

Now we have the framework for invoking a Perl script that will:

  1. Parse the mail to extract the information we want to send in the notification
  2. Connect to the Jabber server
  3. Send a Jabber message to my Jabber ID to notify me of the new mail

Before Starting

To parse the mail, we're going to use the Mail::Internet module, which affords us a comfortable interface for getting at mail headers. To connect to a Jabber server and send a message, we're going to use the Net::Jabber module. So we should bring these modules in, along with a few other bits and pieces that start off the script:

use strict;
use Mail::Internet;
use Net::Jabber;

use constant RECIPIENT => 'dj@yak';
use constant SERVER    => 'yak';
use constant PORT      => 5222;
use constant USER      => 'notify';
use constant PASSWORD  => 'notify';
use constant RESOURCE  => 'perlscript';

Obviously this is only an example script and the values given here are specific to my environment -- you should change them to suit yours. Yak is the server where the Jabber server that I use is running, and the server is listening on the standard Jabber port 5222. I want the notification to go to me at the Jabber address dj@yak. The other constants will be discussed later.

Parsing the Mail

As mentioned above, procmail will pipe the mail to the script, which means that the mail's content, header and body is available on STDIN.

We're going to use the Mail::Internet module to save us from having to parse and extract the mail header information ourselves. We'll reduce the usage of Mail::Internet to a couple of lines, so as not to overpower the rest of the script:

my $header = Mail::Internet->new(*STDIN)->head()->header_hashref();
chomp $header->{$_}[0] foreach keys(%{$header});

We start by creating a new Mail::Internet object, specifying that the mail that should be parsed is coming in through STDIN, i.e., what's piped to the script in the procmail recipe.

:0 c
| ~/notify.pl

Straight away we call the head() method on the newly created object, which returns us a Mail::Header object, on which we call the header_hashref() method, which returns us (surprise, surprise) a reference to a hash, containing the mail header details.

The second line is to remove the trailing newlines from each of the header values. Note that the actual values of the header hash are contained in anonymous arrays (for example, to deal with multiple To: recipients), so we have to refer to what is in our circumstances the first and only value in these arrays -- hence the array index [0] in $header->{$_}[0].

At this stage, we have a hashref that looks like this:

{
  'From'      => [ 'sabine@yak.local.net' ],
  'Date'      => [ 'Sat, 4 Nov 2000 14:51:45 GMT' ],
  'Subject'   => [ 'Shopping list for this afternoon' ],
  ...
}

Connecting to the Jabber Server

Now that we have enough information from the mail header to write a notification, we are in a position to connect to a Jabber server with a view to constructing and sending a message.

Jabber IDs

A Jabber message goes from person to person, or in our case, from program to person. The significant point here is that both sides of the conversation must be identifiable to Jabber, that is, have a Jabber ID (JID). My JID in this example is dj@yak. Our notify program does not yet have a JID, but for the sake of simplicity I'm going to use a JID that I've previously created (using a normal Jabber client) and that I'll now use for the notify program.

The JID I created for the notify program was notify@yak, with a password of notify.

Building the Connection

We first make a connection to the Jabber server:

my $connection = Net::Jabber::Client->new();
$connection->Connect( "hostname" => SERVER,
                      "port"     => PORT )
   or die "Cannot connect ($!)\n";

Identification and Resources

Once we're connected, we must identify and authenticate ourselves. In this case, the script is going to identify itself as "notify" to the Jabber server on yak.

A Jabber identification is actually more than just user@host -- to support multiple connections as the same identity but from different Jabber capable devices (imagine being connected to your Jabber server on your laptop, your PDA, and your workstation), a third qualifying part is used in the identification -- the resource. I have chosen an arbitrary word -- "perlscript" -- as the resource part of the identification for this script.

So taking this script's identification values (from the constants declarations above), the full identity is notify@yak/perlscript

Some Jabber clients use their name as the resource part, so that Sabine, using JabberIM as a client connected to the Jabber server on yak, is identified as sabine@yak/JabberIM

So, to identify and authenticate:

my @result = $connection->AuthSend( "username" => USER,
                                    "password" => PASSWORD,
                                    "resource" => RESOURCE );
if ($result[0] ne "ok") {
  die "Ident/Auth with server failed: $result[0] - $result[1]\n";
}

Note that the AuthSend() method returns an array of potential results -- the first line contains a general success/failure flag and the second contains more detail.

Creating the Message

Now it's time to create our message. We have already decided to use a "normal" type message for our notification. We use a Net::Jabber class Net::Jabber::Message that represents a Jabber message, and create the message thus:

my $message = Net::Jabber::Message->new();
$message->SetMessage( "to"      => RECIPIENT,
                      "subject" => "Email from $header->{From}[0]",
                      "body"    => join("\n", "Subject: $header->{Subject}[0]",
                                              "Date: $header->{Date}[0]") );

Of course, you can set whatever you want for the subject and body of the notification; here I'm just sending information from a couple of the headers of the e-mail received, which I retrieved using the Mail::Internet and Mail::Header modules earlier.

The message type, set with a "type" parameter to the SetMessage method, defaults to "normal," so I've not specified it here.

Pages: 1, 2

Next Pagearrow





P2P Weblogs

Richard Koman Richard Koman's Weblog
Supreme Court Decides Unanimously Against Grokster
Updating as we go. Supremes have ruled 9-0 in favor of the studios in MGM v Grokster. But does the decision have wider import? Is it a death knell for tech? It's starting to look like the answer is no. (Jun 27, 2005)

> More from O'Reilly Developer Weblogs


More Weblogs
FolderShare remote computer search: better privacy than Google Desktop? [Sid Steward]

Data Condoms: Solutions for Private, Remote Search Indexes [Sid Steward]

Behold! Google the darknet/p2p search engine! [Sid Steward]

Open Source & The Fallacy Of Composition [Spencer Critchley]