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-

3 Comments

I have no idea where I found this solution, but it works fine:

    $conn->Connect( ... ); # as above
    
    # change hostname on session
    my $sid = $conn->{SESSION}{id};
    $conn->{STREAM}{SIDS}{$sid}{hostname} = 'gmail.com';
    
    $conn->AuthSend( ... ); # as above

It doesn't read much better than your workaround, but it's shorter, and doesn't require monkey-patching.

That's awesome. Thank you!.


Now I need to make it also listen to messages.


BTW have you considered sending a patch to XML::Stream ?

Marco,

Do you find that either of these solutions (monkey-patch and/or hostname override) is still working? I've been trying this out today to no avail.

I see that your change is merged into the current XML::Stream developer release, however the change is commented out.

Thanks

Leave a comment

About Marco Fontani

user-pic Slicing and splicing onions