1package DJabberd::Connection;
2use strict;
3use warnings;
4use base 'Danga::Socket';
5use bytes;
6use fields (
7            'saxhandler',
8            'parser',
9
10            'bound_jid',      # undef until resource binding - then DJabberd::JID object
11
12            'vhost',          # DJabberd::VHost instance (undef until they send a stream start element)
13            'server',         # our DJabberd server object, which we used to find the VHost
14            'ssl',            # undef when not in ssl mode, else the $ssl object from Net::SSLeay
15            'stream_id',      # undef until set first time
16            'to_host',        # undef until stream start
17            'version',        # the DJabberd::StreamVersion we negotiated
18            'rcvd_features',  # the features stanza we've received from the other party
19            'log',            # Log::Log4perl object for this connection
20            'xmllog',         # Log::Log4perl object that controls raw xml logging
21            'id',             # connection id, used for logging purposes
22            'write_when_readable',  # arrayref/bool, for SSL:  as boolean, we're only readable so we can write again.
23                                    # but bool true is actually an arrayref of previous watch_read state
24            'iqctr',          # iq counter.  incremented whenever we SEND an iq to the party (roster pushes, etc)
25            'in_stream',      # bool:  true if we're in a stream tag
26            'counted_close',  # bool:  temporary here to track down the overcounting of disconnects
27            'disconnect_handlers',  # array of coderefs to call when this connection is closed for any reason
28            'sasl',           # the sasl connection object, when sasl has been or is being negotiated
29            );
30
31our $connection_id = 1;
32
33use XML::SAX ();
34use DJabberd::XMLParser;
35use Digest::SHA1 qw(sha1_hex);
36
37use DJabberd::SAXHandler;
38use DJabberd::JID;
39use DJabberd::IQ;
40use DJabberd::Message;
41use DJabberd::Util qw(exml tsub);
42
43use Data::Dumper;
44use Carp qw(croak);
45
46use DJabberd::Log;
47our $hook_logger = DJabberd::Log->get_logger("DJabberd::Hook");
48
49use constant POLLIN        => 1;
50use constant POLLOUT       => 4;
51
52BEGIN {
53    my $xmldebug = $ENV{XMLDEBUG};
54
55    if ($xmldebug) {
56        eval 'use constant XMLDEBUG => "' . quotemeta($xmldebug) . '"';
57        die "XMLDEBUG path '$xmldebug' needs to be a directory writable by the user you are running $0 as\n" unless -w $xmldebug;
58    } else {
59        eval "use constant XMLDEBUG => ''";
60    }
61}
62
63our %LOGMAP;
64
65sub new {
66    my ($class, $sock, $server) = @_;
67    my $self = $class->SUPER::new($sock);
68
69    croak("Server param not a DJabberd (server) object, '" . ref($server) . "'")
70        unless $server->isa("DJabberd");
71
72    $self->{vhost}   = undef;  # set once we get a stream start header from them.
73    $self->{server}  = $server;
74    Scalar::Util::weaken($self->{server});
75
76    $self->{log}     = DJabberd::Log->get_logger($class);
77
78    # hack to inject XML after Connection:: in the logger category
79    my $xml_category = $class;
80    $xml_category =~ s/Connection::/Connection::XML::/;
81
82    $self->{xmllog}  = DJabberd::Log->get_logger($xml_category);
83
84    my $fromip = $self->peer_ip_string || "<undef>";
85
86    # a health check monitor doesn't get an id assigned/wasted on it, and doesn't log
87    # so it's less annoying to look at:
88    unless ($server->is_monitor_host($fromip)) {
89        $self->{id}      = $connection_id++;
90        $self->log->debug("New connection '$self->{id}' from $fromip");
91    }
92
93    if (XMLDEBUG) {
94        system("mkdir -p " . XMLDEBUG ."/$$/");
95        my $handle = IO::Handle->new;
96        no warnings;
97        my $from = $fromip || "outbound";
98        my $filename = "+>" . XMLDEBUG . "/$$/$from-$self->{id}";
99        open ($handle, $filename) || die "Cannot open $filename: $!";
100        $handle->autoflush(1);
101        $LOGMAP{$self} = $handle;
102    }
103    return $self;
104}
105
106sub log {
107    return $_[0]->{log};
108}
109
110sub xmllog {
111    return $_[0]->{xmllog};
112}
113
114sub handler {
115    return $_[0]->{saxhandler};
116}
117
118sub vhost {
119    my DJabberd::Connection $self = $_[0];
120    return $self->{vhost};
121}
122
123sub server {
124    my DJabberd::Connection $self = $_[0];
125    return $self->{server};
126}
127
128sub bound_jid {
129    my DJabberd::Connection $self = $_[0];
130    return $self->{bound_jid};
131}
132
133sub new_iq_id {
134    my DJabberd::Connection $self = shift;
135    $self->{iqctr}++;
136    return "iq$self->{iqctr}";
137}
138
139sub log_outgoing_data {
140    my ($self, $text) = @_;
141    my $id = $self->{id} ||= 'no_id';
142
143    if($self->xmllog->is_debug) {
144        $self->xmllog->debug("$id > " . $text);
145    } else {
146        local $DJabberd::ASXML_NO_TEXT = 1;
147        $self->xmllog->info("$id > " . $text);
148    }
149}
150
151sub log_incoming_data {
152    my ($self, $node) = @_;
153    if($self->xmllog->is_debug) {
154        $self->xmllog->debug("$self->{id} < " . $node->as_xml);
155    } else {
156        local $DJabberd::ASXML_NO_TEXT = 1;
157        $self->xmllog->info("$self->{id} < " . $node->as_xml);
158    }
159}
160
161sub discard_parser {
162    my DJabberd::Connection $self = shift;
163    # TODOTEST: bunch of new connections speaking not-well-formed xml and getting booted, then watch for mem leaks
164    my $p = $self->{parser}   or return;
165    $self->{parser}        = undef;
166    $self->{saxhandler}->cleanup;
167    $self->{saxhandler} = undef;
168    Danga::Socket->AddTimer(0, sub {
169        $p->finish_push;
170    });
171}
172
173my %free_parsers;  # $ns -> [ [parser,handler]* ]
174sub borrow_a_parser {
175    my DJabberd::Connection $self = $_[0];
176
177    # get a parser off the freelist
178    if ($self->{in_stream}) {
179        my $ns = $self->namespace;
180        my $freelist = $free_parsers{$ns} || [];
181        if (my $ent = pop @$freelist) {
182            ($self->{parser}, $self->{saxhandler}) = @$ent;
183            $self->{saxhandler}->set_connection($self);
184            # die "ASSERT" unless $self->{parser}{LibParser};
185            return $self->{parser};
186        }
187    }
188
189    # no parser?  gotta make one.
190    my $handler = DJabberd::SAXHandler->new;
191    my $p       = DJabberd::XMLParser->new(Handler => $handler);
192
193    if ($self->{in_stream}) {
194        # gotta get it into stream-able state with an open root node
195        # so client can send us multiple stanzas.  unless we're waiting for
196        # the start stream, in which case it may also have an xml declaration
197        # like <?xml ... ?> at top, which can only come at top, so we need
198        # a virgin parser.
199        my $ns = $self->namespace;
200
201        # this is kinda a hack, in that it hard-codes the namespace
202        # prefixes 'db' and 'stream',...  however, RFC 3920 seection
203        # 11.2.1, 11.2.3, etc say it's okay for historical reasons to
204        # force the prefixes for both 'stream' and 'db'
205        my $other = $ns eq "jabber:server" ? "xmlns:db='jabber:server:dialback'" : "";
206        $p->parse_chunk_scalarref(\ qq{<stream:stream
207                                           xmlns='$ns'
208                                           xmlns:stream='http://etherx.jabber.org/streams'
209                                           $other>});
210    }
211
212    $handler->set_connection($self);
213    $self->{saxhandler} = $handler;
214    $self->{parser} = $p;
215    return $p;
216}
217
218sub return_parser {
219    my DJabberd::Connection $self = $_[0];
220    return unless $self->{server}->share_parsers;
221    return unless $self->{in_stream};
222
223    my $freelist = $free_parsers{$self->namespace} ||= [];
224
225    # BIG FAT WARNING:  with fields objects, you can't do:
226    #   my $p = delete $self->{parser}.
227    # You'd think you could, but it leaves $self->{parser} with some magic fucked up undef/but not
228    # value and $p's refcount never goes down.  Some Perl bug due to fields, weakrefs, etc?  Who knows.
229    # This affects Perl 5.8.4, but not Perl 5.8.8.
230    my $p       = $self->{parser};     $self->{parser} = undef;
231    my $handler = $self->{saxhandler}; $self->{saxhandler} = undef;
232    $handler->set_connection(undef);
233
234    if (@$freelist < 5) {
235        push @$freelist, [$p, $handler];
236
237    } else {
238        Danga::Socket->AddTimer(0, sub {
239            $p->finish_push;
240        });
241    }
242}
243
244sub set_rcvd_features {
245    my ($self, $feat_stanza) = @_;
246    $self->{rcvd_features} = $feat_stanza;
247}
248
249sub set_bound_jid {
250    my ($self, $jid) = @_;
251    die unless $jid && $jid->isa('DJabberd::JID');
252    $self->{bound_jid} = $jid;
253}
254
255sub set_to_host {
256    my ($self, $host) = @_;
257    $self->{to_host} = $host;
258}
259
260sub to_host {
261    my $self = shift;
262    return $self->{to_host} or
263        die "To host accessed before it was set";
264}
265
266sub set_version {
267    my ($self, $verob) = @_;
268    $self->{version} = $verob;
269}
270
271sub version {
272    my $self = shift;
273    return $self->{version} or
274        die "Version accessed before it was set";
275}
276
277sub stream_id {
278    my $self = shift;
279    return $self->{stream_id} ||= Digest::SHA1::sha1_hex(rand() . rand() . rand());
280}
281
282# only use this run_hook_chain when
283sub run_hook_chain {
284    my $self = shift;
285    my %opts = @_;
286    $opts{hook_invocant} = $self;
287
288    my $known_deprecated = delete $opts{deprecated};
289    my ($pkg, $filename, $line) = caller;
290    my $vhost = $self->vhost;
291
292    unless ($known_deprecated) {
293        warn("DEPRECATED caller ($pkg/$filename/$line) of run_hook_chain on a connection\n");
294    }
295    return DJabberd::VHost::run_hook_chain($vhost, %opts);
296}
297
298# this can fail to signal that this connection can't work on this
299# vhost for instance, this vhost doesn't support s2s, so a serverin or
300# dialback subclass can override this to return 0 when s2s isn't
301# enabled for the vhost
302sub set_vhost {
303    my ($self, $vhost) = @_;
304    Carp::croak("Not a DJabberd::VHost: $vhost") unless UNIVERSAL::isa($vhost, "DJabberd::VHost");
305    $self->{vhost} = $vhost;
306    Scalar::Util::weaken($self->{vhost});
307    return 1;
308}
309
310# called by DJabberd::SAXHandler
311sub on_stanza_received {
312    my ($self, $node) = @_;
313    die "SUBCLASSES MUST OVERRIDE 'on_stanza_received' for $self\n";
314}
315
316# subclasses should override returning 0 or 1
317sub is_server {
318    die "Undefined 'is_server' for $_[0]";
319}
320
321sub process_incoming_stanza_from_s2s_out {
322    my ($self, $node) = @_;
323
324    my %stanzas = (
325                   "{urn:ietf:params:xml:ns:xmpp-tls}starttls"  => 'DJabberd::Stanza::StartTLS',
326                   "{http://etherx.jabber.org/streams}features" => 'DJabberd::Stanza::StreamFeatures',
327                   );
328
329    my $class = $stanzas{$node->element};
330    unless ($class) {
331        warn "Unknown/handled stanza: " . $node->element . " on connection ($self->{id}), " . ref($self) .  "\n";
332        return;
333    }
334
335    my $obj = $class->downbless($node, $self);
336    $obj->on_recv_from_server($self);
337}
338
339sub send_stanza {
340    my ($self, $stanza) = @_;
341
342    # getter subref for pre_stanza_write hooks to
343    # get at their own private copy of the stanza
344    my $cloned;
345    my $getter = sub {
346        return $cloned if $cloned;
347        if ($self != $stanza->connection) {
348            $cloned = $stanza->clone;
349            $cloned->set_connection($self);
350        } else {
351            $cloned = $stanza;
352        }
353        return $cloned;
354    };
355
356    $self->vhost->hook_chain_fast("pre_stanza_write",
357                                  [ $getter ],
358                                  {
359                                     # TODO: implement.
360                                  },
361                                  sub {
362                                      # if any hooks called the $getter, instantiating
363                                      # the $cloned copy, then that's what we write.
364                                      # as an optimization (the fast path), we just
365                                      # write the untouched, uncloned original.
366                                      $self->write_stanza($cloned || $stanza);
367                                  });
368}
369
370sub write_stanza {
371    my ($self, $stanza) = @_;
372
373    my $to_jid    = $stanza->to_jid  || die "missing 'to' attribute in ".$stanza->element_name." stanza";
374    my $from_jid  = $stanza->from_jid;  # this can be iq
375    my $elename   = $stanza->element_name;
376
377    my $other_attrs = "";
378    my $attrs = $stanza->attrs;
379    while (my ($k, $v) = each %$attrs) {
380        $k =~ s/.+\}//; # get rid of the namespace
381        next if $k eq "to" || $k eq "from";
382        $other_attrs .= "$k=\"" . exml($v) . "\" ";
383    }
384
385    my $from = "";
386    die "no from" if ($elename ne 'iq' && !$from_jid);
387
388    $from = $from_jid ? " from='" . $from_jid->as_string_exml . "'" : "";
389
390    my $to_str = $to_jid->as_string_exml;
391    my $ns = $self->namespace;
392
393    my $xml = "<$elename $other_attrs to='$to_str'$from>" . $stanza->innards_as_xml . "</$elename>";
394
395    if ($self->xmllog->is_info) {
396        # refactor this out
397        my $debug;
398        if($self->xmllog->is_debug) {
399            $debug = "<$elename $other_attrs to='$to_str'$from>" . $stanza->innards_as_xml . "</$elename>";
400        } else {
401            local $DJabberd::ASXML_NO_TEXT = 1;
402            $debug = "<$elename $other_attrs to='$to_str'$from>" . $stanza->innards_as_xml . "</$elename>";
403        }
404        $self->log_outgoing_data($debug);
405    }
406
407    $self->write(\$xml);
408}
409
410sub namespace {
411    my $self = shift;
412    Carp::confess("namespace called on $self which has no namespace");
413}
414
415# return SSL state object.  more useful as a boolean if conn is in SSL mode.
416sub ssl {
417    my $self = shift;
418    return $self->{ssl};
419}
420
421# return the DJabberd::SASL::Connection object attached to this connection
422# if SASL is being or has been negotiated
423sub sasl {
424    my $self = shift;
425    return $self->{sasl};
426}
427
428# called by Danga::Socket when a write doesn't fully go through.  by default it
429# enables writability.  but we want to do nothing if we're waiting for a read for SSL
430sub on_incomplete_write {
431    my $self = shift;
432    return if $self->{write_when_readable};
433    $self->SUPER::on_incomplete_write;
434}
435
436# called by SSL machinery to let us know a write is stalled on readability.
437# so we need to (at least temporarily) go readable and then process writes.
438sub write_when_readable {
439    my $self = shift;
440
441    # enable readability, but remember old value so we can pop it back
442    my $prev_readable = ($self->{event_watch} & POLLIN)  ? 1 : 0;
443    $self->watch_read(1);
444    $self->{write_when_readable} = [ $prev_readable ];
445
446    # don't need to push/pop its state because Danga::Socket->write, called later,
447    # will do the one final write, or if not all written, will turn on watch_write
448    $self->watch_write(0);
449}
450
451sub restart_stream {
452    my DJabberd::Connection $self = shift;
453
454    $self->{in_stream} = 0;
455
456    # to be safe, we just discard the parser, as they might've sent,
457    # say, "<starttls/><attack", knowing the next user will get that
458    # parser with "<attack" open and get a parser error and be
459    # disconnected.
460    $self->discard_parser;
461}
462
463
464# this is a hack to get everything we print
465# this is a slow down now, will fix later but
466# eval is being annoying
467sub write {
468    my $self = shift;
469    if (XMLDEBUG) {
470        my $time = Time::HiRes::time;
471        no warnings;
472        my $data = $_[0];
473        $data = $$data if (ref($data) eq 'SCALAR');
474        $LOGMAP{$self}->print("$time\t> $data\n") if $LOGMAP{$self} &&  ref($data) ne 'CODE' ;
475    }
476    $self->SUPER::write(@_);
477}
478
479
480# DJabberd::Connection
481sub event_read {
482    my DJabberd::Connection $self = shift;
483
484    # for async SSL:  if a session renegotation is in progress,
485    # our previous write wants us to become readable first.
486    # we then go back into the write path (by flushing the write
487    # buffer) and it then does a read on this socket.
488    if (my $ar = $self->{write_when_readable}) {
489        $self->{write_when_readable} = 0;
490        $self->watch_read($ar->[0]);  # restore previous readability state
491        $self->watch_write(1);
492        return;
493    }
494
495    my $bref;
496    if (my $ssl = $self->{ssl}) {
497        my $data = Net::SSLeay::read($ssl);
498
499        my $errs = Net::SSLeay::print_errs('SSL_read');
500        if ($errs) {
501            warn "SSL Read error: $errs\n";
502            $self->close;
503            return;
504        }
505
506        # Net::SSLeays buffers internally, so if we didn't read anything, it's
507        # in its buffer
508        unless ($data && length $data) {
509            # a few of these in a row implies an EOF.  else it could
510            # just be the underlying socket was readable, but there
511            # wasn't enough of an SSL packet for OpenSSL/etc to return
512            # any unencrypted data back to us.
513            # We call 'actual_error_on_empty_read' to avoid counting
514            # SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE as 'actual' errors
515            my $err = DJabberd::Stanza::StartTLS->actual_error_on_empty_read($ssl);
516            if ($err) {
517                $self->log->warn("SSL Read error: $err (assuming ssl_eof)");
518                $self->close('ssl_eof');
519            }
520            return;
521        }
522        $bref = \$data;
523    } else {
524        # non-ssl mode:
525        $bref = $self->read(20_000);
526    }
527
528    return $self->close unless defined $bref;
529
530    # clients send whitespace between stanzas as keep-alives.  let's just ignore those,
531    # not going through the bother to checkout a parser and all.
532    return if ! $self->{parser} && $$bref !~ /\S/;
533
534    Carp::confess if ($self->{closed});
535
536    if (XMLDEBUG) {
537        my $time = Time::HiRes::time;
538        $LOGMAP{$self}->print("$time\t< $$bref\n");
539    }
540
541    my $p = $self->{parser} || $self->borrow_a_parser;
542    my $len = length $$bref;
543    #$self->log->debug("$self->{id} parsing $len bytes...") unless $len == 1;
544
545    # remove invalid low unicode code points which aren't allowed in XML,
546    # but both iChat and gaim have been observed to send in the wild, often
547    # when copy/pasting from bizarre sources.  this probably isn't compliant,
548    # and there's a speed hit, so only regexp them out in quirks mode:
549    if ($self->{vhost} && $self->{vhost}{quirksmode}) {
550        $$bref =~ s/&\#([\da-f]{0,8});/DJabberd::Util::numeric_entity_clean($1)/ieg;
551    }
552
553    eval {
554        $p->parse_chunk_scalarref($bref);
555    };
556
557    if ($@) {
558        # FIXME: give them stream error before closing them,
559        # wait until they get the stream error written to them before closing
560        $self->discard_parser;
561        $self->log->error("$self->{id} disconnected $self because: $@");
562        $self->log->warn("$self->{id} parsing *****\n$$bref\n*******\n\n\n");
563        $self->close;
564        return;
565    }
566
567    # if we still have a handler and haven't already closed down (cleanly),
568    # then let's consider giving our xml parser/sax pair back, if we're at
569    # a good breaking point.
570    if ((my $handler = $self->handler) && ! $self->{closed}) {
571        my $depth = $handler->depth;
572        if ($depth == 0 && $$bref =~ m!>\s*$!) {
573            # if no errors and not inside a stanza, return our parser to save memory
574            $self->return_parser;
575        }
576    }
577}
578
579sub on_stream_start {
580    my DJabberd::Connection $self = shift;
581    my $ss = shift;
582
583    die "on_stream_start not defined for $self";
584}
585
586# when we're the client of a stream (we're talking first)
587sub start_init_stream {
588    my DJabberd::Connection  $self = shift;
589    my %opts = @_;
590    my $extra_attr = delete $opts{'extra_attr'} || "";
591    my $to         = delete $opts{'to'} || Carp::croak("need 'to' domain");
592    my $xmlns      = delete $opts{'xmlns'} || "jabber:server";
593    die if %opts;
594
595    # {=init-version-is-max} -- we must announce the highest version we support
596    my $our_version = $self->server->spec_version;
597    my $ver_attr    = $our_version->as_attr_string;
598
599    # by default we send the optional to='' attribute in our stream, but we have support for
600    # disabling it for our test suite.
601    $to = "to='$to'";
602    $to = "" if $DJabberd::_T_NO_TO_IN_DIALBACKVERIFY_STREAM;
603
604    # {=xml-lang}
605    my $xml = qq{<?xml version="1.0" encoding="UTF-8"?><stream:stream $to xmlns:stream='http://etherx.jabber.org/streams' xmlns='}.exml($xmlns).qq{' xml:lang='en' $extra_attr $ver_attr>};
606    $self->log_outgoing_data($xml);
607    $self->write($xml);
608}
609
610# sending a stream when we're the server (replier) of the stream. a client already
611# started one with us (the $ss object)
612sub start_stream_back {
613    my DJabberd::Connection  $self = shift;
614    my DJabberd::StreamStart $ss   = shift;
615
616    # bind us to a vhost now.
617    my $to_host     = $ss->to;
618    $self->set_to_host($to_host) if $to_host;
619
620    # Spec rfc3920 (dialback section) says: Note: The 'to' and 'from'
621    # attributes are OPTIONAL on the root stream element.  (during
622    # dialback)
623    if ($to_host || ! $ss->announced_dialback) {
624        my $exist_vhost = $self->vhost;
625        my $vhost       = $self->server->lookup_vhost($to_host);
626
627        return $self->close_no_vhost($to_host)
628            unless ($vhost);
629
630        # if they previously had a stream open, it shouldn't change (after SASL/SSL)
631        if ($exist_vhost && $exist_vhost != $vhost) {
632            $self->log->info("Vhost changed for connection, disconnecting.");
633            $self->close;
634            return;
635        }
636
637        # set_vhost returns 0 to signal that this connection won't
638        # accept this vhost.  and by then it probably closed the stream
639        # with an error, so we just stop processing if we can't set it.
640        return unless $self->set_vhost($vhost);
641    }
642
643    my %opts = @_;
644    my $ns         = delete $opts{'namespace'} or
645        die "No default namespace"; # {=stream-def-namespace}
646
647    my $extra_attr    = delete $opts{'extra_attr'} || "";
648    my $features_body = delete $opts{'features'} || "";
649    die if %opts;
650
651    my $features = "";
652    if ($ss->version->supports_features) {
653        # unless we're already in SSL mode, advertise it as a feature...
654        # {=must-send-features-on-1.0}
655        if (!$self->{ssl}
656            && $self->server->ssl_cert_file
657            && !$self->isa("DJabberd::Connection::ServerIn")) {
658            $features_body .= "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />";
659        }
660        if (my $vh = $self->vhost) {
661            $vh->hook_chain_fast("SendFeatures",
662                                  [ $self ],
663                                  {
664                                      stanza => sub {
665                                        my ($self, $xml_string) = @_;
666                                        $features_body .= $xml_string;
667                                      },
668                                  }
669                                  );
670        }
671        $features = qq{<stream:features>$features_body</stream:features>};
672    }
673
674    # The receiving entity MUST set the value of the 'version'
675    # attribute on the response stream header to either the value
676    # supplied by the initiating entity or the highest version number
677    # supported by the receiving entity, whichever is lower.
678    # {=response-version-is-min}
679    my $our_version = $self->server->spec_version;
680    my $min_version = $ss->version->min($our_version);
681    $self->set_version($min_version);
682    my $ver_attr    = $min_version->as_attr_string;
683
684    my $id = $self->stream_id;
685
686    # only include a from='hostname' attribute if we know our vhost.
687    # if they didn't send one to set us, it's probably dialback
688    # and they can cope without knowing ours yet.
689    my $vhost = $self->vhost;
690    my $sname = $vhost ? $vhost->name : "";
691    my $from_attr = $sname ? "from='$sname'" : "";
692
693    # {=streams-namespace}
694    my $back = qq{<?xml version="1.0" encoding="UTF-8"?><stream:stream $from_attr id="$id" $ver_attr $extra_attr xmlns:stream="http://etherx.jabber.org/streams" xmlns="$ns">$features};
695    $self->log_outgoing_data($back);
696    $self->write($back);
697}
698
699sub end_stream {
700    my DJabberd::Connection $self = shift;
701    $self->write("</stream:stream>");
702    $self->write(sub { $self->close; });
703}
704
705sub event_write {
706    my $self = shift;
707    $self->watch_write(0) if $self->write(undef);
708}
709
710# info is optional descriptive text
711sub stream_error {
712    my ($self, $err, $info) = @_;
713    $info ||= "";
714
715    # {=stream-errors}
716    $self->log->warn("$self->{id} stream error '$err': $info");
717    my $infoxml = "";
718    if ($info) {
719        $infoxml = "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>" . exml($info) . "</text>";
720    }
721    unless ($self->{in_stream}) {
722        $self->write(qq{<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='bye' xml:lang='en'>});
723    }
724
725    $self->write("<stream:error><$err xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>$infoxml</stream:error>");
726    # {=error-must-close-stream}
727    $self->close_stream;
728}
729
730sub close_no_vhost {
731    my ($self, $vhost) = @_;
732
733    # FIXME: send proper "vhost not found message"
734    # spec says:
735    #   -- we have to start stream back to them,
736    #   -- then send stream error
737    #   -- stream should have proper 'from' address (but what if we have 2+)
738    #
739    # If the error occurs while the stream is being set up, the
740    # receiving entity MUST still send the opening <stream> tag,
741    # include the <error/> element as a child of the stream
742    # element, send the closing </stream> tag, and terminate the
743    # underlying TCP connection. In this case, if the initiating
744    # entity provides an unknown host in the 'to' attribute (or
745    # provides no 'to' attribute at all), the server SHOULD
746    # provide the server's authoritative hostname in the 'from'
747    # attribute of the stream header sent before termination
748
749    # FIXME: send proper "vhost not found message"
750    # spec says:
751    #   -- we have to start stream back to them,
752    #   -- then send stream error
753    #   -- stream should have proper 'from' address (but what if we have 2+)
754    # If the error occurs while the stream is being set up, the receiving entity MUST still send the opening <stream> tag, include the <error/> element as a child of the stream element, send the closing </stream> tag, and terminate the underlying TCP connection. In this case, if the initiating entity provides an unknown host in the 'to' attribute (or provides no 'to' attribute at all), the server SHOULD provide the server's authoritative hostname in the 'from' attribute of the stream header sent before termination
755
756    $self->log->info("No vhost found for host '$vhost', disconnecting");
757    $self->close;
758    return;
759}
760
761sub close_stream {
762    my ($self, $err) = @_;
763    $self->write("</stream:stream>");
764    $self->write(sub { $self->close; });
765}
766
767sub add_disconnect_handler {
768    my ($self, $callback) = @_;
769    $self->{disconnect_handlers} ||= [];
770    push @{$self->{disconnect_handlers}}, $callback;
771}
772
773sub _run_callback_list {
774    my ($self, $listref, @args) = @_;
775
776    return unless ref $listref eq 'ARRAY';
777
778    foreach my $callback (@$listref) {
779        next unless ref $callback eq 'CODE';
780
781        $callback->($self, @args);
782    }
783
784}
785
786sub close {
787    my DJabberd::Connection $self = shift;
788    return if $self->{closed};
789
790
791    if ($self->{counted_close}++) {
792        $self->log->logcluck("We are about to increment the diconnect counter one time too many, but we didn't");
793    } else {
794        $DJabberd::Stats::counter{disconnect}++;
795    }
796
797    $self->log->debug("DISCONNECT: $self->{id}\n") if $self->{id};
798    $self->_run_callback_list($self->{disconnect_handlers});
799
800    if (my $p = $self->{parser}) {
801        # libxml isn't reentrant apparently, so we can't finish_push
802        # from inside an existint callback.  so schedule immediately,
803        # after event loop.
804        Danga::Socket->AddTimer(0, sub {
805            $p->finish_push;
806            $self->{saxhandler}->cleanup if $self->{saxhandler};
807            $self->{saxhandler} = undef;
808            $self->{parser}     = undef;
809        });
810    }
811    if (XMLDEBUG) {
812        $LOGMAP{$self}->close;
813        delete $LOGMAP{$self};
814    }
815    $self->SUPER::close;
816
817}
818
819# DJabberd::Connection
820sub event_err { my $self = shift; $self->close; }
821sub event_hup { my $self = shift; $self->close; }
822
823
824# Local Variables:
825# mode: perl
826# c-basic-indent: 4
827# indent-tabs-mode: nil
828# End:
829
8301;
831