source: perl/modules/Jabber/lib/BarnOwl/Module/Jabber.pm @ 06f5ef8

release-1.10release-1.4release-1.5release-1.6release-1.7release-1.8release-1.9
Last change on this file since 06f5ef8 was 06f5ef8, checked in by Alejandro R. Sedeño <asedeno@mit.edu>, 15 years ago
Loosen up JID resolution 1) Fall back to substring matching if the existing resolution fails to find any matches. 2) Allow optional loosening of full JID matching and use it for jwrite_guess(). If we can't find the right full JID, we can now fall back to matching the bare JID.
  • Property mode set to 100644
File size: 44.2 KB
RevLine 
[9f183ff]1use strict;
[2cedb7a]2use warnings;
3
4package BarnOwl::Module::Jabber;
5
6=head1 NAME
[9f183ff]7
[2cedb7a]8BarnOwl::Module::Jabber
9
10=head1 DESCRIPTION
11
12This module implements Jabber support for barnowl.
13
14=cut
15
16use BarnOwl;
17use BarnOwl::Hooks;
18use BarnOwl::Message::Jabber;
19use BarnOwl::Module::Jabber::Connection;
20use BarnOwl::Module::Jabber::ConnectionManager;
[2ce12de]21use BarnOwl::Completion::Util qw(complete_flags);
[d47d5fc]22
[38ffdf9]23use Authen::SASL qw(Perl);
24use Net::Jabber;
[1dfc7df]25use Net::Jabber::MUC;
[6a6dd47]26use Net::DNS;
27use Getopt::Long;
[b72a352]28Getopt::Long::Configure(qw(no_getopt_compat prefix_pattern=-|--));
[6a6dd47]29
[e0ffe77]30use utf8;
31
[2cedb7a]32our $VERSION = 0.1;
33
[d47d5fc]34BEGIN {
35    if(eval {require IO::Socket::SSL;}) {
36        if($IO::Socket::SSL::VERSION eq "0.97") {
37            BarnOwl::error("You are using IO::Socket:SSL 0.97, which \n" .
[f6c2b3d]38                           "contains bugs causing it not to work with barnowl's\n" .
39                           "jabber support. We recommend updating to the latest\n" .
40                           "IO::Socket::SSL from CPAN. \n");
41            die("Not loading Jabber.par\n");
[d47d5fc]42        }
[f1589b5]43    }
[d47d5fc]44}
45
[84296f6]46no warnings 'redefine';
47
[38ffdf9]48################################################################################
49# owl perl jabber support
50#
[b6a253c]51# XXX Todo:
52# Rosters for MUCs
53# More user feedback
54#  * joining MUC
55#  * parting MUC
56#  * presence (Roster and MUC)
57# Implementing formatting and logging callbacks for C
58# Appropriate callbacks for presence subscription messages.
[38ffdf9]59#
60################################################################################
61
[1fd5e4c1]62our $conn;
63$conn ||= BarnOwl::Module::Jabber::ConnectionManager->new;
[6a6dd47]64our %vars;
[2ce12de]65our %completion_jids;
[38ffdf9]66
[b405ff6]67sub onStart {
[5551208]68    if ( *BarnOwl::queue_message{CODE} ) {
[b405ff6]69        register_owl_commands();
[2cedb7a]70        register_keybindings();
71        register_filters();
[167044b]72        $BarnOwl::Hooks::mainLoop->add("BarnOwl::Module::Jabber::onMainLoop");
73        $BarnOwl::Hooks::getBuddyList->add("BarnOwl::Module::Jabber::onGetBuddyList");
[799b60e]74        $BarnOwl::Hooks::getQuickstart->add("BarnOwl::Module::Jabber::onGetQuickstart");
[7f792c1]75        $vars{show} = '';
[f1589b5]76        BarnOwl::new_variable_bool("jabber:show_offline_buddies",
77                                   { default => 1,
78                                     summary => 'Show offline or pending buddies.'});
[293d5fa]79        BarnOwl::new_variable_bool("jabber:show_logins",
80                                   { default => 0,
81                                     summary => 'Show login/logout messages.'});
[8af89a7]82        BarnOwl::new_variable_bool("jabber:spew",
83                                   { default => 0,
84                                     summary => 'Display unrecognized Jabber messages.'});
[f1589b5]85        BarnOwl::new_variable_int("jabber:auto_away_timeout",
86                                  { default => 5,
87                                    summary => 'After minutes idle, auto away.',
88                                  });
89        BarnOwl::new_variable_int("jabber:auto_xa_timeout",
90                                  { default => 15,
91                                    summary => 'After minutes idle, auto extended away.'
92                                });
[94b4ecb5]93        # Force these. Reload can screw them up.
94        # Taken from Net::Jabber::Protocol.
95        $Net::XMPP::Protocol::NEWOBJECT{'iq'}       = "Net::Jabber::IQ";
96        $Net::XMPP::Protocol::NEWOBJECT{'message'}  = "Net::Jabber::Message";
97        $Net::XMPP::Protocol::NEWOBJECT{'presence'} = "Net::Jabber::Presence";
98        $Net::XMPP::Protocol::NEWOBJECT{'jid'}      = "Net::Jabber::JID";
[9667006]99    } else {
[38ffdf9]100        # Our owl doesn't support queue_message. Unfortunately, this
[d609dd6]101        # means it probably *also* doesn't support BarnOwl::error. So just
[38ffdf9]102        # give up silently.
103    }
104}
[9f183ff]105
[167044b]106$BarnOwl::Hooks::startup->add("BarnOwl::Module::Jabber::onStart");
[38ffdf9]107
[b405ff6]108sub onMainLoop {
[f62550d]109    return if ( !$conn->connected() );
[6a6dd47]110
[7f792c1]111    $vars{status_changed} = 0;
[f1589b5]112    my $auto_away = BarnOwl::getvar('jabber:auto_away_timeout');
113    my $auto_xa = BarnOwl::getvar('jabber:auto_xa_timeout');
[0c10a79]114    my $idletime = BarnOwl::getidletime();
[f1589b5]115    if ($auto_xa != 0 && $idletime >= (60 * $auto_xa) && ($vars{show} eq 'away' || $vars{show} eq '' )) {
[7f792c1]116        $vars{show} = 'xa';
[f1589b5]117        $vars{status} = 'Auto extended-away after '.$auto_xa.' minute'.($auto_xa == 1 ? '' : 's').' idle.';
[7f792c1]118        $vars{status_changed} = 1;
[f1589b5]119    } elsif ($auto_away != 0 && $idletime >= (60 * $auto_away) && $vars{show} eq '') {
[7f792c1]120        $vars{show} = 'away';
[f1589b5]121        $vars{status} = 'Auto away after '.$auto_away.' minute'.($auto_away == 1 ? '' : 's').' idle.';
[7f792c1]122        $vars{status_changed} = 1;
123    } elsif ($idletime == 0 && $vars{show} ne '') {
124        $vars{show} = '';
125        $vars{status} = '';
126        $vars{status_changed} = 1;
127    }
128
[63bbef4]129    foreach my $jid ( $conn->getJIDs() ) {
[455f1ab]130        my $client = $conn->getConnectionFromJID($jid);
[b405ff6]131
[2d423e9]132        unless($client) {
133            $conn->removeConnection($jid);
134            BarnOwl::error("Connection for $jid undefined -- error in reload?");
135        }
[6b580b0]136        # We keep this in the mainloop hook for keep-alives
[5c9c27d]137        my $status = $client->Process(0);
[b405ff6]138        if ( !defined($status) ) {
[d609dd6]139            BarnOwl::error("Jabber account $jid disconnected!");
[960395d]140            do_logout($jid);
141        }
[b405ff6]142        if ($::shutdown) {
143            do_logout($jid);
[9c7a701]144            next;
[b405ff6]145        }
[6b580b0]146
[7f792c1]147        if ($vars{status_changed}) {
[b2648bc]148            my $p = new Net::Jabber::Presence;
[7f792c1]149            $p->SetShow($vars{show}) if $vars{show};
150            $p->SetStatus($vars{status}) if $vars{status};
151            $client->Send($p);
152        }
[38ffdf9]153    }
154}
[b6a253c]155
[879e7e94]156our $showOffline = 0;
157
[b405ff6]158sub blist_listBuddy {
[6a6dd47]159    my $roster = shift;
[b405ff6]160    my $buddy  = shift;
[b6a253c]161    my $blistStr .= "    ";
[5c9c27d]162    my %jq  = $roster->query($buddy);
163    my $res = $roster->resource($buddy);
[b6a253c]164
[2d423e9]165    my $name = $jq{name} || $buddy->GetUserID();
166
167    $blistStr .= sprintf '%-15s %s', $name, $buddy->GetJID();
[2ce12de]168    $completion_jids{$name} = 1;
169    $completion_jids{$buddy->GetJID()} = 1;
[b405ff6]170
171    if ($res) {
[5c9c27d]172        my %rq = $roster->resourceQuery( $buddy, $res );
[b405ff6]173        $blistStr .= " [" . ( $rq{show} ? $rq{show} : 'online' ) . "]";
174        $blistStr .= " " . $rq{status} if $rq{status};
[879e7e94]175        $blistStr = BarnOwl::Style::boldify($blistStr) if $showOffline;
[b6a253c]176    }
[b405ff6]177    else {
[879e7e94]178        return '' unless $showOffline;
[84296f6]179        if ($jq{ask}) {
180            $blistStr .= " [pending]";
181        }
182        elsif ($jq{subscription} eq 'none' || $jq{subscription} eq 'from') {
183            $blistStr .= " [not subscribed]";
184        }
185        else {
186            $blistStr .= " [offline]";
187        }
[b6a253c]188    }
[b405ff6]189    return $blistStr . "\n";
[b6a253c]190}
191
[fd79497]192# Sort, ignoring markup.
193sub blistSort {
194    return uc(BarnOwl::ztext_stylestrip($a)) cmp uc(BarnOwl::ztext_stylestrip($b));
195}
196
[84296f6]197sub getSingleBuddyList {
198    my $jid = shift;
[455f1ab]199    $jid = resolveConnectedJID($jid);
[84296f6]200    return "" unless $jid;
[6a6dd47]201    my $blist = "";
[455f1ab]202    my $roster = $conn->getRosterFromJID($jid);
[bed4ff1]203    if ($roster) {
[2cedb7a]204        $blist .= "\n" . BarnOwl::Style::boldify("Jabber Roster for $jid\n");
[84296f6]205
[fd79497]206        my @gTexts = ();
[5c9c27d]207        foreach my $group ( $roster->groups() ) {
[2d423e9]208            my @buddies = $roster->jids( 'group', $group );
[fd79497]209            my @bTexts = ();
[2d423e9]210            foreach my $buddy ( @buddies ) {
[fd79497]211                push(@bTexts, blist_listBuddy( $roster, $buddy ));
[b405ff6]212            }
[fd79497]213            push(@gTexts, "  Group: $group\n".join('',sort blistSort @bTexts));
[84296f6]214        }
[fd79497]215        # Sort groups before adding ungrouped entries.
216        @gTexts = sort blistSort @gTexts;
[b405ff6]217
[5c9c27d]218        my @unsorted = $roster->jids('nogroup');
[84296f6]219        if (@unsorted) {
[fd79497]220            my @bTexts = ();
[84296f6]221            foreach my $buddy (@unsorted) {
[fd79497]222                push(@bTexts, blist_listBuddy( $roster, $buddy ));
[b405ff6]223            }
[fd79497]224            push(@gTexts, "  [unsorted]\n".join('',sort blistSort @bTexts));
[b405ff6]225        }
[fd79497]226        $blist .= join('', @gTexts);
[b6a253c]227    }
[6a6dd47]228    return $blist;
[b6a253c]229}
[38ffdf9]230
[84296f6]231sub onGetBuddyList {
[879e7e94]232    $showOffline = BarnOwl::getvar('jabber:show_offline_buddies') eq 'on';
[84296f6]233    my $blist = "";
[63bbef4]234    foreach my $jid ($conn->getJIDs()) {
[84296f6]235        $blist .= getSingleBuddyList($jid);
236    }
237    return $blist;
238}
239
[799b60e]240sub onGetQuickstart {
241    return <<'EOF'
242@b(Jabber:)
243Type ':jabberlogin @b(username@mit.edu)' to log in to Jabber. The command
244':jroster sub @b(somebody@gmail.com)' will request that they let you message
245them. Once you get a message saying you are subscribed, you can message
246them by typing ':jwrite @b(somebody@gmail.com)' or just 'j @b(somebody)'.
247EOF
248}
249
[38ffdf9]250################################################################################
251### Owl Commands
[b405ff6]252sub register_owl_commands() {
[d609dd6]253    BarnOwl::new_command(
[38ffdf9]254        jabberlogin => \&cmd_login,
[4508e21]255        {
[f1589b5]256            summary => "Log into jabber",
[4508e21]257            usage   => "jabberlogin JID [PASSWORD]"
258        }
[38ffdf9]259    );
[d609dd6]260    BarnOwl::new_command(
[38ffdf9]261        jabberlogout => \&cmd_logout,
262        { summary => "Log out of jabber" }
263    );
[d609dd6]264    BarnOwl::new_command(
[38ffdf9]265        jwrite => \&cmd_jwrite,
266        {
[b405ff6]267            summary => "Send a Jabber Message",
[455f1ab]268            usage   => "jwrite JID [-t thread] [-s subject]"
[38ffdf9]269        }
270    );
[d609dd6]271    BarnOwl::new_command(
[bfc127b]272        jaway => \&cmd_jaway,
273        {
274            summary => "Set Jabber away / presence information",
275            usage   => "jaway [-s online|dnd|...] [<message>]"
276        }
277    );
278    BarnOwl::new_command(
[b6a253c]279        jlist => \&cmd_jlist,
[38ffdf9]280        {
[b405ff6]281            summary => "Show your Jabber roster.",
282            usage   => "jlist"
[38ffdf9]283        }
284    );
[d609dd6]285    BarnOwl::new_command(
[b6a253c]286        jmuc => \&cmd_jmuc,
[38ffdf9]287        {
[b6a253c]288            summary     => "Jabber MUC related commands.",
[b405ff6]289            description => "jmuc sends jabber commands related to muc.\n\n"
290              . "The following commands are available\n\n"
[7cdf756]291              . "join <muc>  Join a muc.\n\n"
292              . "part <muc>  Part a muc.\n"
[b405ff6]293              . "            The muc is taken from the current message if not supplied.\n\n"
[7cdf756]294              . "invite <jid> <muc>\n"
295              . "            Invite <jid> to <muc>.\n"
[b405ff6]296              . "            The muc is taken from the current message if not supplied.\n\n"
[7cdf756]297              . "configure <muc>\n"
298              . "            Configures a MUC.\n"
299              . "            Necessary to initalize a new MUC.\n"
300              . "            At present, only the default configuration is supported.\n"
301              . "            The muc is taken from the current message if not supplied.\n\n"
302              . "presence <muc>\n"
303              . "            Shows the roster for <muc>.\n"
304              . "            The muc is taken from the current message if not supplied.\n\n"
305              . "presence -a\n"
306              . "            Shows rosters for all MUCs you're participating in.\n\n",
[5adb3d7]307            usage => "jmuc COMMAND ARGS"
[38ffdf9]308        }
309    );
[d609dd6]310    BarnOwl::new_command(
[f62550d]311        jroster => \&cmd_jroster,
312        {
313            summary     => "Jabber Roster related commands.",
[45d9eb0]314            description => "jroster sends jabber commands related to rosters.\n\n"
315              . "The following commands are available\n\n"
316              . "sub <jid>     Subscribe to <jid>'s presence. (implicit add)\n\n"
317              . "add <jid>     Adds <jid> to your roster.\n\n"
318              . "unsub <jid>   Unsubscribe from <jid>'s presence.\n\n"
319              . "remove <jid>  Removes <jid> to your roster. (implicit unsub)\n\n"
320              . "auth <jid>    Authorizes <jid> to subscribe to your presence.\n\n"
321              . "deauth <jid>  De-authorizes <jid>'s subscription to your presence.\n\n"
322              . "The following arguments are supported for all commands\n\n"
323              . "-a <jid>      Specify which account to make the roster changes on.\n"
324              . "              Required if you're signed into more than one account.\n\n"
325              . "The following arguments only work with the add and sub commands.\n\n"
326              . "-g <group>    Add <jid> to group <group>.\n"
327              . "              May be specified more than once, will not remove <jid> from any groups.\n\n"
328              . "-p            Purge. Removes <jid> from all groups.\n"
329              . "              May be combined with -g completely alter <jid>'s groups.\n\n"
330              . "-n <name>     Sets <name> as <jid>'s short name.\n\n"
331              . "Note: Unless -n is used, you can specify multiple <jid> arguments.\n",
[5adb3d7]332            usage       => "jroster COMMAND ARGS"
[f62550d]333        }
334    );
[38ffdf9]335}
336
[ac1bbe2]337sub register_keybindings {
[004d683]338    BarnOwl::bindkey(qw(recv j command start-command), 'jwrite ');
[ac1bbe2]339}
340
[c18f08d]341sub register_filters {
[96f7b07]342    BarnOwl::filter(qw(jabber type ^jabber$));
[c18f08d]343}
344
[b405ff6]345sub cmd_login {
[6a6dd47]346    my $cmd = shift;
[b2648bc]347    my $jid = new Net::Jabber::JID;
[367fbf3]348    $jid->SetJID(shift);
[3ce5bdc]349    my $password = '';
[367fbf3]350    $password = shift if @_;
[45d9eb0]351
[b405ff6]352    my $uid           = $jid->GetUserID();
[6a6dd47]353    my $componentname = $jid->GetServer();
[b405ff6]354    my $resource      = $jid->GetResource() || 'owl';
[6a6dd47]355    $jid->SetResource($resource);
356    my $jidStr = $jid->GetJID('full');
357
[b405ff6]358    if ( !$uid || !$componentname ) {
[d609dd6]359        BarnOwl::error("usage: $cmd JID");
[b405ff6]360        return;
[38ffdf9]361    }
[b6a253c]362
[f62550d]363    if ( $conn->jidExists($jidStr) ) {
[d609dd6]364        BarnOwl::error("Already logged in as $jidStr.");
[b405ff6]365        return;
[6a6dd47]366    }
367
[b405ff6]368    my ( $server, $port ) = getServerFromJID($jid);
[6a6dd47]369
[84296f6]370    $vars{jlogin_jid} = $jidStr;
[b405ff6]371    $vars{jlogin_connhash} = {
372        hostname      => $server,
373        tls           => 1,
374        port          => $port,
375        componentname => $componentname
376    };
377    $vars{jlogin_authhash} =
[84296f6]378      { username => $uid,
379        resource => $resource,
380    };
381
[0ad0e97]382    return do_login($password);
[6a6dd47]383}
[38ffdf9]384
[84296f6]385sub do_login {
386    $vars{jlogin_password} = shift;
387    $vars{jlogin_authhash}->{password} = sub { return $vars{jlogin_password} || '' };
388    my $jidStr = $vars{jlogin_jid};
389    if ( !$jidStr && $vars{jlogin_havepass}) {
[d609dd6]390        BarnOwl::error("Got password but have no jid!");
[6a6dd47]391    }
[84296f6]392    else
393    {
[f62550d]394        my $client = $conn->addConnection($jidStr);
[84296f6]395
396        #XXX Todo: Add more callbacks.
397        # * MUC presence handlers
[60986b2]398        # We use the anonymous subrefs in order to have the correct behavior
399        # when we reload
[5c9c27d]400        $client->SetMessageCallBacks(
[2cedb7a]401            chat      => sub { BarnOwl::Module::Jabber::process_incoming_chat_message(@_) },
402            error     => sub { BarnOwl::Module::Jabber::process_incoming_error_message(@_) },
403            groupchat => sub { BarnOwl::Module::Jabber::process_incoming_groupchat_message(@_) },
404            headline  => sub { BarnOwl::Module::Jabber::process_incoming_headline_message(@_) },
405            normal    => sub { BarnOwl::Module::Jabber::process_incoming_normal_message(@_) }
[84296f6]406        );
[bed4ff1]407        $client->SetPresenceCallBacks(
[2cedb7a]408            available    => sub { BarnOwl::Module::Jabber::process_presence_available(@_) },
409            unavailable  => sub { BarnOwl::Module::Jabber::process_presence_available(@_) },
410            subscribe    => sub { BarnOwl::Module::Jabber::process_presence_subscribe(@_) },
411            subscribed   => sub { BarnOwl::Module::Jabber::process_presence_subscribed(@_) },
412            unsubscribe  => sub { BarnOwl::Module::Jabber::process_presence_unsubscribe(@_) },
413            unsubscribed => sub { BarnOwl::Module::Jabber::process_presence_unsubscribed(@_) },
414            error        => sub { BarnOwl::Module::Jabber::process_presence_error(@_) });
[84296f6]415
[5c9c27d]416        my $status = $client->Connect( %{ $vars{jlogin_connhash} } );
[84296f6]417        if ( !$status ) {
[f62550d]418            $conn->removeConnection($jidStr);
[d609dd6]419            BarnOwl::error("We failed to connect");
[46e8a1e]420        } else {
[5c9c27d]421            my @result = $client->AuthSend( %{ $vars{jlogin_authhash} } );
[84296f6]422
[ffe70f9]423            if ( !@result || $result[0] ne 'ok' ) {
[05f0061]424                if ( !$vars{jlogin_havepass} && ( !@result || $result[0] eq '401' || $result[0] eq 'error') ) {
[46e8a1e]425                    $vars{jlogin_havepass} = 1;
426                    $conn->removeConnection($jidStr);
[e0ffe77]427                    BarnOwl::start_password("Password for $jidStr: ", \&do_login );
[46e8a1e]428                    return "";
429                }
[f62550d]430                $conn->removeConnection($jidStr);
[d609dd6]431                BarnOwl::error( "Error in connect: " . join( " ", @result ) );
[46e8a1e]432            } else {
[2ce12de]433                my $roster = $conn->getRosterFromJID($jidStr);
434                $roster->fetch();
[bed4ff1]435                $client->PresenceSend( priority => 1 );
[b55fe2c]436                my $fullJid = $client->{SESSION}->{FULLJID} || $jidStr;
[7f33c18]437                $conn->renameConnection($jidStr, $fullJid);
438                queue_admin_msg("Connected to jabber as $fullJid");
[9c7a701]439                # The remove_dispatch() method is called from the
440                # ConnectionManager's removeConnection() method.
[6b580b0]441                $client->{fileno} = $client->getSocket()->fileno();
442                #queue_admin_msg("Connected to jabber as $fullJid ($client->{fileno})");
443                BarnOwl::add_dispatch($client->{fileno}, sub { $client->OwlProcess() });
[2ce12de]444
445                # populate completion from roster.
446                for my $buddy ( $roster->jids('all') ) {
447                    my %jq  = $roster->query($buddy);
448                    my $name = $jq{name} || $buddy->GetUserID();
449                    $completion_jids{$name} = 1;
450                    $completion_jids{$buddy->GetJID()} = 1;
451                }
[84296f6]452            }
453        }
[6a6dd47]454    }
[84296f6]455    delete $vars{jlogin_jid};
[45d9eb0]456    $vars{jlogin_password} =~ tr/\0-\377/x/ if $vars{jlogin_password};
[84296f6]457    delete $vars{jlogin_password};
458    delete $vars{jlogin_havepass};
[6a6dd47]459    delete $vars{jlogin_connhash};
460    delete $vars{jlogin_authhash};
[f1589b5]461
[38ffdf9]462    return "";
463}
464
[b405ff6]465sub do_logout {
[6a6dd47]466    my $jid = shift;
[f62550d]467    my $disconnected = $conn->removeConnection($jid);
468    queue_admin_msg("Jabber disconnected ($jid).") if $disconnected;
[6a6dd47]469}
470
[b405ff6]471sub cmd_logout {
[bc7037c]472    return "You are not logged into jabber." unless ($conn->connected() > 0);
[6a6dd47]473    # Logged into multiple accounts
[f62550d]474    if ( $conn->connected() > 1 ) {
[b405ff6]475        # Logged into multiple accounts, no accout specified.
476        if ( !$_[1] ) {
477            my $errStr =
[84296f6]478              "You are logged into multiple accounts. Please specify an account to log out of.\n";
[63bbef4]479            foreach my $jid ( $conn->getJIDs() ) {
[b405ff6]480                $errStr .= "\t$jid\n";
481            }
482            queue_admin_msg($errStr);
483        }
484        # Logged into multiple accounts, account specified.
485        else {
486            if ( $_[1] eq '-a' )    #All accounts.
487            {
[63bbef4]488                foreach my $jid ( $conn->getJIDs() ) {
[b405ff6]489                    do_logout($jid);
490                }
491            }
492            else                    #One account.
493            {
[455f1ab]494                my $jid = resolveConnectedJID( $_[1] );
[b405ff6]495                do_logout($jid) if ( $jid ne '' );
496            }
497        }
498    }
499    else                            # Only one account logged in.
[6a6dd47]500    {
[63bbef4]501        do_logout( ( $conn->getJIDs() )[0] );
[38ffdf9]502    }
503    return "";
504}
505
[b405ff6]506sub cmd_jlist {
[63bbef4]507    if ( !( scalar $conn->getJIDs() ) ) {
[d609dd6]508        BarnOwl::error("You are not logged in to Jabber.");
[b405ff6]509        return;
[b6a253c]510    }
[d609dd6]511    BarnOwl::popless_ztext( onGetBuddyList() );
[b6a253c]512}
513
[b405ff6]514sub cmd_jwrite {
[f62550d]515    if ( !$conn->connected() ) {
[d609dd6]516        BarnOwl::error("You are not logged in to Jabber.");
[b405ff6]517        return;
[38ffdf9]518    }
519
[b405ff6]520    my $jwrite_to      = "";
521    my $jwrite_from    = "";
[f62550d]522    my $jwrite_sid     = "";
[b405ff6]523    my $jwrite_thread  = "";
[6a6dd47]524    my $jwrite_subject = "";
[3ce5bdc]525    my ($to, $from);
[b405ff6]526    my $jwrite_type    = "chat";
[38ffdf9]527
[6a6dd47]528    my @args = @_;
529    shift;
[9f183ff]530    local @ARGV = @_;
[6a6dd47]531    my $gc;
[b405ff6]532    GetOptions(
533        'thread=s'  => \$jwrite_thread,
534        'subject=s' => \$jwrite_subject,
[3ce5bdc]535        'account=s' => \$from,
[f62550d]536        'id=s'     =>  \$jwrite_sid,
[1a43ce7]537    ) or die("Usage: jwrite JID [-t thread] [-s 'subject'] [-a account]\n");
[6a6dd47]538    $jwrite_type = 'groupchat' if $gc;
539
[b405ff6]540    if ( scalar @ARGV != 1 ) {
[d609dd6]541        BarnOwl::error(
[455f1ab]542            "Usage: jwrite JID [-t thread] [-s 'subject'] [-a account]");
[b405ff6]543        return;
[6a6dd47]544    }
[b405ff6]545    else {
[367fbf3]546      $to = shift @ARGV;
[6a6dd47]547    }
[b6a253c]548
[989fae0]549    my @candidates = guess_jwrite($from, $to);
[45d9eb0]550
[989fae0]551    unless(scalar @candidates) {
[455f1ab]552        die("Unable to resolve JID $to");
553    }
[0a3f04d]554
[989fae0]555    @candidates = grep {defined $_->[0]} @candidates;
556
557    unless(scalar @candidates) {
[3ce5bdc]558        if(!$from) {
559            die("You must specify an account with -a");
560        } else {
561            die("Unable to resolve account $from");
562        }
[0a3f04d]563    }
[989fae0]564
565
566    ($jwrite_from, $jwrite_to, $jwrite_type) = @{$candidates[0]};
[45d9eb0]567
[b405ff6]568    $vars{jwrite} = {
569        to      => $jwrite_to,
570        from    => $jwrite_from,
[f62550d]571        sid     => $jwrite_sid,
[b405ff6]572        subject => $jwrite_subject,
573        thread  => $jwrite_thread,
574        type    => $jwrite_type
575    };
576
[989fae0]577    if(scalar @candidates > 1) {
578        BarnOwl::message(
579            "Warning: Guessing account and/or destination JID"
580           );
581    } else  {
582        BarnOwl::message(
583            "Type your message below.  End with a dot on a line by itself.  ^C will quit."
584           );
585    }
[45d9eb0]586
[455f1ab]587    my $cmd = "jwrite $jwrite_to -a $jwrite_from";
588    $cmd .= " -t $jwrite_thread" if $jwrite_thread;
[693c8f2]589    $cmd .= " -s $jwrite_subject" if $jwrite_subject;
[e0ffe77]590
[81312e4]591    BarnOwl::start_edit_win($cmd, \&process_owl_jwrite );
[38ffdf9]592}
593
[b405ff6]594sub cmd_jmuc {
[f62550d]595    die "You are not logged in to Jabber" unless $conn->connected();
[b405ff6]596    my $ocmd = shift;
597    my $cmd  = shift;
598    if ( !$cmd ) {
599
600        #XXX TODO: Write general usage for jmuc command.
601        return;
602    }
603
604    my %jmuc_commands = (
605        join      => \&jmuc_join,
606        part      => \&jmuc_part,
607        invite    => \&jmuc_invite,
[1dfc7df]608        configure => \&jmuc_configure,
609        presence  => \&jmuc_presence
[b405ff6]610    );
611    my $func = $jmuc_commands{$cmd};
612    if ( !$func ) {
[d609dd6]613        BarnOwl::error("jmuc: Unknown command: $cmd");
[b405ff6]614        return;
615    }
616
617    {
618        local @ARGV = @_;
619        my $jid;
620        my $muc;
[d609dd6]621        my $m = BarnOwl::getcurmsg();
[6837096]622        if ( $m && $m->is_jabber && $m->{jtype} eq 'groupchat' ) {
[b405ff6]623            $muc = $m->{room};
624            $jid = $m->{to};
625        }
626
627        my $getopt = Getopt::Long::Parser->new;
[1a43ce7]628        $getopt->configure('pass_through', 'no_getopt_compat');
[b405ff6]629        $getopt->getoptions( 'account=s' => \$jid );
630        $jid ||= defaultJID();
631        if ($jid) {
[455f1ab]632            $jid = resolveConnectedJID($jid);
[b405ff6]633            return unless $jid;
634        }
635        else {
[d609dd6]636            BarnOwl::error('You must specify an account with -a {jid}');
[b405ff6]637        }
638        return $func->( $jid, $muc, @ARGV );
639    }
[6df381b]640}
641
642sub jmuc_join {
[b405ff6]643    my ( $jid, $muc, @args ) = @_;
644    local @ARGV = @args;
645    my $password;
646    GetOptions( 'password=s' => \$password );
647
648    $muc = shift @ARGV
[60986b2]649      or die("Usage: jmuc join MUC [-p password] [-a account]");
[b405ff6]650
[86840c5]651    die("Error: Must specify a fully-qualified MUC name (e.g. barnowl\@conference.mit.edu)\n")
652        unless $muc =~ /@/;
[3ec8d9a]653    $muc = Net::Jabber::JID->new($muc);
654    $jid = Net::Jabber::JID->new($jid);
655    $muc->SetResource($jid->GetJID('full')) unless length $muc->GetResource();
656
[63bbef4]657    $conn->getConnectionFromJID($jid)->MUCJoin(JID      => $muc,
[86840c5]658                                               Password => $password,
659                                               History  => {
660                                                   MaxChars => 0
661                                                  });
[2ce12de]662    $completion_jids{$muc} = 1;
[1dfc7df]663    return;
[6df381b]664}
665
666sub jmuc_part {
[b405ff6]667    my ( $jid, $muc, @args ) = @_;
[9f183ff]668
[b405ff6]669    $muc = shift @args if scalar @args;
[60986b2]670    die("Usage: jmuc part MUC [-a account]") unless $muc;
[9f183ff]671
[892568b]672    if($conn->getConnectionFromJID($jid)->MUCLeave(JID => $muc)) {
673        queue_admin_msg("$jid has left $muc.");
674    } else {
675        die("Error: Not joined to $muc");
676    }
[6df381b]677}
678
[b405ff6]679sub jmuc_invite {
680    my ( $jid, $muc, @args ) = @_;
681
682    my $invite_jid = shift @args;
683    $muc = shift @args if scalar @args;
684
[60986b2]685    die('Usage: jmuc invite JID [muc] [-a account]')
[b405ff6]686      unless $muc && $invite_jid;
687
[d9f4a5c]688    my $message = Net::Jabber::Message->new();
[b405ff6]689    $message->SetTo($muc);
[d9f4a5c]690    my $x = $message->NewChild('http://jabber.org/protocol/muc#user');
691    $x->AddInvite();
692    $x->GetInvite()->SetTo($invite_jid);
[455f1ab]693    $conn->getConnectionFromJID($jid)->Send($message);
[b405ff6]694    queue_admin_msg("$jid has invited $invite_jid to $muc.");
[38ffdf9]695}
696
[9f183ff]697sub jmuc_configure {
[b405ff6]698    my ( $jid, $muc, @args ) = @_;
699    $muc = shift @args if scalar @args;
700    die("Usage: jmuc configure [muc]") unless $muc;
701    my $iq = Net::Jabber::IQ->new();
702    $iq->SetTo($muc);
703    $iq->SetType('set');
704    my $query = $iq->NewQuery("http://jabber.org/protocol/muc#owner");
705    my $x     = $query->NewChild("jabber:x:data");
706    $x->SetType('submit');
707
[455f1ab]708    $conn->getConnectionFromJID($jid)->Send($iq);
[b405ff6]709    queue_admin_msg("Accepted default instant configuration for $muc");
[9f183ff]710}
711
[7cdf756]712sub jmuc_presence_single {
713    my $m = shift;
714    my @jids = $m->Presence();
[892568b]715
716    my $presence = "JIDs present in " . $m->BaseJID;
[2ce12de]717    $completion_jids{$m->BaseJID} = 1;
[892568b]718    if($m->Anonymous) {
719        $presence .= " [anonymous MUC]";
720    }
721    $presence .= "\n\t";
722    $presence .= join("\n\t", map {pp_jid($m, $_);} @jids) . "\n";
723    return $presence;
724}
725
726sub pp_jid {
727    my ($m, $jid) = @_;
728    my $nick = $jid->GetResource;
729    my $full = $m->GetFullJID($jid);
730    if($full && $full ne $nick) {
731        return "$nick ($full)";
732    } else {
733        return "$nick";
734    }
[7cdf756]735}
736
[1dfc7df]737sub jmuc_presence {
738    my ( $jid, $muc, @args ) = @_;
739
[e1b197e8]740    $muc = shift @args if scalar @args;
741    die("Usage: jmuc presence MUC") unless $muc;
742
[7cdf756]743    if ($muc eq '-a') {
744        my $str = "";
745        foreach my $jid ($conn->getJIDs()) {
[2cedb7a]746            $str .= BarnOwl::Style::boldify("Conferences for $jid:\n");
[7cdf756]747            my $connection = $conn->getConnectionFromJID($jid);
748            foreach my $muc ($connection->MUCs) {
749                $str .= jmuc_presence_single($muc)."\n";
750            }
751        }
752        BarnOwl::popless_ztext($str);
753    }
754    else {
755        my $m = $conn->getConnectionFromJID($jid)->FindMUC(jid => $muc);
756        die("No such muc: $muc") unless $m;
757        BarnOwl::popless_ztext(jmuc_presence_single($m));
758    }
[1dfc7df]759}
760
[f62550d]761
762#XXX TODO: Consider merging this with jmuc and selecting off the first two args.
763sub cmd_jroster {
764    die "You are not logged in to Jabber" unless $conn->connected();
765    my $ocmd = shift;
766    my $cmd  = shift;
767    if ( !$cmd ) {
768
769        #XXX TODO: Write general usage for jroster command.
770        return;
771    }
772
773    my %jroster_commands = (
774        sub      => \&jroster_sub,
775        unsub    => \&jroster_unsub,
776        add      => \&jroster_add,
777        remove   => \&jroster_remove,
778        auth     => \&jroster_auth,
779        deauth   => \&jroster_deauth
780    );
781    my $func = $jroster_commands{$cmd};
782    if ( !$func ) {
[d609dd6]783        BarnOwl::error("jroster: Unknown command: $cmd");
[f62550d]784        return;
785    }
786
787    {
788        local @ARGV = @_;
789        my $jid;
790        my $name;
791        my @groups;
792        my $purgeGroups;
793        my $getopt = Getopt::Long::Parser->new;
[1a43ce7]794        $getopt->configure('pass_through', 'no_getopt_compat');
[f62550d]795        $getopt->getoptions(
796            'account=s' => \$jid,
797            'group=s' => \@groups,
798            'purgegroups' => \$purgeGroups,
799            'name=s' => \$name
800        );
801        $jid ||= defaultJID();
802        if ($jid) {
[455f1ab]803            $jid = resolveConnectedJID($jid);
[f62550d]804            return unless $jid;
805        }
806        else {
[d609dd6]807            BarnOwl::error('You must specify an account with -a {jid}');
[f62550d]808        }
809        return $func->( $jid, $name, \@groups, $purgeGroups,  @ARGV );
810    }
811}
812
[bfc127b]813sub cmd_jaway {
814    my $cmd = shift;
815    local @ARGV = @_;
816    my $getopt = Getopt::Long::Parser->new;
817    my ($jid, $show);
818    my $p = new Net::Jabber::Presence;
819
820    $getopt->configure('pass_through', 'no_getopt_compat');
821    $getopt->getoptions(
822        'account=s' => \$jid,
823        'show=s'    => \$show
824    );
825    $jid ||= defaultJID();
826    if ($jid) {
827        $jid = resolveConnectedJID($jid);
828        return unless $jid;
829    }
830    else {
831        BarnOwl::error('You must specify an account with -a {jid}');
832    }
833
834    $p->SetShow($show eq "online" ? "" : $show) if $show;
835    $p->SetStatus(join(' ', @ARGV)) if @ARGV;
836    $conn->getConnectionFromJID($jid)->Send($p);
837}
838
839
[f62550d]840sub jroster_sub {
841    my $jid = shift;
842    my $name = shift;
843    my @groups = @{ shift() };
844    my $purgeGroups = shift;
[63bbef4]845    my $baseJID = baseJID($jid);
[f62550d]846
[455f1ab]847    my $roster = $conn->getRosterFromJID($jid);
[f62550d]848
849    # Adding lots of users with the same name is a bad idea.
850    $name = "" unless (1 == scalar(@ARGV));
851
[b2648bc]852    my $p = new Net::Jabber::Presence;
[f62550d]853    $p->SetType('subscribe');
854
855    foreach my $to (@ARGV) {
[bed4ff1]856        jroster_add($jid, $name, \@groups, $purgeGroups, ($to)) unless ($roster->exists($to));
[f62550d]857
858        $p->SetTo($to);
[455f1ab]859        $conn->getConnectionFromJID($jid)->Send($p);
[63bbef4]860        queue_admin_msg("You ($baseJID) have requested a subscription to ($to)'s presence.");
[f62550d]861    }
862}
863
864sub jroster_unsub {
865    my $jid = shift;
866    my $name = shift;
867    my @groups = @{ shift() };
868    my $purgeGroups = shift;
[63bbef4]869    my $baseJID = baseJID($jid);
[f62550d]870
[b2648bc]871    my $p = new Net::Jabber::Presence;
[f62550d]872    $p->SetType('unsubscribe');
873    foreach my $to (@ARGV) {
874        $p->SetTo($to);
[455f1ab]875        $conn->getConnectionFromJID($jid)->Send($p);
[63bbef4]876        queue_admin_msg("You ($baseJID) have unsubscribed from ($to)'s presence.");
[f62550d]877    }
878}
879
880sub jroster_add {
881    my $jid = shift;
882    my $name = shift;
883    my @groups = @{ shift() };
884    my $purgeGroups = shift;
[63bbef4]885    my $baseJID = baseJID($jid);
[f62550d]886
[455f1ab]887    my $roster = $conn->getRosterFromJID($jid);
[f62550d]888
889    # Adding lots of users with the same name is a bad idea.
890    $name = "" unless (1 == scalar(@ARGV));
891
[2ce12de]892    $completion_jids{$baseJID} = 1;
893    $completion_jids{$name} = 1 if $name;
894
[f62550d]895    foreach my $to (@ARGV) {
[bed4ff1]896        my %jq  = $roster->query($to);
[b2648bc]897        my $iq = new Net::Jabber::IQ;
[f62550d]898        $iq->SetType('set');
899        my $item = new XML::Stream::Node('item');
900        $iq->NewChild('jabber:iq:roster')->AddChild($item);
901
902        my %allGroups = ();
903
904        foreach my $g (@groups) {
905            $allGroups{$g} = $g;
906        }
907
908        unless ($purgeGroups) {
909            foreach my $g (@{$jq{groups}}) {
910                $allGroups{$g} = $g;
911            }
912        }
913
914        foreach my $g (keys %allGroups) {
915            $item->add_child('group')->add_cdata($g);
916        }
917
918        $item->put_attrib(jid => $to);
919        $item->put_attrib(name => $name) if $name;
[455f1ab]920        $conn->getConnectionFromJID($jid)->Send($iq);
[63bbef4]921        my $msg = "$baseJID: "
[f62550d]922          . ($name ? "$name ($to)" : "($to)")
923          . " is on your roster in the following groups: { "
924          . join(" , ", keys %allGroups)
925          . " }";
926        queue_admin_msg($msg);
927    }
928}
929
930sub jroster_remove {
931    my $jid = shift;
932    my $name = shift;
933    my @groups = @{ shift() };
934    my $purgeGroups = shift;
[63bbef4]935    my $baseJID = baseJID($jid);
[f62550d]936
[b2648bc]937    my $iq = new Net::Jabber::IQ;
[f62550d]938    $iq->SetType('set');
939    my $item = new XML::Stream::Node('item');
940    $iq->NewChild('jabber:iq:roster')->AddChild($item);
941    $item->put_attrib(subscription=> 'remove');
942    foreach my $to (@ARGV) {
943        $item->put_attrib(jid => $to);
[455f1ab]944        $conn->getConnectionFromJID($jid)->Send($iq);
[63bbef4]945        queue_admin_msg("You ($baseJID) have removed ($to) from your roster.");
[f62550d]946    }
947}
948
949sub jroster_auth {
950    my $jid = shift;
951    my $name = shift;
952    my @groups = @{ shift() };
953    my $purgeGroups = shift;
[63bbef4]954    my $baseJID = baseJID($jid);
[f62550d]955
[b2648bc]956    my $p = new Net::Jabber::Presence;
[f62550d]957    $p->SetType('subscribed');
958    foreach my $to (@ARGV) {
959        $p->SetTo($to);
[455f1ab]960        $conn->getConnectionFromJID($jid)->Send($p);
[63bbef4]961        queue_admin_msg("($to) has been subscribed to your ($baseJID) presence.");
[f62550d]962    }
963}
964
965sub jroster_deauth {
966    my $jid = shift;
967    my $name = shift;
968    my @groups = @{ shift() };
969    my $purgeGroups = shift;
[63bbef4]970    my $baseJID = baseJID($jid);
[f62550d]971
[b2648bc]972    my $p = new Net::Jabber::Presence;
[f62550d]973    $p->SetType('unsubscribed');
974    foreach my $to (@ARGV) {
975        $p->SetTo($to);
[455f1ab]976        $conn->getConnectionFromJID($jid)->Send($p);
[63bbef4]977        queue_admin_msg("($to) has been unsubscribed from your ($baseJID) presence.");
[f62550d]978    }
979}
980
[38ffdf9]981################################################################################
982### Owl Callbacks
[b405ff6]983sub process_owl_jwrite {
[38ffdf9]984    my $body = shift;
985
[b2648bc]986    my $j = new Net::Jabber::Message;
[38ffdf9]987    $body =~ s/\n\z//;
[b405ff6]988    $j->SetMessage(
989        to   => $vars{jwrite}{to},
990        from => $vars{jwrite}{from},
991        type => $vars{jwrite}{type},
992        body => $body
993    );
[f62550d]994
[b405ff6]995    $j->SetThread( $vars{jwrite}{thread} )   if ( $vars{jwrite}{thread} );
996    $j->SetSubject( $vars{jwrite}{subject} ) if ( $vars{jwrite}{subject} );
997
[f62550d]998    my $m = j2o( $j, { direction => 'out' } );
[1c2e0b3]999    if ( $vars{jwrite}{type} ne 'groupchat') {
[13a3c1db]1000        BarnOwl::queue_message($m);
[38ffdf9]1001    }
[f62550d]1002
[b2648bc]1003    $j->RemoveFrom(); # Kludge to get around gtalk's random bits after the resource.
[f62550d]1004    if ($vars{jwrite}{sid} && $conn->sidExists( $vars{jwrite}{sid} )) {
[bed4ff1]1005        $conn->getConnectionFromSid($vars{jwrite}{sid})->Send($j);
[f62550d]1006    }
1007    else {
[455f1ab]1008        $conn->getConnectionFromJID($vars{jwrite}{from})->Send($j);
[f62550d]1009    }
1010
[6a6dd47]1011    delete $vars{jwrite};
[d609dd6]1012    BarnOwl::message("");   # Kludge to make the ``type your message...'' message go away
[38ffdf9]1013}
1014
1015### XMPP Callbacks
1016
[b405ff6]1017sub process_incoming_chat_message {
[f62550d]1018    my ( $sid, $j ) = @_;
[0eb4036]1019    if ($j->DefinedBody()) {
1020        BarnOwl::queue_message( j2o( $j, { direction => 'in',
1021                                           sid => $sid } ) );
1022    }
[38ffdf9]1023}
1024
[b405ff6]1025sub process_incoming_error_message {
[f62550d]1026    my ( $sid, $j ) = @_;
1027    my %jhash = j2hash( $j, { direction => 'in',
1028                              sid => $sid } );
[b6a253c]1029    $jhash{type} = 'admin';
[79c06ae]1030   
[d609dd6]1031    BarnOwl::queue_message( BarnOwl::Message->new(%jhash) );
[38ffdf9]1032}
1033
[b405ff6]1034sub process_incoming_groupchat_message {
[f62550d]1035    my ( $sid, $j ) = @_;
[b405ff6]1036
[38ffdf9]1037    # HACK IN PROGRESS (ignoring delayed messages)
[b405ff6]1038    return if ( $j->DefinedX('jabber:x:delay') && $j->GetX('jabber:x:delay') );
[d609dd6]1039    BarnOwl::queue_message( j2o( $j, { direction => 'in',
[f62550d]1040                                   sid => $sid } ) );
[38ffdf9]1041}
1042
[b405ff6]1043sub process_incoming_headline_message {
[f62550d]1044    my ( $sid, $j ) = @_;
[d609dd6]1045    BarnOwl::queue_message( j2o( $j, { direction => 'in',
[f62550d]1046                                   sid => $sid } ) );
[38ffdf9]1047}
1048
[b405ff6]1049sub process_incoming_normal_message {
[f62550d]1050    my ( $sid, $j ) = @_;
1051    my %jhash = j2hash( $j, { direction => 'in',
1052                              sid => $sid } );
[b6a253c]1053
1054    # XXX TODO: handle things such as MUC invites here.
1055
[b405ff6]1056    #    if ($j->HasX('http://jabber.org/protocol/muc#user'))
1057    #    {
1058    #   my $x = $j->GetX('http://jabber.org/protocol/muc#user');
1059    #   if ($x->HasChild('invite'))
1060    #   {
1061    #       $props
1062    #   }
1063    #    }
1064    #
[8af89a7]1065    if(BarnOwl::getvar('jabber:spew') eq 'on') {
1066        BarnOwl::queue_message( BarnOwl::Message->new(%jhash) );
1067    }
[b6a253c]1068}
1069
[b405ff6]1070sub process_muc_presence {
[f62550d]1071    my ( $sid, $p ) = @_;
[b405ff6]1072    return unless ( $p->HasX('http://jabber.org/protocol/muc#user') );
[f62550d]1073}
1074
1075
1076sub process_presence_available {
1077    my ( $sid, $p ) = @_;
[a1a2036]1078    my $from = $p->GetFrom('jid')->GetJID('base');
[2ce12de]1079    $completion_jids{$from} = 1;
1080    return unless (BarnOwl::getvar('jabber:show_logins') eq 'on');
[f62550d]1081    my $to = $p->GetTo();
1082    my $type = $p->GetType();
1083    my %props = (
1084        to => $to,
[a1a2036]1085        from => $p->GetFrom(),
[f62550d]1086        recipient => $to,
1087        sender => $from,
1088        type => 'jabber',
1089        jtype => $p->GetType(),
1090        status => $p->GetStatus(),
1091        show => $p->GetShow(),
1092        xml => $p->GetXML(),
1093        direction => 'in');
1094
1095    if ($type eq '' || $type eq 'available') {
1096        $props{body} = "$from is now online. ";
1097        $props{loginout} = 'login';
1098    }
1099    else {
1100        $props{body} = "$from is now offline. ";
1101        $props{loginout} = 'logout';
1102    }
[a1a2036]1103    BarnOwl::queue_message(BarnOwl::Message->new(%props));
[f62550d]1104}
1105
1106sub process_presence_subscribe {
1107    my ( $sid, $p ) = @_;
1108    my $from = $p->GetFrom();
1109    my $to = $p->GetTo();
1110    my %props = (
1111        to => $to,
1112        from => $from,
1113        xml => $p->GetXML(),
1114        type => 'admin',
1115        adminheader => 'Jabber presence: subscribe',
1116        direction => 'in');
1117
[5551208]1118    $props{body} = "Allow user ($from) to subscribe to your ($to) presence?\n" .
1119                   "(Answer with the `yes' or `no' commands)";
1120    $props{yescommand} = "jroster auth $from -a $to";
1121    $props{nocommand} = "jroster deauth $from -a $to";
1122    $props{question} = "true";
[d609dd6]1123    BarnOwl::queue_message(BarnOwl::Message->new(%props));
[f62550d]1124}
1125
1126sub process_presence_unsubscribe {
1127    my ( $sid, $p ) = @_;
1128    my $from = $p->GetFrom();
1129    my $to = $p->GetTo();
1130    my %props = (
1131        to => $to,
1132        from => $from,
1133        xml => $p->GetXML(),
1134        type => 'admin',
1135        adminheader => 'Jabber presence: unsubscribe',
1136        direction => 'in');
1137
1138    $props{body} = "The user ($from) has been unsubscribed from your ($to) presence.\n";
[d609dd6]1139    BarnOwl::queue_message(BarnOwl::Message->new(%props));
[f62550d]1140
1141    # Find a connection to reply with.
[63bbef4]1142    foreach my $jid ($conn->getJIDs()) {
[b2648bc]1143        my $cJID = new Net::Jabber::JID;
[63bbef4]1144        $cJID->SetJID($jid);
1145        if ($to eq $cJID->GetJID('base') ||
1146            $to eq $cJID->GetJID('full')) {
[f62550d]1147            my $reply = $p->Reply(type=>"unsubscribed");
[455f1ab]1148            $conn->getConnectionFromJID($jid)->Send($reply);
[f62550d]1149            return;
1150        }
1151    }
1152}
[38ffdf9]1153
[f62550d]1154sub process_presence_subscribed {
1155    my ( $sid, $p ) = @_;
[b73ce70]1156    queue_admin_msg("ignoring:".$p->GetXML()) if BarnOwl::getvar('jabber:spew') eq 'on';
[f62550d]1157    # RFC 3921 says we should respond to this with a "subscribe"
1158    # but this causes a flood of sub/sub'd presence packets with
1159    # some servers, so we won't. We may want to detect this condition
1160    # later, and have per-server settings.
1161    return;
1162}
1163
1164sub process_presence_unsubscribed {
1165    my ( $sid, $p ) = @_;
[b73ce70]1166    queue_admin_msg("ignoring:".$p->GetXML()) if BarnOwl::getvar('jabber:spew') eq 'on';
[f62550d]1167    # RFC 3921 says we should respond to this with a "subscribe"
1168    # but this causes a flood of unsub/unsub'd presence packets with
1169    # some servers, so we won't. We may want to detect this condition
1170    # later, and have per-server settings.
1171    return;
[b405ff6]1172}
[38ffdf9]1173
[9667006]1174sub process_presence_error {
1175    my ( $sid, $p ) = @_;
1176    my $code = $p->GetErrorCode();
1177    my $error = $p->GetError();
[d609dd6]1178    BarnOwl::error("Jabber: $code $error");
[9667006]1179}
1180
[f62550d]1181
[38ffdf9]1182### Helper functions
1183
[b405ff6]1184sub j2hash {
1185    my $j   = shift;
[7a45a72]1186    my %props = (type => 'jabber',
1187                 dir  => 'none',
1188                 %{$_[0]});
[38ffdf9]1189
[7a45a72]1190    my $dir = $props{direction};
[38ffdf9]1191
[b6a253c]1192    my $jtype = $props{jtype} = $j->GetType();
[b405ff6]1193    my $from = $j->GetFrom('jid');
1194    my $to   = $j->GetTo('jid');
[38ffdf9]1195
[b6a253c]1196    $props{from} = $from->GetJID('full');
1197    $props{to}   = $to->GetJID('full');
[38ffdf9]1198
[b6a253c]1199    $props{recipient}  = $to->GetJID('base');
1200    $props{sender}     = $from->GetJID('base');
[b405ff6]1201    $props{subject}    = $j->GetSubject() if ( $j->DefinedSubject() );
1202    $props{thread}     = $j->GetThread() if ( $j->DefinedThread() );
[e979da9]1203    if ( $j->DefinedBody() ) {
1204        $props{body}   = $j->GetBody();
1205        $props{body}  =~ s/\xEF\xBB\xBF//g; # Strip stray Byte-Order-Marks.
1206    }
[b405ff6]1207    $props{error}      = $j->GetError() if ( $j->DefinedError() );
1208    $props{error_code} = $j->GetErrorCode() if ( $j->DefinedErrorCode() );
[b6a253c]1209    $props{xml}        = $j->GetXML();
[38ffdf9]1210
[b405ff6]1211    if ( $jtype eq 'chat' ) {
[cb769bb]1212        $props{private} = 1;
[b2648bc]1213
1214        my $connection;
1215        if ($dir eq 'in') {
1216            $connection = $conn->getConnectionFromSid($props{sid});
1217        }
1218        else {
1219            $connection = $conn->getConnectionFromJID($props{from});
1220        }
1221
1222        # Check to see if we're doing personals with someone in a muc.
1223        # If we are, show the full jid because the base jid is the room.
1224        if ($connection) {
1225            $props{sender} = $props{from}
1226              if ($connection->FindMUC(jid => $from));
1227            $props{recipient} = $props{to}
1228              if ($connection->FindMUC(jid => $to));
1229        }
[2ce12de]1230
1231        # Populate completion.
1232        if ($dir eq 'in') {
1233            $completion_jids{ $props{sender} }= 1;
1234        }
1235        else {
1236            $completion_jids{ $props{recipient} } = 1;
1237        }
[38ffdf9]1238    }
[b405ff6]1239    elsif ( $jtype eq 'groupchat' ) {
1240        my $nick = $props{nick} = $from->GetResource();
1241        my $room = $props{room} = $from->GetJID('base');
[2ce12de]1242        $completion_jids{$room} = 1;
[45d9eb0]1243
[b405ff6]1244        $props{sender} = $nick || $room;
1245        $props{recipient} = $room;
1246
1247        if ( $props{subject} && !$props{body} ) {
1248            $props{body} =
1249              '[' . $nick . " has set the topic to: " . $props{subject} . "]";
1250        }
[b6a253c]1251    }
[b405ff6]1252    elsif ( $jtype eq 'normal' ) {
[cb769bb]1253        $props{private} = 1;
[b6a253c]1254    }
[b405ff6]1255    elsif ( $jtype eq 'headline' ) {
[b6a253c]1256    }
[b405ff6]1257    elsif ( $jtype eq 'error' ) {
1258        $props{body}     = "Error "
1259          . $props{error_code}
1260          . " sending to "
1261          . $props{from} . "\n"
1262          . $props{error};
1263    }
1264
[b6a253c]1265    return %props;
1266}
[38ffdf9]1267
[b405ff6]1268sub j2o {
[d609dd6]1269    return BarnOwl::Message->new( j2hash(@_) );
[38ffdf9]1270}
1271
[b405ff6]1272sub queue_admin_msg {
[38ffdf9]1273    my $err = shift;
[f6c2b3d]1274    BarnOwl::admin_message("Jabber", $err);
[38ffdf9]1275}
[b6a253c]1276
[b405ff6]1277sub getServerFromJID {
[6a6dd47]1278    my $jid = shift;
1279    my $res = new Net::DNS::Resolver;
[b405ff6]1280    my $packet =
1281      $res->search( '_xmpp-client._tcp.' . $jid->GetServer(), 'srv' );
[6a6dd47]1282
[b405ff6]1283    if ($packet)    # Got srv record.
[6a6dd47]1284    {
[b405ff6]1285        my @answer = $packet->answer;
1286        return $answer[0]{target}, $answer[0]{port};
[6a6dd47]1287    }
1288
1289    return $jid->GetServer(), 5222;
1290}
1291
[9f183ff]1292sub defaultJID {
[63bbef4]1293    return ( $conn->getJIDs() )[0] if ( $conn->connected() == 1 );
[b405ff6]1294    return;
[9f183ff]1295}
1296
[84296f6]1297sub baseJID {
[63bbef4]1298    my $givenJIDStr = shift;
[b2648bc]1299    my $givenJID    = new Net::Jabber::JID;
[63bbef4]1300    $givenJID->SetJID($givenJIDStr);
1301    return $givenJID->GetJID('base');
[84296f6]1302}
1303
[455f1ab]1304sub resolveConnectedJID {
[367fbf3]1305    my $givenJIDStr = shift;
[06f5ef8]1306    my $loose = shift || 0;
[b2648bc]1307    my $givenJID    = new Net::Jabber::JID;
[63bbef4]1308    $givenJID->SetJID($givenJIDStr);
[b405ff6]1309
[6a6dd47]1310    # Account fully specified.
[63bbef4]1311    if ( $givenJID->GetResource() ) {
[b405ff6]1312        # Specified account exists
[63bbef4]1313        return $givenJIDStr if ($conn->jidExists($givenJIDStr) );
[06f5ef8]1314        return resolveConnectedJID($givenJID->GetJID('base')) if $loose;
[63bbef4]1315        die("Invalid account: $givenJIDStr");
[6a6dd47]1316    }
[b405ff6]1317
[6a6dd47]1318    # Disambiguate.
[b405ff6]1319    else {
[06f5ef8]1320        my $JIDMatchingJID = "";
1321        my $strMatchingJID = "";
1322        my $JIDMatches = "";
1323        my $strMatches = "";
1324        my $JIDAmbiguous = 0;
1325        my $strAmbiguous = 0;
[b405ff6]1326
[63bbef4]1327        foreach my $jid ( $conn->getJIDs() ) {
[b2648bc]1328            my $cJID = new Net::Jabber::JID;
[63bbef4]1329            $cJID->SetJID($jid);
1330            if ( $givenJIDStr eq $cJID->GetJID('base') ) {
[06f5ef8]1331                $JIDAmbiguous = 1 if ( $JIDMatchingJID ne "" );
1332                $JIDMatchingJID = $jid;
1333                $JIDMatches .= "\t$jid\n";
1334            }
1335            if ( $cJID->GetJID('base') =~ /$givenJIDStr/ ) {
1336                $strAmbiguous = 1 if ( $strMatchingJID ne "" );
1337                $strMatchingJID = $jid;
1338                $strMatches .= "\t$jid\n";
[b405ff6]1339            }
1340        }
1341
1342        # Need further disambiguation.
[06f5ef8]1343        if ($JIDAmbiguous) {
1344            my $errStr =
1345                "Ambiguous account reference. Please specify a resource.\n";
1346            die($errStr.$JIDMatches);
[b405ff6]1347        }
1348
[06f5ef8]1349        # It's this one.
1350        elsif ($JIDMatchingJID ne "") {
1351            return $JIDMatchingJID;
[b405ff6]1352        }
1353
[06f5ef8]1354        # Further resolution by substring.
1355        elsif ($strAmbiguous) {
1356            my $errStr =
1357                "Ambiguous account reference. Please be more specific.\n";
1358            die($errStr.$strMatches);
1359        }
1360
1361        # It's this one, by substring.
1362        elsif ($strMatchingJID ne "") {
1363            return $strMatchingJID;
1364        }
1365
1366        # Not one of ours.
[b405ff6]1367        else {
[06f5ef8]1368            die("Invalid account: $givenJIDStr");
[b405ff6]1369        }
[06f5ef8]1370
[6a6dd47]1371    }
1372    return "";
1373}
[84296f6]1374
[455f1ab]1375sub resolveDestJID {
1376    my ($to, $from) = @_;
1377    my $jid = Net::Jabber::JID->new($to);
1378
1379    my $roster = $conn->getRosterFromJID($from);
1380    my @jids = $roster->jids('all');
1381    for my $j (@jids) {
[63bbef4]1382        if(($roster->query($j, 'name') || $j->GetUserID()) eq $to) {
[3ce5bdc]1383            return $j->GetJID('full');
1384        } elsif($j->GetJID('base') eq baseJID($to)) {
[c25a20f]1385            return $jid->GetJID('full');
[455f1ab]1386        }
1387    }
1388
[71e33ca]1389    # If we found nothing being clever, check to see if our input was
1390    # sane enough to look like a jid with a UserID.
1391    return $jid->GetJID('full') if $jid->GetUserID();
1392    return undef;
[455f1ab]1393}
1394
1395sub resolveType {
1396    my $to = shift;
1397    my $from = shift;
[3ce5bdc]1398    return unless $from;
[455f1ab]1399    my @mucs = $conn->getConnectionFromJID($from)->MUCs;
1400    if(grep {$_->BaseJID eq $to } @mucs) {
1401        return 'groupchat';
1402    } else {
1403        return 'chat';
1404    }
1405}
1406
1407sub guess_jwrite {
1408    # Heuristically guess what jids a jwrite was meant to be going to/from
1409    my ($from, $to) = (@_);
1410    my ($from_jid, $to_jid);
[989fae0]1411    my @matches;
[455f1ab]1412    if($from) {
[06f5ef8]1413        $from_jid = resolveConnectedJID($from, 1);
[455f1ab]1414        die("Unable to resolve account $from") unless $from_jid;
1415        $to_jid = resolveDestJID($to, $from_jid);
[0da506c]1416        push @matches, [$from_jid, $to_jid] if $to_jid;
[455f1ab]1417    } else {
[989fae0]1418        for my $f ($conn->getJIDs) {
[455f1ab]1419            $to_jid = resolveDestJID($to, $f);
1420            if(defined($to_jid)) {
[989fae0]1421                push @matches, [$f, $to_jid];
[455f1ab]1422            }
1423        }
[989fae0]1424        if($to =~ /@/) {
1425            push @matches, [$_, $to]
1426               for ($conn->getJIDs);
1427        }
[455f1ab]1428    }
1429
[989fae0]1430    for my $m (@matches) {
1431        my $type = resolveType($m->[1], $m->[0]);
1432        push @$m, $type;
1433    }
[45d9eb0]1434
[989fae0]1435    return @matches;
[455f1ab]1436}
1437
[2ce12de]1438################################################################################
1439### Completion
1440
1441sub complete_user_or_muc { return keys %completion_jids; }
1442sub complete_account { return $conn->getJIDs(); }
1443
1444sub complete_jwrite {
1445    my $ctx = shift;
1446    return complete_flags($ctx,
1447                          [qw(-t -i -s)],
1448                          {
1449                              "-a" => \&complete_account,
1450                          },
[57ba9f1]1451                          \&complete_user_or_muc
[2ce12de]1452        );
1453}
1454
[57ba9f1]1455BarnOwl::Completion::register_completer(jwrite => sub { BarnOwl::Module::Jabber::complete_jwrite(@_) });
[2ce12de]1456
[84296f6]14571;
Note: See TracBrowser for help on using the repository browser.