package ASM::Log; no autovivification; use warnings; use strict; use ASM::Util; use POSIX qw(strftime); use Data::UUID; no if $] >= 5.017011, warnings => 'experimental::smartmatch'; sub new { my $module = shift; my ($conn) = @_; my $self = {}; $self->{CONFIG} = $::settings->{log}; $self->{backlog} = {}; $self->{CONN} = $conn; $self->{UUID} = Data::UUID->new; bless($self); mkdir($self->{CONFIG}->{dir}); $conn->add_handler('public', sub { logg($self, @_); }, "before"); $conn->add_handler('join', sub { logg($self, @_); }, "before"); $conn->add_handler('part', sub { logg($self, @_); }, "before"); $conn->add_handler('caction', sub { logg($self, @_); }, "before"); $conn->add_handler('nick', sub { logg($self, @_); }, "after"); #allow state tracking to molest this $conn->add_handler('quit', sub { logg($self, @_); }, "after"); #allow state tracking to molest this too $conn->add_handler('kick', sub { logg($self, @_); }, "before"); $conn->add_handler('notice', sub { logg($self, @_); }, "before"); $conn->add_handler('mode', sub { logg($self, @_); }, "before"); $conn->add_handler('topic', sub { logg($self, @_); }, "before"); return $self; } sub actionlog { my ($self, $event, $modedata1, $modedata2) = @_; my ($action, $reason, $channel, $nick, $user, $host, $gecos, $account, $ip, $bynick, $byuser, $byhost, $bygecos, $byaccount); my ($lcnick, $lcbynick); if ($event->{type} eq 'mode') { $action = $modedata1; $nick = $modedata2; $channel = lc $event->{to}->[0]; $bynick = $event->{nick}; $byuser = $event->{user}; $byhost = $event->{host}; } elsif ($event->{type} eq 'quit') { my $quitmsg = $event->{args}->[0]; if ($quitmsg =~ /^Killed \((\S+) \((.*)\)\)$/) { $bynick = $1; $reason = $2 unless ($2 eq ''); return if (($reason // '') =~ /Nickname regained by services/); $action = 'kill'; } elsif ($quitmsg =~ /^K-Lined$/) { $action = 'k-line'; } else { return; #quit not forced/tracked } $nick = $event->{nick}; $user = $event->{user}; $host = $event->{host}; } elsif (($event->{type} eq 'part') && ($event->{args}->[0] =~ /^requested by (\S+) \((.*)\)/)) { $bynick = $1; $reason = $2 unless (lc $2 eq lc $event->{nick}); $action = 'remove'; $nick = $event->{nick}; $user = $event->{user}; $host = $event->{host}; $channel = $event->{to}->[0]; } elsif ($event->{type} eq 'kick') { $action = 'kick'; $bynick = $event->{nick}; $byuser = $event->{user}; $byhost = $event->{host}; $reason = $event->{args}->[1] unless ($event->{args}->[1] eq $event->{to}->[0]); $nick = $event->{to}->[0]; $channel = $event->{args}->[0]; } return unless defined($action); $lcbynick = lc $bynick if defined $bynick; #we will lowercase the NUHGA info later. if ( (defined($bynick)) && (defined($::sn{$lcbynick})) ) { #we have the nick taking the action available, fill in missing NUHGA info $byuser //= $::sn{$lcbynick}{user}; $byhost //= $::sn{$lcbynick}{host}; $bygecos //= $::sn{$lcbynick}{gecos}; $byaccount //= $::sn{$lcbynick}{account}; if (($byaccount eq '0') or ($byaccount eq '*')) { $byaccount = undef; } } $lcnick = lc $nick if defined $nick; if ( (defined($nick)) && (defined($::sn{$lcnick})) ) { #this should always be true, else something has gone FUBAR $user //= $::sn{$lcnick}{user}; $host //= $::sn{$lcnick}{host}; $gecos //= $::sn{$lcnick}{gecos}; $account //= $::sn{$lcnick}{account}; if (($account eq '0') or ($account eq '*')) { $account = undef; } $ip = ASM::Util->getNickIP($lcnick); } return $::db->resultset('Actionlog')->create({ action => $action, reason => $reason, channel => $channel, nick => $nick, user => $user, host => $host, gecos => $gecos, account => $account, ip => $ip, bynick => $bynick, byuser => $byuser, byhost => $byhost, bygecos => $bygecos, byaccount => $byaccount, })->id; # $::sn{ow} looks like: #$VAR1 = { # "account" => "afterdeath", # "gecos" => "William Athanasius Heimbigner", # "user" => "icxcnika", # "mship" => [ # "#baadf00d", # "#antispammeta-debug", # "#antispammeta" # ], # "host" => "freenode/weird-exception/network-troll/afterdeath" # }; } sub incident { my $self = shift; my ($chan, $nick, $user, $host, $gecos, $risk, $id, $reason) = @_; $chan = lc $chan; my $uuid = $self->{UUID}->create_str(); my $is_opalert = ($risk eq 'opalert'); my $header; if ($is_opalert) { $header = "$chan: $nick requested op attention\n"; } else { $header = "$chan: $risk risk: $nick - $reason\n"; } open(FH, '>', $self->{CONFIG}->{detectdir} . $uuid . '.txt'); print FH $header; if (defined($self->{backlog}->{$chan})) { print FH join('', @{$self->{backlog}->{$chan}}); } print FH "\n\n"; close(FH); return $uuid if $is_opalert; $gecos //= "NOT_DEFINED"; $::db->resultset('Alertlog')->create({ channel => $chan, nick => $nick, user => $user, host => $host, gecos => $gecos, level => $risk, id => $id, reason => $reason, }); return $uuid; } #writes out the backlog to a file which correlates to ASM's SQL actionlog table sub sqlIncident { my $self = shift; my ($channel, $index) = @_; $channel = lc $channel; my @chans = split(/,/, $channel); open(FH, '>', $self->{CONFIG}->{actiondir} . $index . '.txt'); foreach my $chan (@chans) { if (defined($self->{backlog}->{$chan})) { say FH "$chan"; say FH join('', @{$self->{backlog}->{$chan}}); } } close(FH); } sub logg { my $self = shift; my ($conn, $event) = @_; my $cfg = $self->{CONFIG}; my @chans = @{$event->{to}}; @chans = ( $event->{args}->[0] ) if ($event->{type} eq 'kick'); my @time = ($cfg->{zone} eq 'local') ? localtime : gmtime; foreach my $chan ( @chans ) { $chan = lc $chan; next if ($chan eq '$$*'); $chan =~ s/^[@+]//; if ($chan eq '*') { ASM::Util->dprint("$event->{nick}: $event->{args}->[0]", 'snotice'); next; } my $path = ">>$cfg->{dir}${chan}/${chan}" . strftime($cfg->{filefmt}, @time); $_ = ''; $_ = "<$event->{nick}> $event->{args}->[0]" if $event->{type} eq 'public'; $_ = "*** $event->{nick} has joined $chan" if $event->{type} eq 'join'; $_ = "*** $event->{nick} has left $chan ($event->{args}->[0])" if $event->{type} eq 'part'; $_ = "* $event->{nick} $event->{args}->[0]" if $event->{type} eq 'caction'; $_ = "*** $event->{nick} is now known as $event->{args}->[0]" if $event->{type} eq 'nick'; $_ = "*** $event->{nick} has quit ($event->{args}->[0])" if $event->{type} eq 'quit'; $_ = "*** $event->{to}->[0] was kicked by $event->{nick}" if $event->{type} eq 'kick'; $_ = "-$event->{nick}- $event->{args}->[0]" if $event->{type} eq 'notice'; $_ = "*** $event->{nick} sets mode: " . join(" ",@{$event->{args}}) if $event->{type} eq 'mode'; $_ = "*** $event->{nick} changes topic to \"$event->{args}->[0]\"" if $event->{type} eq 'topic'; my $nostamp = $_; $_ = strftime($cfg->{timefmt}, @time) . $_ . "\n"; my $line = $_; my @backlog = (); if (defined($self->{backlog}->{$chan})) { @backlog = @{$self->{backlog}->{$chan}}; if (scalar @backlog >= 30) { shift @backlog; } } push @backlog, $line; $self->{backlog}->{$chan} = \@backlog; } } 1; # vim: ts=2:sts=2:sw=2:expandtab