package DJabberd::Connection::ClientIn;
use strict;
use base 'DJabberd::Connection';
use fields (
# {=server-needs-client-wanted-roster-state}
'requested_roster', # bool: if user has requested their roster,
'got_initial_presence', # bool: if user has already sent their initial presence
'is_available', # bool: is an "available resource"
'directed_presence', # the jids we have sent directed presence too
'pend_in_subscriptions', # undef or arrayref of presence type='subscribe' packets to be redelivered when we become available
);
sub note_pend_in_subscription {
my ($self, $pres_packet) = @_;
if ($self->is_available) {
# can send it now if we're online
$pres_packet->deliver($self->vhost);
} else {
# keep it on a list and deliver it later, when we get initial presence
push @{$self->{pend_in_subscriptions} ||= []}, $pres_packet;
}
}
sub directed_presence {
my $self = shift;
return keys %{$self->{directed_presence}};
}
sub add_directed_presence {
my ($self, $to_jid) = @_;
($self->{directed_presence} ||= {})->{$to_jid} = 1;
}
sub clear_directed_presence {
my $self = shift;
delete $self->{directed_presence};
}
sub requested_roster {
my $self = shift;
return $self->{requested_roster};
}
sub set_requested_roster {
my ($self, $val) = @_;
$self->{requested_roster} = $val;
}
sub set_available {
my ($self, $val) = @_;
$self->{is_available} = $val;
}
sub is_available {
my $self = shift;
return $self->{is_available};
}
# called when a presence broadcast is received. on first time,
# returns tru.
sub is_initial_presence {
my $self = shift;
return 0 if $self->{got_initial_presence};
return $self->{got_initial_presence} = 1;
}
sub on_initial_presence {
my $self = shift;
$self->send_resource_presences;
$self->send_presence_probes;
$self->send_pending_sub_requests;
$self->vhost->hook_chain_fast('OnInitialPresence',
[ $self ], {});
}
sub send_resource_presences {
my $self = shift;
my $vhost = $self->vhost;
my $my_jid = $self->bound_jid;
$vhost->check_presence($my_jid, sub {
my $map = shift;
foreach my $from_jid_str (keys %$map) {
next if $from_jid_str eq $my_jid->as_string;
my $stanza = $map->{$from_jid_str};
my $to_send = $stanza->clone;
#$to_send->set_from($from_jid_str); # set_from is happy to recieve a string instead of a JID object
$to_send->set_to($my_jid);
$to_send->deliver($self);
}
});
}
sub send_presence_probes {
my $self = shift;
my $send_probes = sub {
my $roster = shift;
# go through rosteritems who we're subscribed to
my $from_jid = $self->bound_jid;
foreach my $it ($roster->to_items) {
my $probe = DJabberd::Presence->probe(to => $it->jid, from => $from_jid);
# if we know the other side trusts us, let's avoid us internally not
# trusting ourselves and doing more work in Presence.pm than we need to,
# reloading lots of roster items and such.
if ($it->subscription->sub_from) {
$probe->{dont_load_rosteritem} = 1;
}
$probe->procdeliver($self->vhost);
}
};
$self->vhost->get_roster($self->bound_jid, on_success => $send_probes);
}
sub send_pending_sub_requests {
my $self = shift;
return unless $self->{pend_in_subscriptions};
foreach my $pkt (@{ $self->{pend_in_subscriptions} }) {
$pkt->deliver($self->vhost);
}
$self->{pend_in_subscriptions} = undef;
}
sub close {
my $self = shift;
return if $self->{closed};
# send an unavailable presence broadcast if we've gone away
if ($self->is_available) {
# set unavailable here, BEFORE we sent out unavailable stanzas,
# so if the stanzas 'bounce' or otherwise write back to our full JID,
# they'll either drop/bounce instead of writing to what will
# soon be a dead full JID.
$self->set_available(0);
my $unavail = DJabberd::Presence->unavailable_stanza;
$unavail->broadcast_from($self);
}
if (my $jid = $self->bound_jid) {
$self->vhost->unregister_jid($jid, $self);
DJabberd::Presence->forget_last_presence($jid);
}
if ($self->vhost && $self->vhost->are_hooks("ConnectionClosing")) {
$self->vhost->run_hook_chain(phase => "ConnectionClosing",
args => [ $self ],
methods => {
fallback => sub {
},
},
);
}
$self->SUPER::close;
}
sub namespace {
return "jabber:client";
}
sub on_stream_start {
my DJabberd::Connection $self = shift;
my $ss = shift;
return $self->close unless $ss->xmlns eq $self->namespace; # FIXME: should be stream error
$self->{in_stream} = 1;
my $to_host = $ss->to;
DJabberd::Log->get_logger->info($to_host);
my $vhost = $self->server->lookup_vhost($to_host);
return $self->close_no_vhost($to_host)
unless ($vhost);
$self->set_vhost($vhost);
# FIXME: bitch if we're starting a stream when we already have one, and we aren't
# expecting a new stream to start (like after SSL or SASL)
my %opts = ( namespace => 'jabber:client' );
## If sasl successfully completed, we shouldn't propose deprecated iq-auth method anymore
if ($self->sasl && $self->sasl->authenticated_jid) {
$opts{features} = qq{};
# The protocol for session establishment was determined to be unnecessary and
# therefore the content previously defined in Section 3 of RFC 3921
# was removed. However, for the sake of backward-compatibility server
# implementations are encouraged to advertise support for the feature,
# even though session establishment is a "no-op".
$opts{features} .= qq{};
}
else {
$opts{features} = qq{};
}
$self->start_stream_back($ss, %opts);
}
sub is_server { 0 }
my %element2class = (
"{jabber:client}iq" => 'DJabberd::IQ',
"{jabber:client}message" => 'DJabberd::Message',
"{jabber:client}presence" => 'DJabberd::Presence',
"{urn:ietf:params:xml:ns:xmpp-tls}starttls" => 'DJabberd::Stanza::StartTLS',
);
sub on_stanza_received {
my ($self, $node) = @_;
if ($self->xmllog->is_info) {
$self->log_incoming_data($node);
}
my $class = $element2class{$node->element};
$self->log->debug("node is " . $node->as_xml);
#warn $node->as_xml;
$self->vhost->hook_chain_fast("HandleStanza",
[ $node, $self ],
{
handle => sub {
my ($self, $handling_class) = @_;
$class = $handling_class;
},
}
) unless $class;
return $self->stream_error("unsupported-stanza-type") unless $class;
$DJabberd::Stats::counter{"ClientIn:$class"}++;
# same variable as $node, but down(specific)-classed.
my $stanza = $class->downbless($node, $self);
$self->vhost->hook_chain_fast("filter_incoming_client",
[ $stanza, $self ],
{
reject => sub { }, # just stops the chain
},
\&filter_incoming_client_builtin,
);
}
sub is_authenticated_jid {
my ($self, $jid) = @_;
my $bj = $self->bound_jid;
return 0 unless $jid && $bj;
return $bj->as_bare_string eq $jid->as_bare_string if $jid->is_bare;
return $bj->as_string eq $jid->as_string;
}
# This is not really a method, but gets invoked as a hookchain item
# so if you subclass this class, this will still get called
sub filter_incoming_client_builtin {
my ($vhost, $cb, $stanza, $self) = @_;
# -- the JID or hostname provided in a 'from'
# address does not match an authorized JID or validated domain
# negotiated between servers via SASL or dialback, or between a
# client and a server via authentication and resource binding.
#{=clientin-invalid-from}
my $from = $stanza->from_jid;
if ($from && ! $self->is_authenticated_jid($from)) {
# make sure it is from them, if they care to tell us who they are.
# (otherwise further processing should assume it's them anyway)
# libgaim quirks bug. libgaim sends bogus from on IQ errors.
# see doc/quirksmode.txt.
if ($vhost->quirksmode && $stanza->isa("DJabberd::IQ") &&
$stanza->type eq "error" && $stanza->from eq $stanza->to) {
# fix up from address
$from = $self->bound_jid;
$stanza->set_from($from);
} else {
return $self->stream_error('invalid-from');
}
}
# if no from, we set our own
if (! $from) {
my $bj = $self->bound_jid;
$stanza->set_from($bj->as_string) if $bj;
}
$vhost->hook_chain_fast("switch_incoming_client",
[ $stanza ],
{
process => sub { $stanza->process($self) },
deliver => sub { $stanza->deliver($self) },
},
sub {
$stanza->on_recv_from_client($self);
});
}
1;