source: perl/modules/jabber.pl @ 3066d23

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