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;