diff --git a/nagibot.pl b/nagibot.pl index 75970fe..62c71cb 100755 --- a/nagibot.pl +++ b/nagibot.pl @@ -59,6 +59,7 @@ nagios_msg_fifo => '/usr/local/nagios/var/rw/nagibot.fifo', nagios_status_log_file => '/usr/local/nagios/var/status.dat', htmlify_msg => 0, + max_retry_interval => 600, ); %bot_command = ( @@ -164,164 +165,189 @@ # Add namespace for showing idle time AnyEvent::XMPP::Namespaces::set_xmpp_ns_alias('last', 'jabber:iq:last'); -CON: -$event = AnyEvent->condvar; +my $endtime = time() + $config->{retry_timeout} if $config->{retry_timeout}; +my $tts = 2; -$connection = AnyEvent::XMPP::IM::Connection->new ( - jid => $jid, - password => $config->{'password'}, - resource => $config->{'bot_resource'}, - initial_presence => 0, -); - -$connection->reg_cb ( - # On Connect - session_ready => sub { - my ($con) = @_; - print 'Connected as ' . $con->jid . "\n" if $verbose; - - print "Setting presence ...\n" if $verbose > 1; - $con->send_presence ( - undef, - undef, - show => $config->{'bot_status'}, - status => $config->{'bot_status_text'}, - priority => $config->{'bot_status_priority'} - ); - - print "Joining rooms (if any) ...\n" if $verbose > 1; - $con->add_extension ($disco = AnyEvent::XMPP::Ext::Disco->new); - $con->add_extension ( - $muc = AnyEvent::XMPP::Ext::MUC->new (disco => $disco, connection => $con) - ); - for my $room (keys %$rooms) { - $muc->join_room ( - $con, - $room, - $rooms->{$room}, - timeout=>20, - create_instant => 0, - history => {stanzas => 0} - ); - } +until ($terminating) { + $event = AnyEvent->condvar; - print "Register room message handler ...\n" if $verbose > 1; - $muc->reg_cb ( - message => sub { - my ($con, $room, $msg, $is_echo) = @_; - - return if $is_echo; + $connection = AnyEvent::XMPP::IM::Connection->new ( + jid => $jid, + password => $config->{'password'}, + resource => $config->{'bot_resource'}, + initial_presence => 0, + ); - print "Message (" . $room->jid .") from " . $msg->from . ": " . $msg->any_body . "\n" if $verbose > 2; + $connection->reg_cb ( + # On Connect + session_ready => sub { + my ($con) = @_; + print 'Connected as ' . $con->jid . "\n" if $verbose; + + print "Setting presence ...\n" if $verbose > 1; + $con->send_presence ( + undef, + undef, + show => $config->{'bot_status'}, + status => $config->{'bot_status_text'}, + priority => $config->{'bot_status_priority'} + ); - my $my_name = 'nagibot'; - if (my $me = $room->get_me) { - $my_name = lc $me->nick; + print "Joining rooms (if any) ...\n" if $verbose > 1; + $con->add_extension ($disco = AnyEvent::XMPP::Ext::Disco->new); + $con->add_extension ( + $muc = AnyEvent::XMPP::Ext::MUC->new ( + disco => $disco, + connection => $con + ) + ); + + for my $room (keys %$rooms) { + $muc->join_room ( + $con, + $room, + $rooms->{$room}, + timeout => 20, + create_instant => 0, + history => {stanzas => 0} + ); + } + + print "Register room message handler ...\n" if $verbose > 1; + $muc->reg_cb ( + message => sub { + my ($con, $room, $msg, $is_echo) = @_; + + return if $is_echo; + + print "Message (" . $room->jid .") from " . $msg->from . ": " . $msg->any_body . "\n" if $verbose > 2; + + my $my_name = 'nagibot'; + if (my $me = $room->get_me) { + $my_name = lc $me->nick; + } + my $cmd = $1 if $msg->any_body =~ /^(?:\@$my_name|$my_name:)\s+(.*)$/i; + return unless $cmd; + &SetIdlePresence(0); + &SendRoomMessage($room, &ProcessBotCommand ($msg->from, $cmd)); + &StartTimer($config->{'bot_show_idle_after'}); } - my $cmd = $1 if $msg->any_body =~ /^(?:\@$my_name|$my_name:)\s+(.*)$/i; - return unless $cmd; - &SetIdlePresence(0); - &SendRoomMessage($room, &ProcessBotCommand ($msg->from, $cmd)); - &StartTimer($config->{'bot_show_idle_after'}); - } - - ); + ); - &StartTimer($config->{'bot_show_idle_after'}); - }, + &StartTimer($config->{'bot_show_idle_after'}); + + # Reset reconnection parameters + $endtime = time() + $config->{retry_timeout} if $config->{retry_timeout}; + $tts = 2; + }, - # On Receive - message => sub { - my ($con, $msg) = @_; + # On Receive + message => sub { + my ($con, $msg) = @_; - return unless $msg->any_body; + return unless $msg->any_body; - my $from = $con->get_roster()->get_contact($msg->from); + my $from = $con->get_roster()->get_contact($msg->from); - print "Message from " , $msg->from , "(", $from->is_on_roster() ? "" : "NOT " , "on roster): " , $msg->any_body , "\n" if $verbose > 2; - return unless $from->is_on_roster; + print "Message from " , $msg->from , "(", $from->is_on_roster() ? "" : "NOT " , "on roster): " , $msg->any_body , "\n" if $verbose > 2; + return unless $from->is_on_roster; - &SetIdlePresence(0); - my $reply = $msg->make_reply; - $reply->add_body (&ProcessBotCommand($msg->from, $msg->any_body) => undef); - $reply->send($con); - &StartTimer($config->{'bot_show_idle_after'}); - }, + &SetIdlePresence(0); + my $reply = $msg->make_reply; + $reply->add_body (&ProcessBotCommand($msg->from, $msg->any_body) => undef); + $reply->send($con); + &StartTimer($config->{'bot_show_idle_after'}); + }, - # On Error - error => sub { - my ($con, $error) = @_; + # On Error + error => sub { + my ($con, $error) = @_; - warn "Error: " . $error->string . "\n"; - }, - # On connect - connect => sub { - my ($con, $h, $p) = @_; - print "connected to $h:$p\n" if $verbose > 1; - }, + warn "Error: " . $error->string . "\n"; + }, + # On connect + connect => sub { + my ($con, $h, $p) = @_; + print "connected to $h:$p\n" if $verbose > 1; + }, - # On Disconnect - disconnect => sub { - my ($con, $h, $p, $reason) = @_; - print "Disconnected from $h:$p: $reason\n"; - $event->send; - }, + # On Disconnect + disconnect => sub { + my ($con, $h, $p, $reason) = @_; + if (defined($h) && defined($p)) { + $reason = "Disconnected from $h:$p: $reason"; + } + print "$reason\n"; + $event->send; + }, -); + ); -print "Trying to connect ...\n" if $verbose; -$connection->connect (); + print "Trying to connect ...\n" if $verbose; + $connection->connect (); -print "Opening fifo for reading ...\n" if $verbose; -open (FIFO, "+< " . $config->{'nagios_msg_fifo'}) or die "Cannot open fifo " . $config->{'nagios_msg_fifo'} .": $!"; -binmode FIFO, ':encoding(' . $config->{'incoming_charset'} . ')'; + print "Opening fifo for reading ...\n" if $verbose; + open (FIFO, "+< " . $config->{'nagios_msg_fifo'}) or die "Cannot open fifo " . $config->{'nagios_msg_fifo'} .": $!"; + binmode FIFO, ':encoding(' . $config->{'incoming_charset'} . ')'; + $io_event = AnyEvent->io ( + fh => \*FIFO, + poll => 'r', + cb => sub { + return unless $connection->is_connected(); -$io_event = AnyEvent->io ( - fh => \*FIFO, - poll => 'r', - cb => sub { - return unless $connection->is_connected(); + chomp (my $input = ); + print "read: $input\n" if $verbose > 2; - chomp (my $input = ); - print "read: $input\n" if $verbose > 2; + # Reset idle presence + &SetIdlePresence(0); - # Reset idle presence - &SetIdlePresence(0); + # Send message to all rooms + foreach my $room (keys %$rooms) { + my $r = $muc->get_room($connection, $room); + unless ($r) { + print "ERROR: Lost connection to the room $room"; + # TODO: Implement rejoin + return; + } + my $msg = $r->make_message (body => $input); + &HtmlifyMsg($msg, $input) if $config->{'htmlify_msg'}; + $msg->send (); + } - # Send message to all rooms - foreach my $room (keys %$rooms) { - my $r = $muc->get_room($connection, $room); - unless ($r) { - print "ERROR: Lost connection to the room $room"; - # TODO: Implement rejoin - return; + # send message to all users + foreach my $ujid (@$jids) { + my $msg = AnyEvent::XMPP::IM::Message->new (to => $ujid, body => $input, type => 'chat'); + &HtmlifyMsg($msg, $input) if $config->{'htmlify_msg'}; + $msg->send ($connection); } - my $msg = $r->make_message (body => $input); - &HtmlifyMsg($msg, $input) if $config->{'htmlify_msg'}; - $msg->send (); - } - # send message to all users - foreach my $ujid (@$jids) { - my $msg = AnyEvent::XMPP::IM::Message->new (to => $ujid, body => $input, type => 'chat'); - &HtmlifyMsg($msg, $input) if $config->{'htmlify_msg'}; - $msg->send ($connection); + # Restart idle timer + &StartTimer($config->{'bot_show_idle_after'}); } + ); - # Restart idle timer - &StartTimer($config->{'bot_show_idle_after'}); - } -); - -$event->wait; + $event->wait; -undef $io_event; -undef $timer_event; -undef $connection; + undef $io_event; + undef $timer_event; + undef $connection; -# reconnect ? -goto CON unless ($terminating); + last if $terminating; + if (defined($endtime) && time() > $endtime) { + print "Disconnected. Giving up.\n"; + } + + my $t = int(rand(0xffffffff)) % ($tts - 1) + 1; + print "Disconnected. Sleeping for $t seconds before retry...\n" if $verbose; + sleep($t); + if ($tts < $config->{max_retry_interval}) { + $tts <<= 1; + if ($tts < 0 || $tts > $config->{max_retry_interval}) { + $tts = $config->{max_retry_interval}; + } + } +} print "Quitting.\n" if $verbose; @@ -882,6 +908,26 @@ =head1 CONFIGURATION FILE Name of the pid file if the bot forks into background. (defaults to /var/run/nagibot/.pid) +=item B + +When B gets disconnected from the XMPP server, it tries to +reconnect. It can happen that the attempt to reconnect will fail too +(e.g. if the remote server is down or heavy loaded). In order to limit +the use of system resources and minimize the eventual impact on the XMPP +server, the interval between two subsequent failed reconnection attempts +is selected as a random number between 1 and certain upper limit, which +grows exponentially. The initial value of that upper limit is 2 seconds. +It gets doubled after each subsequent failure. The B +parameter sets the maximum value for that limit, after which it is reset +to 2 seconds. The default value is 600 seconds. + +=item B + +By default, B will retry connections to the XMPP server forever +(see the description under B). Setting B +instructs it to give up after it is unable to connect to the server for +that many seconds. + =back =head1 SAMPLE CONFIGURATION