#!/usr/bin/perl -w use utf8; use strict; use POSIX qw(setsid); use Net::XMPP; use Getopt::Long; use Orangutan::Query; use Orangutan::Date; use Orangutan::Task; use Orangutan::User; use Orangutan::Users; use Orangutan::Config; use Orangutan::Logger; use Orangutan::Monkey; use Orangutan::Monkeys; use Orangutan::Context; use Data::Dumper; my $daemonize = 1; my $config_path = '/etc/orangutan.conf'; our $pid_path = undef; GetOptions( 'config=s' => \$config_path, 'pid=s' => \$pid_path, 'debug' => sub { $daemonize = 0; } ); our $config = new Orangutan::Config( file => $config_path ); our $logger = new Orangutan::Logger( directory => $config->Get('logs', 'directory'), stdout => !$daemonize ); our $users = new Orangutan::Users( directory => $config->Get('users', 'directory') ); our $query = new Orangutan::Query( hostname => $config->Get('db', 'hostname'), database => $config->Get('db', 'database'), username => $config->Get('db', 'username'), password => $config->Get('db', 'password'), timeout => $config->Get('db', 'timeout') ); our $jabber = new Net::XMPP::Client( debuglevel => $config->Get('jabber', 'debug') || 0 ); our $monkeys = new Orangutan::Monkeys; $SIG{'INT'} = \&Interrupt; $SIG{'TERM'} = \&Interrupt; $SIG{'__DIE__'} = \&Catch; $SIG{'__WARN__'} = \&Catch; &CheckPID; if ($daemonize) { &Daemonize; &SavePID; } $config->Init; $config->InitContexts; $config->InitMonkeys; # Set callbacks $jabber->SetCallBacks( presence => \&Presence, message => \&Message ); my %jopts = ( ); if ($config->Get('jabber', 'component')) { $jopts{'componentname'} = $config->Get('jabber', 'component'); } # Connect if ($jabber->Connect( hostname => $config->Get('jabber', 'hostname'), port => $config->Get('jabber', 'port'), tls => $config->Get('jabber', 'tls'), %jopts )) { # Authenticate my @auth = $jabber->AuthSend( username => $config->Get('jabber', 'username'), password => $config->Get('jabber', 'password'), resource => $config->Get('jabber', 'resource') ); if (scalar @auth == 2) { # Register if ($auth[0] eq '401') { $logger->Log("authorization failed: %s", join(' ', @auth)); $logger->Log("trying to register"); my %reghash = $jabber->RegisterRequest; if (defined($reghash{'fields'}->{'username'})) { $reghash{'fields'}->{'username'} = $config->Get('jabber', 'username'); } if (defined($reghash{'fields'}->{'password'})) { $reghash{'fields'}->{'password'} = $config->Get('jabber', 'password'); } if (defined($reghash{'fields'}->{'name'})) { $reghash{'fields'}->{'name'} = $config->Get('jabber', 'name'); } if (defined($reghash{'fields'}->{'email'})) { $reghash{'fields'}->{'email'} = $config->Get('jabber', 'username').'@'.$config->Get('jabber', 'hostname'); } @auth = $jabber->RegisterSend(%{$reghash{'fields'}}); } if ($auth[0] eq 'ok') { $logger->Log("logged in"); # Generate vCard request my $iq = new Net::XMPP::IQ; $iq->SetType('get'); $iq->SetFrom(sprintf('%s@%s/%s', $config->Get('jabber', 'username'), $config->Get('jabber', 'hostname'), $config->Get('jabber', 'resource'))); $iq->InsertRawXML(""); # Send vCard request my $vcard = $jabber->SendAndReceiveWithID($iq, 10); if ($vcard) { if ($vcard->GetError) { $logger->Log("vCard request returned: %s", $vcard->GetError); } else { if (!defined($vcard->GetQuery) || !defined($vcard->GetQuery->GetTree->children)) { # Add vCard information my $xml = ""; $xml .= 'Orangutan'; $xml .= ''; $xml .= sprintf('%s@%s', $config->Get('jabber', 'username'), $config->Get('jabber', 'hostname')); $xml .= ''; $xml .= 'Time Tracking Assistant'; $xml .= ''; my $req = new Net::XMPP::IQ; $req->SetType('set'); $req->InsertRawXML($xml); $jabber->SendWithID($req); $logger->Log("updated vCard"); } } } else { $logger->Log("failed to retrieve vCard"); } my %roster = $jabber->RosterGet; $users->Roster(\%roster); $jabber->PresenceSend; $users->Init; $monkeys->Init; my $timeout = 60; while (1) { my $signal = $jabber->Process($timeout); if (defined($signal)) { my $nextrun = $users->RunSchedule; $timeout = $users->RunContexts; if (defined($nextrun) && (!defined($timeout) || ($nextrun < $timeout))) { $timeout = $nextrun; } } else { $logger->Log("connection died"); $jabber->Disconnect; $users->Shutdown; $users->Save; # Try reconnecting $logger->Log("reconnecting"); while (1) { sleep(10); $jabber = new Net::XMPP::Client; $jabber->SetCallBacks( presence => \&Presence, message => \&Message ); if ($jabber->Connect( hostname => $config->Get('jabber', 'hostname'), port => $config->Get('jabber', 'port') )) { @auth = $jabber->AuthSend( username => $config->Get('jabber', 'username'), password => $config->Get('jabber', 'password'), resource => $config->Get('jabber', 'resource') ); if ((scalar @auth == 2) && ($auth[0] eq 'ok')) { $logger->Log("logged in"); %roster = $jabber->RosterGet; $users->Roster(\%roster); $jabber->PresenceSend; $timeout = 1; last; } $jabber->Disconnect; } } } } } else { $logger->Log("authorization failed: %s", join(' ', @auth)); } } else { $logger->Log("authorization failed"); } $jabber->Disconnect; } else { $logger->Log("could not connect"); } sub Daemonize { my $pid = fork; if (!defined($pid)) { $logger->Log("could not fork"); } elsif ($pid > 0) { exit(0); } setsid; open(STDIN, '/dev/null'); open(STDOUT, '>', '/dev/null'); open(STDERR, '>', '/dev/null'); } sub CheckPID { if (defined($pid_path) && (-f $pid_path)) { $logger->Log("already running or unclean shutdown"); exit(0); } } sub SavePID { if (defined($pid_path)) { if (open(PID, '>', $pid_path)) { print(PID $$); close(PID); } else { $logger->Log("failed to save pid to: %s", $pid_path); $pid_path = undef; } } } sub Presence($$) { my ($sid, $presence) = @_; my $from = $presence->GetFrom('jid'); my $type = $presence->GetType; my $show = $presence->GetShow; if ($type eq 'subscribe') { $logger->Log("got subscription request from %s", $from->GetJID('base')); my ($id, $login) = $query->UserExists($from->GetJID('base')); if ($id) { my $user = $users->Add($from->GetJID('base')); $user->SetResource($from->GetResource); $user->SetUserID($id); $user->SetLogin($login); $user->Set(USER_UNCONFIGURED); $user->Init; $user->FireEvent('subscription', 'subscribe'); $jabber->Subscription( type => 'subscribed', to => $from->GetJID('full') ); $jabber->Subscription( type => 'subscribe', to => $from->GetJID('full') ); my $format = Orangutan::Context::Random( 'User %s has just subscribed to me...', 'User %s added me to their roster...', "We've got new user: %s!", 'Just met %s... :)' ); $users->TellCreator(sprintf($format, $from->GetJID('base'))); $logger->Log("added user %s", $from->GetJID('base')); } else { my $body; if ($config->Get('redmine', 'same_login')) { $body = Orangutan::Context::Random( 'You need to use the same login for Jabber as you do for Redmine.', 'I could not find account with the same username in Redmine.', 'Your usernames in Jabber and Redmine must be the same.', "Seems you don't have an account in Redmine." ); } else { $body = Orangutan::Context::Random( 'I could not find any account in Redmine with your Jabber ID.', 'You need to put your Jabber ID into your account in Redmine.', 'Failed to find Redmine account with your Jabber ID.' ); } $jabber->MessageSend( to => $from->GetJID('full'), type => 'chat', body => $body ); $jabber->Subscription( type => 'unsubscribed', to => $from->GetJID('full') ); $logger->Log("ignoring request from %s", $from->GetJID('base')); } } elsif ($type eq 'unsubscribe') { $logger->Log("got unsubscription request from %s", $from->GetJID('base')); my $user = $users->GetUser($from->GetJID('base')); if (defined($user)) { $user->FireEvent('subscription', 'unsubscribe'); $users->Delete($from->GetJID('base')); $jabber->Subscription( type => 'unsubscribed', to => $from->GetJID('full') ); $jabber->Subscription( type => 'unsubscribe', to => $from->GetJID('full') ); my $format = Orangutan::Context::Random( 'User %s removed me from their roster... :(', 'User %s unsubscribed... :(', 'User %s has gone... :(' ); $users->TellCreator(sprintf($format, $from->GetJID('base'))); $logger->Log("removed user %s", $from->GetJID('base')); } } elsif ($type eq 'unavailable') { my $user = $users->GetUser($from->GetJID('base')); if (defined($user)) { $user->SetResource($from->GetResource); if ($user->SetStatus) { $logger->LogUser($user, $presence); } } } elsif ($type eq '') { my $user = $users->GetUser($from->GetJID('base')); if (defined($user)) { $user->SetResource($from->GetResource); if ($user->SetStatus($show)) { $logger->LogUser($user, $presence); } } } } sub Message($$) { my ($sid, $message) = @_; my $from = $message->GetFrom('jid'); my $body = $message->GetBody; if ($body ne '') { my $user = $users->GetUser($from->GetJID('base')); if (defined($user)) { my $orig = $body; $user->SetResource($from->GetResource); $logger->LogUser($user, $body); $body =~ s!\n! !sg; $body =~ s!^\s+!!; $body =~ s!\s+$!!; $body =~ s!\s{2,}! !g; $user->HandleMessage; $user->Filter(\$body); $user->Match($body, $orig); if ($user->ShouldSave) { $user->Save; } } else { my $monkey = $monkeys->Exists($from->GetJID('base')); if ($monkey) { $logger->LogMonkey($monkey, $body); $monkeys->Handle($from->GetJID('base'), $body); } else { $logger->Log("message from unknown user: %s", $from->GetJID('base')); } } } } sub Interrupt { if ($jabber) { $logger->Log("terminated"); $jabber->Disconnect; $users->Shutdown; $users->Save; } if (defined($pid_path)) { unlink($pid_path); } exit(0); } sub Catch { my $message = shift; $message =~ s!\n$!!g; $message =~ s!\\!\\\\!g; $message =~ s!\n!\\n!g; $logger->Log($message); } exit(0);