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