anti-flood, IRC, etc: added support for tracking NickServ accounts; refactored flood message history to be keyed on hostmask instead of nick; placed message history channels into its own sub-key

This commit is contained in:
Pragmatic Software 2011-02-11 02:46:35 +00:00
parent 1f4b6bba50
commit c4ed12b0cb
10 changed files with 209 additions and 112 deletions

View File

@ -56,16 +56,27 @@ sub initialize {
sub get_flood_account { sub get_flood_account {
my ($self, $nick, $user, $host) = @_; my ($self, $nick, $user, $host) = @_;
return $nick if exists ${ $self->message_history }{$nick}; return "$nick!$user\@$host" if exists $self->message_history->{"$nick!$user\@$host"};
foreach my $n (keys %{ $self->{message_history} }) { my $suspicious_nick = 0;
my $userhost = "$user\@$host";
if(${ $self->{message_history} }{$n}{hostmask} =~ /\Q$userhost\E/i) { foreach my $mask (keys %{ $self->message_history }) {
$self->{pbot}->logger->log("Using existing hostmask found with nick $n\n");
return $n; # check if foo!bar@baz matches foo!*@*; e.g., same nick, but different user@host (usually logging into nickserv, but could possibly be attempted nick hijacking)
if($mask =~ m/^\Q$nick\E!.*/i) {
$self->{pbot}->logger->log("anti-flood: Warning: Found nick $nick with existing differing hostmask $mask, using $nick!$user\@$host anyway.\n");
# flag this as suspicious, so we check whois to get nickserv account if there is no user@host existing account found
$suspicious_nick = 1;
}
# check if foo!bar@baz matches *!bar@baz; e.g., same user@host, but different nick (usually alt-nicks due to rejoining)
if($mask =~ m/!\Q$user\E@\Q$host\E$/i) {
$self->{pbot}->logger->log("anti-flood: Using existing hostmask $mask found for $nick!$user\@$host\n");
return $mask;
} }
} }
$self->{pbot}->conn->whois($nick) if $suspicious_nick;
return undef; return undef;
} }
@ -76,40 +87,40 @@ sub add_message {
return undef if $channel =~ /[@!]/; # ignore QUIT messages from nick!user@host channels return undef if $channel =~ /[@!]/; # ignore QUIT messages from nick!user@host channels
#$self->{pbot}->logger->log("appending new message\n"); #$self->{pbot}->logger->log("appending new message\n");
push(@{ $self->message_history->{$account}{$channel}{messages} }, { timestamp => $now, msg => $text, mode => $mode }); push(@{ $self->message_history->{$account}->{channels}->{$channel}{messages} }, { timestamp => $now, msg => $text, mode => $mode });
my $length = $#{ $self->message_history->{$account}{$channel}{messages} } + 1; my $length = $#{ $self->message_history->{$account}->{channels}->{$channel}{messages} } + 1;
if($mode == $self->{FLOOD_JOIN}) { if($mode == $self->{FLOOD_JOIN}) {
if($text =~ /^JOIN/) { if($text =~ /^JOIN/) {
${ $self->message_history }{$account}{$channel}{join_watch}++; $self->message_history->{$account}->{channels}->{$channel}{join_watch}++;
$self->{pbot}->logger->log("$account $channel joinwatch adjusted: ${ $self->message_history }{$account}{$channel}{join_watch}\n"); $self->{pbot}->logger->log("$account $channel joinwatch adjusted: " . $self->message_history->{$account}->{channels}->{$channel}{join_watch} . "\n");
} else { } else {
# PART or QUIT # PART or QUIT
# check QUIT message for netsplits, and decrement joinwatch if found # check QUIT message for netsplits, and decrement joinwatch if found
if($text =~ /^QUIT .*\.net .*\.split/) { if($text =~ /^QUIT .*\.net .*\.split/) {
${ $self->message_history }{$account}{$channel}{join_watch}--; $self->message_history->{$account}->{channels}->{$channel}{join_watch}--;
${ $self->message_history }{$account}{$channel}{join_watch} = 0 if ${ $self->message_history }{$account}{$channel}{join_watch} < 0; $self->message_history->{$account}->{channels}->{$channel}{join_watch} = 0 if $self->message_history->{$account}->{channels}->{$channel}{join_watch} < 0;
$self->{pbot}->logger->log("$account $channel joinwatch adjusted: ${ $self->message_history }{$account}{$channel}{join_watch}\n"); $self->{pbot}->logger->log("$account $channel joinwatch adjusted: " . $self->message_history->{$account}->{channels}->{$channel}{join_watch} . "\n");
$self->message_history->{$account}{$channel}{messages}->[$length - 1]{mode} = $self->{FLOOD_IGNORE}; $self->message_history->{$account}->{channels}->{$channel}{messages}->[$length - 1]{mode} = $self->{FLOOD_IGNORE};
} }
# check QUIT message for Ping timeout or Excess Flood # check QUIT message for Ping timeout or Excess Flood
elsif($text =~ /^QUIT Ping timeout/ or $text =~ /^QUIT Excess Flood/) { elsif($text =~ /^QUIT Ping timeout/ or $text =~ /^QUIT Excess Flood/) {
# deal with these aggressively # ignore these (used to treat aggressively)
#${ $self->message_history }{$account}{$channel}{join_watch}++; $self->message_history->{$account}->{channels}->{$channel}{messages}->[$length - 1]{mode} = $self->{FLOOD_IGNORE};
#$self->{pbot}->logger->log("$account $channel joinwatch adjusted: ${ $self->message_history }{$account}{$channel}{join_watch}\n");
} else { } else {
# some other type of QUIT or PART # some other type of QUIT or PART
$self->message_history->{$account}{$channel}{messages}->[$length - 1]{mode} = $self->{FLOOD_IGNORE}; $self->message_history->{$account}->{channels}->{$channel}{messages}->[$length - 1]{mode} = $self->{FLOOD_IGNORE};
} }
} }
} elsif($mode == $self->{FLOOD_CHAT}) { } elsif($mode == $self->{FLOOD_CHAT}) {
# reset joinwatch if they send a message # reset joinwatch if they send a message
${ $self->message_history }{$account}{$channel}{join_watch} = 0; $self->message_history->{$account}->{channels}->{$channel}{join_watch} = 0;
} }
# keep only MAX_NICK_MESSAGES message history per channel
if($length >= $self->{pbot}->{MAX_NICK_MESSAGES}) { if($length >= $self->{pbot}->{MAX_NICK_MESSAGES}) {
my %msg = %{ shift(@{ $self->message_history->{$account}{$channel}{messages} }) }; my %msg = %{ shift(@{ $self->message_history->{$account}->{channels}->{$channel}{messages} }) };
#$self->{pbot}->logger->log("shifting message off top: $msg{msg}, $msg{timestamp}\n"); #$self->{pbot}->logger->log("shifting message off top: $msg{msg}, $msg{timestamp}\n");
$length--; $length--;
} }
@ -119,9 +130,10 @@ sub add_message {
sub check_flood { sub check_flood {
my ($self, $channel, $nick, $user, $host, $text, $max_messages, $max_time, $mode) = @_; my ($self, $channel, $nick, $user, $host, $text, $max_messages, $max_time, $mode) = @_;
my $mask = lc "$nick!$user\@$host";
my $now = gettimeofday; my $now = gettimeofday;
$self->{pbot}->logger->log(sprintf("%-14s | %-65s | %s\n", $channel, "$nick!$user\@$host", $text)); $self->{pbot}->logger->log(sprintf("%-14s | %-65s | %s\n", $channel, $mask, $text));
$nick = lc $nick; $nick = lc $nick;
$user = lc $user; $user = lc $user;
@ -134,42 +146,44 @@ sub check_flood {
if(not defined $account) { if(not defined $account) {
# new addition # new addition
#$self->{pbot}->logger->log("brand new nick addition\n"); #$self->{pbot}->logger->log("brand new account addition\n");
${ $self->message_history }{$nick}{hostmask} = "$nick!$user\@$host"; #$self->message_history->{$mask} = {};
$self->message_history->{$mask}->{channels} = {};
$self->{pbot}->conn->whois($nick);
$account = $nick; $account = $mask;
} }
# handle QUIT events # handle QUIT events
# (these events come from $channel nick!user@host, not a specific channel or nick, # (these events come from $channel nick!user@host, not a specific channel or nick,
# so they need to be dispatched to all channels the bot exists on) # so they need to be dispatched to all channels the bot exists on)
if($mode == $self->{FLOOD_JOIN} and $text =~ /^QUIT/) { if($mode == $self->{FLOOD_JOIN} and $text =~ /^QUIT/) {
foreach my $chan (keys %{ $self->{pbot}->channels->channels->hash }) { foreach my $chan (lc keys %{ $self->{pbot}->channels->channels->hash }) {
$chan = lc $chan;
next if $chan eq $channel; # skip nick!user@host "channel" next if $chan eq $channel; # skip nick!user@host "channel"
if(not exists ${ $self->message_history }{$account}{$chan}) { if(not exists $self->message_history->{$account}->{channels}->{$chan}) {
#$self->{pbot}->logger->log("adding new channel for existing nick\n"); #$self->{pbot}->logger->log("adding new channel for existing account\n");
${ $self->message_history }{$account}{$chan}{offenses} = 0; $self->message_history->{$account}->{channels}->{$chan}{offenses} = 0;
${ $self->message_history }{$account}{$chan}{last_offense_timestamp} = 0; $self->message_history->{$account}->{channels}->{$chan}{last_offense_timestamp} = 0;
${ $self->message_history }{$account}{$chan}{join_watch} = 0; $self->message_history->{$account}->{channels}->{$chan}{join_watch} = 0;
${ $self->message_history }{$account}{$chan}{messages} = []; $self->message_history->{$account}->{channels}->{$chan}{messages} = [];
} }
$self->add_message($account, $chan, $text, $mode); $self->add_message($account, $chan, $text, $mode);
} }
# don't do flood processing for QUIT messages # don't do flood processing for QUIT events
return; return;
} }
if(not exists ${ $self->message_history }{$account}{$channel}) { if(not exists $self->message_history->{$account}->{channels}->{$channel}) {
#$self->{pbot}->logger->log("adding new channel for existing nick\n"); #$self->{pbot}->logger->log("adding new channel for existing nick\n");
${ $self->message_history }{$account}{$channel}{offenses} = 0; $self->message_history->{$account}->{channels}->{$channel}{offenses} = 0;
${ $self->message_history }{$account}{$channel}{last_offense_timestamp} = 0; $self->message_history->{$account}->{channels}->{$channel}{last_offense_timestamp} = 0;
${ $self->message_history }{$account}{$channel}{join_watch} = 0; $self->message_history->{$account}->{channels}->{$channel}{join_watch} = 0;
${ $self->message_history }{$account}{$channel}{messages} = []; $self->message_history->{$account}->{channels}->{$channel}{messages} = [];
} }
my $length = $self->add_message($account, $channel, $text, $mode); my $length = $self->add_message($account, $channel, $text, $mode);
@ -194,69 +208,78 @@ sub check_flood {
my %msg; my %msg;
if($mode == $self->{FLOOD_CHAT}) { if($mode == $self->{FLOOD_CHAT}) {
%msg = %{ @{ ${ $self->message_history }{$account}{$channel}{messages} }[$length - $max_messages] }; %msg = %{ @{ $self->message_history->{$account}->{channels}->{$channel}{messages} }[$length - $max_messages] };
} else { }
elsif($mode == $self->{FLOOD_JOIN}) {
my $count = 0; my $count = 0;
my $i = $length - 1; my $i = $length - 1;
$self->{pbot}->logger->log("Checking flood history, i = $i\n") if ${ $self->message_history }{$account}{$channel}{join_watch} >= $max_messages; $self->{pbot}->logger->log("Checking flood history, i = $i\n") if $self->message_history->{$account}->{channels}->{$channel}{join_watch} >= $max_messages;
for(; $i >= 0; $i--) { for(; $i >= 0; $i--) {
$self->{pbot}->logger->log($i . " " . $self->message_history->{$account}{$channel}{messages}->[$i]{mode} ." " . $self->message_history->{$account}{$channel}{messages}->[$i]{msg} . " " . $self->message_history->{$account}{$channel}{messages}->[$i]{timestamp} . " [" . ago_exact(time - $self->message_history->{$account}{$channel}{messages}->[$i]{timestamp}) . "]\n") if ${ $self->message_history }{$account}{$channel}{join_watch} >= $max_messages; $self->{pbot}->logger->log($i . " " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{mode} ." " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{msg} . " " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{timestamp} . " [" . ago_exact(time - $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{timestamp}) . "]\n") if $self->message_history->{$account}->{channels}->{$channel}{join_watch} >= $max_messages;
next if $self->message_history->{$account}{$channel}{messages}->[$i]{mode} != $self->{FLOOD_JOIN}; next if $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{mode} != $self->{FLOOD_JOIN};
last if ++$count >= 4; last if ++$count >= 4;
} }
$i = 0 if $i < 0; $i = 0 if $i < 0;
%msg = %{ @{ ${ $self->message_history }{$account}{$channel}{messages} }[$i] }; %msg = %{ @{ $self->message_history->{$account}->{channels}->{$channel}{messages} }[$i] };
}
else {
$self->{pbot}->logger->log("Unknown flood mode [$mode] ... aborting flood enforcement.\n");
return;
} }
my %last = %{ @{ ${ $self->message_history }{$account}{$channel}{messages} }[$length - 1] }; my %last = %{ @{ $self->message_history->{$account}->{channels}->{$channel}{messages} }[$length - 1] };
$self->{pbot}->logger->log("Comparing $nick!$user\@$host " . int($last{timestamp}) . " against " . int($msg{timestamp}) . ": " . (int($last{timestamp} - $msg{timestamp})) . " seconds [" . duration_exact($last{timestamp} - $msg{timestamp}) . "]\n") if $mode == $self->{FLOOD_JOIN}; $self->{pbot}->logger->log("Comparing $nick!$user\@$host " . int($last{timestamp}) . " against " . int($msg{timestamp}) . ": " . (int($last{timestamp} - $msg{timestamp})) . " seconds [" . duration_exact($last{timestamp} - $msg{timestamp}) . "]\n") if $mode == $self->{FLOOD_JOIN};
if($last{timestamp} - $msg{timestamp} <= $max_time && not $self->{pbot}->admins->loggedin($channel, "$nick!$user\@$host")) { if($last{timestamp} - $msg{timestamp} <= $max_time && not $self->{pbot}->admins->loggedin($channel, "$nick!$user\@$host")) {
if($mode == $self->{FLOOD_JOIN}) { if($mode == $self->{FLOOD_JOIN}) {
if(${ $self->message_history }{$account}{$channel}{join_watch} >= $max_messages) { if($self->message_history->{$account}->{channels}->{$channel}{join_watch} >= $max_messages) {
${ $self->message_history }{$account}{$channel}{offenses}++; $self->message_history->{$account}->{channels}->{$channel}{offenses}++;
${ $self->message_history }{$account}{$channel}{last_offense_timestamp} = gettimeofday; $self->message_history->{$account}->{channels}->{$channel}{last_offense_timestamp} = gettimeofday;
my $timeout = (2 ** (($self->message_history->{$account}{$channel}{offenses} + 2) < 10 ? ${ $self->message_history }{$account}{$channel}{offenses} + 2 : 10)); my $timeout = (2 ** (($self->message_history->{$account}->{channels}->{$channel}{offenses} + 2) < 10 ? $self->message_history->{$account}->{channels}->{$channel}{offenses} + 2 : 10));
my $banmask = address_to_mask($host); my $banmask = address_to_mask($host);
$self->{pbot}->chanops->ban_user_timed("*!$user\@$banmask\$##stop_join_flood", $channel, $timeout * 60 * 60); $self->{pbot}->chanops->ban_user_timed("*!$user\@$banmask\$##stop_join_flood", $channel, $timeout * 60 * 60);
$self->{pbot}->logger->log("$nick!$user\@$banmask banned for $timeout hours due to join flooding (offense #${ $self->message_history }{$account}{$channel}{offenses}).\n"); $self->{pbot}->logger->log("$nick!$user\@$banmask banned for $timeout hours due to join flooding (offense #" . $self->message_history->{$account}->{channels}->{$channel}{offenses} . ").\n");
$timeout = "several" if($timeout > 8); $timeout = "several" if($timeout > 8);
$self->{pbot}->conn->privmsg($nick, "You have been banned from $channel for $timeout hours due to join flooding. If your connection issues have been fixed, or this was an accident, you may request an unban at any time by responding to this message with: unbanme $channel"); $self->{pbot}->conn->privmsg($nick, "You have been banned from $channel for $timeout hours due to join flooding. If your connection issues have been fixed, or this was an accident, you may request an unban at any time by responding to this message with: unbanme $channel");
${ $self->message_history }{$account}{$channel}{join_watch} = $max_messages - 2; # give them a chance to rejoin $self->message_history->{$account}->{channels}->{$channel}{join_watch} = $max_messages - 2; # give them a chance to rejoin
} }
} elsif($mode == $self->{FLOOD_CHAT}) { } elsif($mode == $self->{FLOOD_CHAT}) {
${ $self->message_history }{$account}{$channel}{offenses}++; $self->message_history->{$account}->{channels}->{$channel}{offenses}++;
${ $self->message_history }{$account}{$channel}{last_offense_timestamp} = gettimeofday; $self->message_history->{$account}->{channels}->{$channel}{last_offense_timestamp} = gettimeofday;
my $length = ${ $self->message_history }{$account}{$channel}{offenses} ** ${ $self->message_history }{$account}{$channel}{offenses} * ${ $self->message_history }{$account}{$channel}{offenses} * 30;
my $length = $self->message_history->{$account}->{channels}->{$channel}{offenses} ** $self->message_history->{$account}->{channels}->{$channel}{offenses} * $self->message_history->{$account}->{channels}->{$channel}{offenses} * 30;
if($channel =~ /^#/) { #channel flood (opposed to private message or otherwise) if($channel =~ /^#/) { #channel flood (opposed to private message or otherwise)
# don't ban again if already banned # don't ban again if already banned
return if exists $self->{pbot}->chanops->{unban_timeout}->hash->{"*!$user\@$host"}; return if exists $self->{pbot}->chanops->{unban_timeout}->hash->{"*!$user\@$host"};
if($mode == $self->{FLOOD_CHAT}) { $self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $length);
$self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $length);
$self->{pbot}->logger->log("$nick $channel flood offense ${ $self->message_history }{$account}{$channel}{offenses} earned $length second ban\n"); $self->{pbot}->logger->log("$nick $channel flood offense " . $self->message_history->{$account}->{channels}->{$channel}{offenses} . " earned $length second ban\n");
if($length < 1000) { if($length < 1000) {
$length = "$length seconds"; $length = "$length seconds";
} else { } else {
$length = "a little while"; $length = "a little while";
}
$self->{pbot}->conn->privmsg($nick, "You have been muted due to flooding. Please use a web paste service such as http://codepad.org for lengthy pastes. You will be allowed to speak again in $length.");
} }
} else { # private message flood
return if exists $self->{pbot}->ignorelist->{ignore_list}->{"$nick!$user\@$host"}{$channel}; $self->{pbot}->conn->privmsg($nick, "You have been muted due to flooding. Please use a web paste service such as http://codepad.org for lengthy pastes. You will be allowed to speak again in $length.");
$self->{pbot}->logger->log("$nick msg flood offense ${ $self->message_history }{$account}{$channel}{offenses} earned $length second ignore\n"); }
else { # private message flood
return if exists $self->{pbot}->ignorelist->{ignore_list}->{"$nick!$user\@$host"}->{channels}->{$channel};
$self->{pbot}->logger->log("$nick msg flood offense " . $self->message_history->{$account}->{channels}->{$channel}{offenses} . " earned $length second ignore\n");
$self->{pbot}->{ignorelistcmds}->ignore_user("", "floodcontrol", "", "", "$nick!$user\@$host $channel $length"); $self->{pbot}->{ignorelistcmds}->ignore_user("", "floodcontrol", "", "", "$nick!$user\@$host $channel $length");
if($length < 1000) { if($length < 1000) {
$length = "$length seconds"; $length = "$length seconds";
} else { } else {
@ -279,27 +302,41 @@ sub prune_message_history {
my $self = shift; my $self = shift;
$self->{pbot}->logger->log("Pruning message history . . .\n"); $self->{pbot}->logger->log("Pruning message history . . .\n");
foreach my $nick (keys %{ $self->{message_history} }) {
foreach my $channel (keys %{ $self->{message_history}->{$nick} })
{
next if $channel eq 'hostmask'; # TODO: move channels into {channel} subkey
#$self->{pbot}->logger->log("Checking [$nick][$channel]\n"); foreach my $mask (keys %{ $self->{message_history} }) {
my $length = $#{ $self->{message_history}->{$nick}{$channel}{messages} } + 1; foreach my $channel (keys %{ $self->{message_history}->{$mask}->{channels} }) {
my %last = %{ @{ $self->{message_history}->{$nick}{$channel}{messages} }[$length - 1] };
#$self->{pbot}->logger->log("Checking [$mask][$channel]\n");
my $length = $#{ $self->{message_history}->{$mask}->{channels}->{$channel}{messages} } + 1;
my %last = %{ @{ $self->{message_history}->{$mask}->{channels}->{$channel}{messages} }[$length - 1] };
# delete channel key if no activity within 3 days
if(gettimeofday - $last{timestamp} >= 60 * 60 * 24 * 3) { if(gettimeofday - $last{timestamp} >= 60 * 60 * 24 * 3) {
$self->{pbot}->logger->log("$nick in $channel hasn't spoken in three days, removing message history.\n"); $self->{pbot}->logger->log("$mask in $channel hasn't spoken in three days; removing channel history.\n");
delete $self->{message_history}->{$nick}{$channel}; delete $self->{message_history}->{$mask}->{channels}->{$channel};
} else { next;
# decrease offenses counter if 24 hours of elapsed without any new offense }
if ($self->{message_history}->{$nick}{$channel}{offenses} > 0 and $self->{message_history}->{$nick}{$channel}{last_offense_timestamp} > 0 and (gettimeofday - $self->{message_history}->{$nick}{$channel}{last_offense_timestamp} >= 60 * 60 * 24)) {
$self->{message_history}->{$nick}{$channel}{offenses}--; # decrease offenses counter if 24 hours of elapsed without any new offense
$self->{message_history}->{$nick}{$channel}{last_offense_timestamp} = gettimeofday; elsif ($self->{message_history}->{$mask}->{channels}->{$channel}{offenses} > 0 and
$self->{pbot}->logger->log("anti-flood: [$channel][$nick] 24 hours since last offense/decrease -- decreasing offenses to $self->{message_history}->{$nick}{$channel}{offenses}\n"); $self->{message_history}->{$mask}->{channels}->{$channel}{last_offense_timestamp} > 0 and
} (gettimeofday - $self->{message_history}->{$mask}->{channels}->{$channel}{last_offense_timestamp} >= 60 * 60 * 24)) {
$self->{message_history}->{$mask}->{channels}->{$channel}{offenses}--;
$self->{message_history}->{$mask}->{channels}->{$channel}{last_offense_timestamp} = gettimeofday;
$self->{pbot}->logger->log("anti-flood: [$channel][$mask] 24 hours since last offense/decrease -- decreasing offenses to $self->{message_history}->{$mask}->{channels}->{$channel}{offenses}\n");
} }
} }
# delete account for this $mask if all its channels have been deleted
my $count = 0;
foreach my $channel (keys %{ $self->{message_history}->{$mask} }) {
$count++;
}
if($count == 0) {
$self->{pbot}->logger->log("$mask has no more channels remaining; deleting history account.\n");
delete $self->{message_history}->{$mask};
}
} }
} }
@ -352,4 +389,32 @@ sub address_to_mask {
return $banmask; return $banmask;
} }
sub check_nickserv_accounts {
my ($self, $nick, $account) = @_;
foreach my $mask (keys %{ $self->{message_history} }) {
if(exists $self->{message_history}->{$mask}->{nickserv_account}) {
if(lc $self->{message_history}->{$mask}->{nickserv_account} eq lc $account) {
$self->{pbot}->logger->log("anti-flood: Found existing NickServ account for $nick [$account] with message history account $mask.\n");
}
}
else {
if($mask =~ m/^\Q$nick\E!/i) {
$self->{pbot}->logger->log("anti-flood: nick $nick matches mask $mask, and no NickServ account; setting account to $account.\n");
$self->message_history->{$mask}->{nickserv_account} = $account;
}
}
}
}
sub on_whoisaccount {
my ($self, $conn, $event) = @_;
my $nick = $event->{args}[1];
my $account = $event->{args}[2];
$self->{pbot}->logger->log("$nick is using NickServ account [$account]\n");
$self->check_nickserv_accounts($nick, $account);
}
1; 1;

View File

@ -40,7 +40,7 @@ sub initialize {
$self->{is_opped} = {}; $self->{is_opped} = {};
$pbot->timer->register(sub { $self->check_opped_timeouts }, 10); $pbot->timer->register(sub { $self->check_opped_timeouts }, 10);
$pbot->timer->register(sub { $self->check_unban_timeouts }, 10); $pbot->timer->register(sub { $self->check_unban_timeouts }, 10);
} }
sub gain_ops { sub gain_ops {

View File

@ -195,23 +195,39 @@ sub list {
} }
if($arguments =~/^messages\s+(.*)$/) { if($arguments =~/^messages\s+(.*)$/) {
my ($nick_search, $channel_search, $text_search) = split / /, $1; my ($mask_search, $channel_search, $text_search) = split / /, $1;
return "/msg $nick Usage: !list messages <nick regex> <channel regex> [text regex]" if not defined $channel_search; return "/msg $nick Usage: !list messages <hostmask or nick regex> <channel regex> [text regex]" if not defined $channel_search;
$text_search = '.*' if not defined $text_search; $text_search = '.*' if not defined $text_search;
my @results = eval { my @results = eval {
my @ret; my @ret;
foreach my $history_nick (keys %{ $self->{pbot}->antiflood->message_history }) { foreach my $history_mask (keys %{ $self->{pbot}->antiflood->message_history }) {
if($history_nick =~ m/$nick_search/i) { my $nickserv = "(undef)";
foreach my $history_channel (keys %{ $self->{pbot}->antiflood->message_history->{$history_nick} }) {
next if $history_channel eq 'hostmask'; # TODO: move channels into {channel} subkey $nickserv = $self->{pbot}->antiflood->message_history->{$history_mask}->{nickserv_account} if exists $self->{pbot}->antiflood->message_history->{$history_mask}->{nickserv_account};
if($history_mask =~ m/$mask_search/i) {
foreach my $history_channel (keys %{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels} }) {
if($history_channel =~ m/$channel_search/i) { if($history_channel =~ m/$channel_search/i) {
my @messages = @{ ${ $self->{pbot}->antiflood->message_history }{$history_nick}{$history_channel}{messages} }; my @messages = @{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{messages} };
for(my $i = 0; $i <= $#messages; $i++) { for(my $i = 0; $i <= $#messages; $i++) {
next if $messages[$i]->{msg} =~ /^!login/; next if $messages[$i]->{msg} =~ /^\Q$self->{pbot}->{trigger}\E?login/; # don't reveal login passwords
push @ret, { offenses => ${ $self->{pbot}->antiflood->message_history }{$history_nick}{$history_channel}{offenses}, last_offense_timestamp => $self->{pbot}->antiflood->message_history->{$history_nick}{$history_channel}{last_offense_timestamp}, join_watch => ${ $self->{pbot}->antiflood->message_history }{$history_nick}{$history_channel}{join_watch}, text => $messages[$i]->{msg}, timestamp => $messages[$i]->{timestamp}, nick => $history_nick, channel => $history_channel } if $messages[$i]->{msg} =~ m/$text_search/i;
print "$history_mask, $history_channel\n";
print "joinwatch: ", $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{join_watch}, "\n";
push @ret, {
offenses => $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{offenses},
last_offense_timestamp => $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{last_offense_timestamp},
join_watch => $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{join_watch},
text => $messages[$i]->{msg},
timestamp => $messages[$i]->{timestamp},
mask => $history_mask,
nickserv => $nickserv,
channel => $history_channel
} if $messages[$i]->{msg} =~ m/$text_search/i;
} }
} }
} }
@ -226,15 +242,16 @@ sub list {
} }
my $text = ""; my $text = "";
my %seen_nicks = (); my %seen_masks = ();
my @sorted = sort { $a->{timestamp} <=> $b->{timestamp} } @results; my @sorted = sort { $a->{timestamp} <=> $b->{timestamp} } @results;
foreach my $msg (@sorted) { foreach my $msg (@sorted) {
if(not exists $seen_nicks{$msg->{nick}}) { if(not exists $seen_masks{$msg->{mask}}) {
$seen_nicks{$msg->{nick}} = 1; $seen_masks{$msg->{mask}} = 1;
$text .= "--- [$msg->{nick}: join counter: $msg->{join_watch}; offenses: $msg->{offenses}; last offense/decrease: " . ($msg->{last_offense_timestamp} > 0 ? ago(gettimeofday - $msg->{last_offense_timestamp}) : "unknown") . "]\n"; $text .= "--- [$msg->{mask} [$msg->{nickserv}]: join counter: $msg->{join_watch}; offenses: $msg->{offenses}; last offense/decrease: " . ($msg->{last_offense_timestamp} > 0 ? ago(gettimeofday - $msg->{last_offense_timestamp}) : "unknown") . "]\n";
} }
$text .= "[$msg->{channel}] " . localtime($msg->{timestamp}) . " <$msg->{nick}> " . $msg->{text} . "\n"; $text .= "[$msg->{channel}] " . localtime($msg->{timestamp}) . " <$msg->{mask}> " . $msg->{text} . "\n";
} }
$self->{pbot}->logger->log($text); $self->{pbot}->logger->log($text);

View File

@ -433,3 +433,8 @@ PBot fork Jan 20, 2011
- Renamed Net::IRC packages throughout to PBot::IRC. - Renamed Net::IRC packages throughout to PBot::IRC.
- Added support for PONG events in Connection.pm - Added support for PONG events in Connection.pm
PBot fork Feb 10, 2011
- Added support for freenode's whoisaccount
- Moved debug output from STDERR to STDOUT

View File

@ -474,7 +474,7 @@ sub handler {
croak "Not enough arguments to handler()"; croak "Not enough arguments to handler()";
} }
print STDERR "Trying to handle event '$ev'.\n" if $self->{_debug}; print "Trying to handle event '$ev'.\n" if $self->{_debug};
my $handler = undef; my $handler = undef;
if (exists $self->{_handler}->{$ev}) { if (exists $self->{_handler}->{$ev}) {
@ -500,7 +500,7 @@ sub handler {
confess "Bad parameter passed to handler(): rp=$rp"; confess "Bad parameter passed to handler(): rp=$rp";
} }
warn "Handler for '$ev' called.\n" if $self->{_debug}; print "Handler for '$ev' called.\n" if $self->{_debug};
return 1; return 1;
} }
@ -864,7 +864,7 @@ sub parse {
$line =~ s/[\012\015]+$//; $line =~ s/[\012\015]+$//;
next unless $line; next unless $line;
print STDERR "<<< $line\n" if $self->{_debug}; print "<<< $line\n" if $self->{_debug};
# Like the RFC says: "respond as quickly as possible..." # Like the RFC says: "respond as quickly as possible..."
if ($line =~ /^PING/) { if ($line =~ /^PING/) {
@ -955,7 +955,7 @@ sub parse {
} elsif ($type eq "public" or $type eq "msg" or } elsif ($type eq "public" or $type eq "msg" or
$type eq "notice" or $type eq "mode" or $type eq "notice" or $type eq "mode" or
$type eq "join" or $type eq "part" or $type eq "join" or $type eq "part" or
$type eq "topic" or $type eq "invite" ) { $type eq "topic" or $type eq "invite" or $type eq "whoisaccount" ) {
$ev = PBot::IRC::Event->new( $type, # pragma_ 2011/21/01 $ev = PBot::IRC::Event->new( $type, # pragma_ 2011/21/01
$from, $from,

View File

@ -95,9 +95,9 @@ sub args {
sub dump { sub dump {
my ($self, $arg, $counter) = (shift, undef, 0); # heh heh! my ($self, $arg, $counter) = (shift, undef, 0); # heh heh!
printf STDERR "TYPE: %-30s FORMAT: %-30s\n", $self->type, $self->format; printf "TYPE: %-30s FORMAT: %-30s\n", $self->type, $self->format;
print STDERR "FROM: ", $self->from, "\n"; print "FROM: ", $self->from, "\n";
print STDERR "TO: ", join(", ", @{$self->to}), "\n"; print "TO: ", join(", ", @{$self->to}), "\n";
foreach $arg ($self->args) { foreach $arg ($self->args) {
print "Arg ", $counter++, ": ", $arg, "\n"; print "Arg ", $counter++, ": ", $arg, "\n";
} }
@ -306,6 +306,7 @@ sub trans {
323 => "listend", 323 => "listend",
324 => "channelmodeis", 324 => "channelmodeis",
329 => "channelcreate", # 1997-11-24 -- archon 329 => "channelcreate", # 1997-11-24 -- archon
330 => "whoisaccount", # 2011-02-10 pragma_ for freenode
331 => "notopic", 331 => "notopic",
332 => "topic", 332 => "topic",
333 => "topicinfo", # 1997-11-24 -- archon 333 => "topicinfo", # 1997-11-24 -- archon

View File

@ -114,7 +114,7 @@ sub process_line {
$result =~ s/\s+/ /g; $result =~ s/\s+/ /g;
if(length $result > $pbot->max_msg_len) { if(length $result > $pbot->max_msg_len) {
my $link = paste_codepad("[$from] <$nick> $text\n\n$original_result"); my $link = paste_codepad("[" . (defined $from ? $from : "stdin") . "] <$nick> $text\n\n$original_result");
my $trunc = "... [truncated; see $link for full text.]"; my $trunc = "... [truncated; see $link for full text.]";
$pbot->logger->log("Message truncated -- pasted to $link\n"); $pbot->logger->log("Message truncated -- pasted to $link\n");

View File

@ -188,7 +188,8 @@ sub connect {
$self->conn->add_handler('part' , sub { $self->irchandlers->on_departure(@_) }); $self->conn->add_handler('part' , sub { $self->irchandlers->on_departure(@_) });
$self->conn->add_handler('join' , sub { $self->irchandlers->on_join(@_) }); $self->conn->add_handler('join' , sub { $self->irchandlers->on_join(@_) });
$self->conn->add_handler('quit' , sub { $self->irchandlers->on_departure(@_) }); $self->conn->add_handler('quit' , sub { $self->irchandlers->on_departure(@_) });
$self->conn->add_handler('pong' , sub { $self->lagchecker->on_pong(@_) }); $self->conn->add_handler('pong' , sub { $self->lagchecker->on_pong(@_) });
$self->conn->add_handler('whoisaccount' , sub { $self->antiflood->on_whoisaccount(@_) });
} }
#main loop #main loop

View File

@ -175,15 +175,23 @@ sub grab_quotegrab {
return "/msg $nick Please choose a history between 1 and $self->{pbot}->{MAX_NICK_MESSAGES}"; return "/msg $nick Please choose a history between 1 and $self->{pbot}->{MAX_NICK_MESSAGES}";
} }
if(not exists $self->{pbot}->antiflood->message_history->{$grab_nick}) { my $found_mask = undef;
foreach my $mask (keys %{ $self->{pbot}->antiflood->message_history }) {
if($mask =~ m/^\Q$grab_nick\E!/i) {
$found_mask = $mask;
last;
}
}
if(not defined $found_mask) {
return "No message history for $grab_nick."; return "No message history for $grab_nick.";
} }
if(not exists $self->{pbot}->antiflood->message_history->{$grab_nick}{$channel}) { if(not exists $self->{pbot}->antiflood->message_history->{$found_mask}->{channels}->{$channel}) {
return "No message history for $grab_nick in $channel."; return "No message history for $grab_nick in $channel.";
} }
my @messages = @{ $self->{pbot}->antiflood->message_history->{$grab_nick}{$channel}{messages} }; my @messages = @{ $self->{pbot}->antiflood->message_history->{$found_mask}->{channels}->{$channel}{messages} };
$grab_history--; $grab_history--;
@ -208,7 +216,7 @@ sub grab_quotegrab {
$self->save_quotegrabs(); $self->save_quotegrabs();
my $msg = $messages[$grab_history]->{msg}; my $msg = $messages[$grab_history]->{msg};
$msg =~ s/(.{8}).*/$1.../; $msg =~ s/(.{21}).*/$1.../;
return "Quote grabbed: " . ($#{ $self->{quotegrabs} } + 1) . ": <$grab_nick> $msg"; return "Quote grabbed: " . ($#{ $self->{quotegrabs} } + 1) . ": <$grab_nick> $msg";
} }

View File

@ -13,7 +13,7 @@ use warnings;
# These are set automatically by the build/commit script # These are set automatically by the build/commit script
use constant { use constant {
BUILD_NAME => "PBot", BUILD_NAME => "PBot",
BUILD_REVISION => 307, BUILD_REVISION => 308,
BUILD_DATE => "2011-02-10", BUILD_DATE => "2011-02-10",
}; };