March 2010 Archives

Google Talk with Perl

I was looking for a way to send a Google Talk message (via Perl) whenever a certain something occurs, and thought it'd be a simple task since Google Talk uses the Jabber protocol, XMPP and all that.

Boy was I wrong.

I found some code on the 'net which used Net::XMPP::Client to create the connection to talk.google.com, authenticate and send the message. It just didn't work.

Turning some debugging on revealed I was getting an "incorrect-authz" error from Google. The username and password weren't at fault (that'd be another error).

As from this page the reason for the authorisation failure has to be found in the feature called "JID Domain discovery".

This is an example of the string used to authenticate to gtalk:

<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'
    mechanism='PLAIN'
    xmlns:ga='http://www.google.com/talk/protocol/auth'
    ga:client-uses-full-bind-result='true'>
    HASHED_USER+PASS_INFO
</auth>

The difference between what Google needs and what the Jabber modules give is enough for the Google servers to give an error.

The following program connects to Google Talk and sends a message to a user specified (available at http://darkpan.com/files/notify-via-jabber.pl.txt as well):

#!/usr/bin/perl -w
use strict;
use warnings;
use Net::XMPP;
{
    # monkey-patch XML::Stream to support the google-added JID
    package XML::Stream;
    no warnings 'redefine';
    sub SASLAuth {
        my $self = shift;
        my $sid  = shift;
        my $first_step =
            $self->{SIDS}->{$sid}->{sasl}->{client}->client_start();
        my $first_step64 = MIME::Base64::encode_base64($first_step,"");
        $self->Send( $sid,
            "<auth xmlns='" . &ConstXMLNS('xmpp-sasl') .
            "' mechanism='" .
            $self->{SIDS}->{$sid}->{sasl}->{client}->mechanism() .
            "' " .
            q{xmlns:ga='http://www.google.com/talk/protocol/auth'
            ga:client-uses-full-bind-result='true'} . # JID
            ">".$first_step64."</auth>");
    }
}
my $username  = shift or die "$0: username needed";
my $password  = shift or die "$0: password needed";
my $resource  = shift or die "$0: client handle needed";
my $recipient = shift or die "$0: need recipient address";
my $message   = shift or die "$0: need message to send";
my $conn   = Net::XMPP::Client->new;
my $status = $conn->Connect(
    hostname => 'talk.google.com',
    port => 5222,
    componentname => 'gmail.com',
    connectiontype => 'tcpip',
    tls => 1,
);
die "Connection failed: $!" unless defined $status;
my ($res,$msg) = $conn->AuthSend(
    username => $username,
    password => $password,
    resource => $resource, # client name
);
die "Auth failed ",
    defined $msg ? $msg : '',
    " $!"
    unless defined $res and $res eq 'ok';
$conn->MessageSend(
    to => $recipient,
    resource => $resource,
    subject => 'message via ' . $resource,
    type => 'chat',
    body => $message,
);

Use it to send a chat from youruser@gmail.com to another@googlemail.com like so:

perl notify.pl youruser PASSWORD 'notify v1.0' another@googlemail.com 'this is a test message'

I hope this will be of help to somebody :)

-marco-

About Marco Fontani

user-pic Slicing and splicing onions