#!/usr/bin/perl # # jolem # # A Jabber Golem (human-like thing) # accepts messages through jabber and performs # actions like logging, or performing other commands # ##### INCLUDES ##### use strict; use POSIX qw(strftime); use Getopt::Long qw(:config no_ignore_case bundling); use Proc::Daemon; use XML::Simple; use Data::Dumper; use Net::Jabber qw(Client); use DBI; require 'smssplit.pm'; ##### CONFIG ##### our $progname = "jolem"; our $version = "0.7"; our $configfile = "jolem.xml"; our $config; our $maxreconnect = 20; our $fork = 0; our $bot; our $dbh; our $getopt_result; our $needhelp; our $debug = 0; ## Forward Declairations sub Stop(); sub nowdate(); sub LogIt($); sub Debug($$); sub ReadConfig(%); sub JabberConnect(); sub MySQLConnect(); sub InMessage($$); sub InPresence($$); sub changestatus($$$$); sub jidcommand($$); sub blog($$$$); sub Usage(); ##### SIGNALS ##### $SIG{HUP} = sub { Debug(1, "Received SIGHUP: $!"); Stop(); }; $SIG{KILL} = sub { Debug(1, "Recieved SIGKILL: $!"); Stop(); }; $SIG{TERM} = sub { Debug(1, "Recieved SIGTERM: $!"); Stop(); }; $SIG{INT} = sub { Debug(1, "Recieved SIGINT: $!"); Stop(); }; ##### MAIN ##### ## handle variables $getopt_result = GetOptions( "h|?|help" => \$needhelp, "d|debug+" => \$debug, "c|config=s" => \$configfile, "f|fork" => \$fork, ); Usage() if (!$getopt_result || $needhelp); Proc::Daemon::Init if ($fork); # read config in ReadConfig(logit => 0); # setup bot for the rest of this program $bot = new Net::Jabber::Client(); # setup what the bot does $bot->SetCallBacks( message => \&InMessage, presence => \&InPresence, ); # end callbacks # connect to jabber and mysql JabberConnect(); MySQLConnect(); # while we're connected, just sit here while(defined($bot->Process())) { } LogIt("ERROR: The connection was killed"); exit(0); ##### SUBROUTINES ##### sub Stop() { LogIt("Stop: Exiting"); $dbh->disconnect() if (defined($dbh)); $bot->Disconnect() if (defined($bot)); exit(0); } # end Stop sub nowdate() { return strftime("%Y.%m.%d %H:%M:%S", localtime); } # end nowdate sub LogIt($) { my $logline = shift(@_); if (defined($config->{logfile})) { open(LOG, ">>$config->{logfile}"); print(LOG nowdate() ." $progname\[$$\] = $logline\n"); close(LOG); } else { print(STDERR nowdate() ." $progname\[$$\] = $logline\n"); } # print to a log or STDERR } # end logit; sub Debug($$) { my $debuglevel = shift(@_); my $logline = @_; if ($debuglevel <= $debug) { if (defined($config->{logfile})) { open(LOG, ">>$config->{logfile}"); print(LOG nowdate() ." $progname\[$$\] = DEBUG$debuglevel = $logline\n"); close(LOG); } else { print(STDERR nowdate() ." $progname\[$$\] = DEBUG$debuglevel = $logline\n"); } # end print to log or STDERR } # end if we're supposed to log } # end Debug; sub ReadConfig(%) { my %arg = @_; my $localconfig; LogIt("ReadConfig: reading in config file '$configfile'") if (defined($arg{logit}) && $arg{logit}); unless (-e $configfile) { LogIt("ReadConfig: ERROR: couldn't find file '$configfile'"); Stop(); } # end if no config file $localconfig = XMLin($configfile); #TODO verify config $config = $localconfig; } # end ReadConfig sub JabberConnect() { # connect to the server my $status = $bot->Connect( hostname => $config->{jabber}{server}, port => $config->{jabber}{port} ); # end connect # if we didn't connect, die if (!(defined($status))) { LogIt("JabberConnect: ERROR: Jabber server is down or connection was not allowed"); exit(0); } # end if not connected # send my info my @result = $bot->AuthSend( username => $config->{jabber}{username}, password => $config->{jabber}{password}, resource => $config->{jabber}{resource} ); # end auth # we we didn't auth, try to create acct if ($result[0] ne "ok") { Debug(1, "JabberConnect: Couldn't authenticate, trying to sign up"); @result = $bot->RegisterSend( to => $config->{jabber}{server}, username => $config->{jabber}{username}, password => $config->{jabber}{password} ); # end register if ($result[0] eq "ok") { my @result = $bot->AuthSend( username => $config->{jabber}{username}, password => $config->{jabber}{password}, resource => $config->{jabber}{resource} ); # end auth } # end if that result was good, try to login again... } # end if we didn't auth if ($result[0] ne "ok") { LogIt("JabberConnect: ERROR: Authorization failed: $result[0] - $result[1]"); exit(0); } # end if we still didn't auth LogIt("JabberConnect: Logged in to $config->{jabber}{server}:$config->{jabber}{port}"); Debug(1, "JabberConnect: Getting Roster to tell server to send presence info"); $bot->RosterGet(); Debug(1, "JabberConnect: Sending presence to tell world that we are logged in"); $bot->PresenceSend(); Debug(1, "JabberConnect: Setting info"); $bot->Info( name => $config->{jabber}{username}, version => $version, ); # end set info } # end JabberConnect sub MySQLConnect() { $dbh = DBI->connect("DBI:mysql:database=$config->{mysql}{database};host=$config->{mysql}{hostname}", $config->{mysql}{username}, $config->{mysql}{password}); if (!$dbh) { LogIt("MySQLConnect: ERROR: couldn't connect to database"); Stop(); } # end if we didn't connect Debug(1, "MySQLConnect: Connected to database $config->{mysql}{database} as $config->{mysql}{username}\@$config->{mysql}{server}"); } # end MySQLConnect; sub InMessage($$) { my $sid = shift; my $message = shift; my $fromJID = $message->GetFrom("jid"); my $fromuser = $fromJID->GetUserID(); my $resource = $fromJID->GetResource(); my $type = $message->GetType(); $type = "normal" if ($type eq ""); my $subject = $message->GetSubject(); my $body = $message->GetBody(); my $thread = $message->GetThread(); my $reply_subject = ""; my $reply_body = ""; # setup subject if ($subject eq "") { $reply_subject = "auto-reply"; } else { $reply_subject = $subject; $reply_subject =~ s/^re:?\s*//i; $reply_subject = "Re: $reply_subject"; } # end if subject LogIt("InMessage: received '$type' message from '$fromuser';"); if ($type eq "normal" || $type eq "chat") { # user needs help if ($body =~ /^help\s*(.*)/i) { LogIt("InMessage: $fromuser needs help"); jidcommand($fromJID->GetJID(), "help"); $reply_body .= "== $config->{jabber}{username} help ==\n"; # help on logging if ($1 =~ /log/i) { $reply_body .= "log:\n"; $reply_body .= "subject: :\n"; $reply_body .= "body: \n"; # help on ping } elsif ($1 =~ /ping/i) { $reply_body .= "ping (hostname | ip)\n"; # help on smssend } elsif ($1 =~ /smssend/i) { $reply_body .= "smssend:\n"; $reply_body .= "subject: \n"; $reply_body .= "body:\n"; $reply_body .= "smssend: \n"; # help on traceroute } elsif ($1 =~ /trace(route|rt|)/i) { $reply_body .= "trace (hostname | ip)\n"; $reply_body .= "tracert (hostname | ip)\n"; $reply_body .= "traceroute (hostname | ip)\n"; # basic help } else { $reply_body .= "help [function]\n"; $reply_body .= "\n"; $reply_body .= "-- functions --\n"; $reply_body .= "log\n"; $reply_body .= "ping\n"; $reply_body .= "smssend\n"; $reply_body .= "trace(route|rt|)\n"; } # end if more specific help } elsif ($body =~ /^ping\s+(.*)/i) { my $host = $1; LogIt("InMessage: $fromuser wants to ping $host"); jidcommand($fromJID->GetJID(), "ping"); if ($host =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ || $host =~ /[\w\-\.]+/) { $reply_body = `/bin/ping -c 5 $host 2> /dev/null`; } else { $reply_body = "$host unreachable"; } # end if host is valid or not if ($reply_body eq "") { $reply_body = "can't ping '$host'"; } # end if no result, say so } elsif ($body =~ /^trace(route|rt|)\s+(.*)/i) { my $host = $2; LogIt("InMessage: $fromuser wants to traceroute to $host"); jidcommand($fromJID->GetJID(), "traceroute"); if ($host =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ || $host =~ /[\w\-\.]+/) { my $traceroute = `/usr/sbin/traceroute $host 2> /dev/null`; $traceroute =~ s/^\s*//; $reply_body = "\n"; $reply_body .= $traceroute; } else { $reply_body = "$host unreachable"; } # end if host is valid or not if ($reply_body eq "") { $reply_body = "can't traceroute to $host"; } # end if no result, say so } elsif ($body =~ /^smssend:\s*(\S+)\s+(.*)$/i) { my $phone_to = $1; my $pager_body = $2; LogIt("InMessage: $fromuser wants to send a sms messge to $phone_to"); jidcommand($fromJID->GetJID(), "smssend"); send_message($phone_to, $fromJID->GetJID(), $subject, $pager_body); $reply_body = "sent message to $phone_to"; } elsif ($body eq "pr0n") { LogIt("InMessage: $fromuser is naugty"); jidcommand($fromJID->GetJID(), "pr0n"); $reply_body = "( . )( . )"; } else { my $category = "general"; if ($subject =~ /^(.*?):(.*)$/) { $category = $1; $subject = $2; } # end if a catagory is specified, use it LogIt("InMessage: $fromuser needs stuff logged"); jidcommand($fromJID->GetJID(), "blog"); blog($fromJID->GetJID(), $category, $subject, $body); $reply_body = "Your message has been logged"; } # end if message body # send our reply $bot->MessageSend( to => $fromJID->GetJID(), subject => $reply_subject, body => $reply_body, type => $type, thread => $thread ); # end MessageSend } # end if message type } # end InMessage sub InPresence($$) { my ($sid, $presence) = @_; my $fromJID = $presence->GetFrom("jid"); my $fromuser = $fromJID->GetUserID(); my $type = $presence->GetType(); $type = "available" if ($presence->GetType() eq ""); my $appearance = $presence->GetShow(); $appearance = "online" if ($type eq "available" && $appearance eq ""); $appearance = "offline" if ($type eq "unavailable" && $appearance eq ""); my $status = $presence->GetStatus(); # handle presences LogIt("InPresence: received '$type' presence from '$fromuser'"); if ($type eq "available" || $type eq "probe") { LogIt("InPresence: '$fromuser' is '$type' - howdy!"); changestatus($fromJID->GetJID(), $type, $appearance, $status); if (!defined($bot->PresenceDBQuery($fromJID)) || $bot->PresenceDBQuery($fromJID)->GetType() eq "unavailable") { $bot->PresenceSend($presence->Reply(type => "available")); } # end if we've never seen this user before, or they were unavailable } elsif ($type eq "unavailable") { LogIt("InPresence: '$fromuser' is '$type' - bye!"); changestatus($fromJID->GetJID(), $type, $appearance, $status); } elsif ($type eq "subscribe") { LogIt("InPresence: '$fromuser' wants to '$type'; I'll show you mine if you show me yours"); $bot->Subscription( to => $fromJID->GetJID(), type => "subscribed", ); # end tell them they're subscribed $bot->Subscription( to => $fromJID->GetJID(), type => "subscribe", ); # end subscribe to them } elsif ($type eq "subscribed") { LogIt("InPresence: '$fromuser' '$type'; woo-hoo!"); $bot->RosterAdd(jid => $fromJID->GetJID()); } elsif ($type eq "unsubscribe") { LogIt("InPresence: '$fromuser' wants to '$type'; I guess they're tired of me"); changestatus($fromJID->GetJID(), "unknown", "", ""); $bot->Subscription( to => $fromJID->GetJID(), type => "unsubscribed", ); # end you're unsubscribed $bot->Subscription( to => $fromJID->GetJID(), type => "unsubscribe", ); # end we don't want to see you either } elsif ($type eq "unsubscribed") { LogIt("InPresence: '$fromuser' '$type'; no more fun"); changestatus($fromJID->GetJID(), "unknown", "", ""); $bot->RosterRemove(jid => $fromJID->GetJID()); } # end if presence type # keep track of preferences $bot->PresenceDBParse($presence); } # end InPresence sub changestatus($$$$) { my $jid = shift(@_); my $type = shift(@_); my $appearance = shift(@_); my $status = shift(@_); LogIt("changestatus: '$jid' to '$status'"); my $jidexists = $dbh->prepare("SELECT 1 FROM users WHERE jid='$jid'"); $jidexists->execute(); my $row = $jidexists->fetchrow_hashref(); my $return; if ($row->{'1'} eq "1") { $return = $dbh->do("UPDATE users SET updated = now(), type = '$type', appearance = '$appearance', status = ".$dbh->quote($status)." WHERE jid = '$jid'"); } else { $return = $dbh->do("INSERT INTO users SET jid = '$jid', created = now(), updated = now(), type = '$type', appearance = '$appearance', status = ".$dbh->quote($status)); } # end if user exists or not LogIt("changestatus: ERROR: $dbh->{'mysql_error'}") if (!$return); } # end changestatus sub jidcommand($$) { my ($jid, $command) = @_; LogIt("jidcommand: $jid command $command"); my $jidexists = $dbh->prepare("SELECT count FROM commands WHERE jid='$jid' AND command='$command'"); $jidexists->execute(); my $row = $jidexists->fetchrow_hashref(); my $return; if ($jidexists->rows > 0 && $row->{'count'} ne "") { $return = $dbh->do("UPDATE commands SET updated = now(), count = ". ($row->{'count'}+1) ." WHERE jid='$jid' AND command='$command'"); } else { $return = $dbh->do("INSERT INTO commands (jid, updated, command, count) VALUES ('$jid', now(), '$command', '1')"); } # end if command exists or not LogIt("jidcommand: ERROR: $dbh->{'mysql_error'}") if (!$return); } # end jidcommand sub blog($$$$) { my ($jid, $category, $subject, $body) = @_; LogIt("blog: $jid blog"); my $return = $dbh->do("INSERT INTO blogs (jid, date, category, subject, body) VALUES ('$jid', now(), '$category', ".$dbh->quote($subject).", ".$dbh->quote($body).")"); LogIt("blog: ERROR: $dbh->{'mysql_error'}") if (!$return); } # end blog sub Usage() { print <] [-h|-?|--help] [-f|--fork] [-d|--debug] END exit(0); } # end Usage;