diff options
| author | 2007-06-06 21:30:25 +0000 | |
|---|---|---|
| committer | 2007-06-06 21:30:25 +0000 | |
| commit | 04c4d4ae0d6fdfe9e59a50e79b40047a08feefb7 (patch) | |
| tree | 3db7aa17c73151e038f08e3cd0c1b2faffc0c482 | |
| parent | 110cb874fc58ae4a214889bb8d6545201a57dd2f (diff) | |
Added files
| -rw-r--r-- | DEPS | 3 | ||||
| -rw-r--r-- | USAGE | 29 | ||||
| -rw-r--r-- | config-default/channels.xml | 148 | ||||
| -rw-r--r-- | config-default/commands.xml | 67 | ||||
| -rw-r--r-- | config-default/settings.xml | 36 | ||||
| -rw-r--r-- | config-default/users.xml | 9 | ||||
| -rwxr-xr-x | meta.pl | 96 | ||||
| -rw-r--r-- | modules/actions.pl | 48 | ||||
| -rw-r--r-- | modules/classes.pl | 124 | ||||
| -rw-r--r-- | modules/command.pl | 41 | ||||
| -rw-r--r-- | modules/event.pl | 264 | ||||
| -rw-r--r-- | modules/inspect.pl | 72 | ||||
| -rw-r--r-- | modules/log.pl | 38 | ||||
| -rw-r--r-- | modules/services.pl | 44 | ||||
| -rw-r--r-- | modules/util.pl | 165 | ||||
| -rw-r--r-- | modules/xml.pl | 39 |
16 files changed, 1223 insertions, 0 deletions
@@ -0,0 +1,3 @@ +IO::All +String::Interpolate +Net::IRC @@ -0,0 +1,29 @@ +a-access commands: + ;join <channel> + ;part <channel>[ :<reason>] + ;quit[ <reason>] + ;rehash + re-reads config files + ;rehash exempts + re-reads exempts.txt + +d-access commands: + ;sl <anything> + sends a raw line + ;ev <anything> + evaluates perl code + +t-access commands: + ;say <something> + says something in the channel where the command was given + ;do <something> + +o-access commands: + ;exempt <something> + prevents sending alerts if <something> matches the nick/ident/host + ;remove <nick> + removes nick from the channel + +always-granted commands: + !ops[ <message>] + gets op attention diff --git a/config-default/channels.xml b/config-default/channels.xml new file mode 100644 index 0000000..e738bfa --- /dev/null +++ b/config-default/channels.xml @@ -0,0 +1,148 @@ +<channels> + <channel id="#wikimedia-commons" op="no"> + <hilights> + <debug>White_cat</debug> + </hilights> + <msgs> + <debug>#wikimedia-ops</debug> + </msgs> + </channel> + <channel id="##windows" op="no"> + <hilights> + <low>numist</low> + <low>quux</low> + <low>HentaiXP</low> + <low>TechSalvager</low> + <low>xyr</low> + <low>njan</low> + <low>JonathanD</low> + <low>Cpudan80</low> + </hilights> + </channel> + <channel id="##asb-meta" op="no" /> + <channel id="##asb-nexus" op="when" /> + <channel id="##asb-testing" op="when"> + <hilights> + <low>a</low> + <low>b</low> + <low>c</low> + <low>d</low> + </hilights> + <msgs> + <debug>##asb-testing</debug> + </msgs> + </channel> + <channel id="##linux" op="no"> + <hilights> + <low>WildPikachu</low> + <low>nalioth</low> + <low>xyr</low> + <low>quux</low> + <low>denny</low> + <low>Fieldy</low> + <low>numist</low> + <medium>PhilKC</medium> + </hilights> + <msgs> + <debug>##linux-ops</debug> + <low>WildPikachu</low> + </msgs> + <event id="no_ahbl" action="none" class="re" reason="placeholder" risk="debug" time="0" type="join" override="ahbl,no_ahbl">.*</event> + </channel> + <channel id="##linux-ops" op="no"> + <hilights> + <opalert>WildPikachu</opalert> + <opalert>xyr</opalert> + <opalert>quux</opalert> + <opalert>denny</opalert> + <opalert>Fieldy</opalert> + <opalert>numist</opalert> + <opalert>PhilKC</opalert> + </hilights> + <msgs> + <opalert>##linux-ops</opalert> + </msgs> + </channel> + <channel id="#baadf00d" op="yes"> + <hilights></hilights> + <msgs></msgs> + </channel> + <channel id="#wikipedia-en-roads-us" op="yes"> + <hilights> + <debug>vishwin60</debug> + </hilights> + <msgs> + <debug>#wikimedia-ops</debug> + </msgs> + </channel> + <channel id="#religion" op="yes"> + <hilights> + <debug>Shadow_mil</debug> + <debug>newmanbe</debug> + </hilights> + </channel> + <channel id="#wikimedia-ops" op="no"> + <hilights></hilights> + <msgs></msgs> + </channel> + <channel id="#wikipedia" op="when"> + <hilights> + <low>AppleBoy</low> + <low>xyr</low> + <low>Fabexplosive</low> + <low>Crazytales2</low> + </hilights> + <msgs> + <debug>#wikimedia-ops</debug> + </msgs> + <event id="autorem_2" action="fmod_wiki" class="re" reason="on chanserv autoremove" risk="info" time="0" type="raw" override="autoremove">^mode \S+ \+b (.*)$</event> + <event id="part_fags" action="ban" class="re" reason="parted with 'Fags!'" risk="low" time="0" type="part">.*Fags! Fags! Fags!.*</event> + </channel> + <channel id="#wikipedia-en" op="no"> + <hilights> + <low>AppleBoy</low> + <low>Soms</low> + <low>xyr</low> + <debug>White_Cat</debug> + </hilights> + <msgs> + <debug>#wikimedia-ops</debug> + </msgs> + </channel> + <channel id="default" op="yes"> + <event id="flood-15to45" action="none" class="floodqueue" reason="flooding 15 to 45" risk="low" time="0" type="public">15:45</event> + </channel> + <channel id="master"> + <event id="ahbl" action="ban" class="dnsbl" reason="in ircbl.ahbl.org" risk="info" time="0" type="join">ircbl.ahbl.org</event> + <event id="autoremove" action="none" class="re" reason="on chanserv autoremove" risk="info" time="0" type="part">requested by ChanServ</event> + <event id="badurl1" action="ban" class="re" reason="sending spam url" risk="medium" time="0" type="public">tinyurl\.com/ypvk4n</event> + <event id="ctcp-dcc" action="ban" class="re" reason="ctcp-dcc" risk="high" time="0" type="cdcc">.*</event> + <event id="dcc" action="ban" class="re" override="dcc-medium" reason="using the DC.C SE.ND exploit" risk="high" time="0" type="public">^DCC SEND |\bDCC SEND "?[A-Za-z0-9]+"? \d+ \d+ \d+</event> + <event id="dcc-medium" action="ban" class="re" reason="using the DC.C SE.ND exploit" risk="medium" time="0" type="public">DCC SEND </event> + <event id="dcc-topic" action="ban" class="re" reason="setting a bad topic" risk="medium" time="0" type="topic">\bDCC SEND </event> + <event id="dosrequest" action="none" class="re" reason="requesting a DoS" risk="medium" time="0" type="public">via paypal if you DoS somone for</event> + <event id="exoticforum" action="ban" class="re" reason="link spam" risk="low" time="0" type="public">http://exotics\.ezbbforum\.com</event> + <event id="flood-10to30" action="none" class="floodqueue" reason="flooding (10 messages per 30 seconds)" risk="low" time="0" type="public">10:30</event> + <event id="flood-5to3" action="quiet" class="floodqueue" reason="flooding (5 messages per 3 seconds)" risk="low" time="10" type="public">5:3</event> + <event id="massflood" action="ban" class="splitflood" reason="distributed flooding" risk="medium" time="0" type="public,part,caction">5:3</event> + <event id="genspam1" action="none" class="re" reason="generic spamming" risk="debug" time="0" type="public">([^ ]{4,} +)\1{5,}</event> + <event id="joinflood-3to20" action="none" class="floodqueue" reason="join flood (3 joins in 20 seconds)" risk="medium" time="0" type="join">3:20</event> + <event id="keylogger" action="ban" class="re" override="keylogger-medium" reason="using the norton start-key-logger exploit" risk="high" time="0" type="public">^startkeylogger$|^stopkeylogger$</event> + <event id="keylogger-medium" action="ban" class="re" reason="using the norton start-key-logger exploit" risk="medium" time="0" type="public">\bstartkeylogger\b|\bstopkeylogger\b</event> + <event id="last_measure_regex" action="kban" class="re" reason="posting what appears to be a last measure link" risk="medium" time="0" type="public">http://\S+\.on\.nimp\.org</event> + <event id="lesbian" action="ban" class="re" reason="bad nick match (lesbian..)" risk="medium" time="0" type="join">^lesbian..$</event> + <event id="nick_cupid" action="ban" class="re" reason="bad nick match (cupid..)" risk="medium" time="0" type="join">^cupid..$</event> + <event id="nick_mudkip" action="ban" class="re" reason="bad nick match (mudkip..)" risk="medium" time="0" type="join">MUDKIP..</event> + <event id="nickspam" action="ban" class="nickspam" reason="nickspamming" risk="high" time="0" type="public">150:20</event> + <event id="notice" action="ban" class="re" reason="sending a notice to the channel" risk="high" time="0" type="notice">.*</event> + <event id="sms_spam" action="none" class="re" reason="spam link / virus" risk="low" time="0" type="public">\.com/sms.exe</event> + <event id="dronebl" action="ban" class="dnsbl" reason="in dnsbl.dronebl.org" risk="info" time="0" type="join" override="ahbl">dnsbl.dronebl.org</event> + <event id="redarmyoflol" action="ban" class="re" reason="parting with 'red army of lol'" risk="low" time="0" type="part">RED ARMY OF LOL</event> + <hilights> + <debug>AfterDeath</debug> + </hilights> + <msgs> + <debug>##asb-nexus</debug> + </msgs> + </channel> +</channels> diff --git a/config-default/commands.xml b/config-default/commands.xml new file mode 100644 index 0000000..4d543c9 --- /dev/null +++ b/config-default/commands.xml @@ -0,0 +1,67 @@ +<commands> + <command cmd="^;join (.*)" flag="a"> + <![CDATA[ + $conn->join($1); + ]]> + </command> + <command cmd="^;part (.*)" flag="a"> + <![CDATA[ + $conn->part($1); + ]]> + </command> + <command cmd="^;sl (.*)" flag="d"> + <![CDATA[ + $conn->sl($1); + ]]> + </command> + <command cmd="^;quit ?(.*)" flag="a"> + <![CDATA[ + $conn->quit($1); + ]]> + </command> + <command cmd="^;ev (.*)" flag="d"> + <![CDATA[ + eval $1; warn $@ if $@; + ]]> + </command> + <command cmd="^;rehash$" flag="a"> + <![CDATA[ + readXML(); + $conn->privmsg($event->{to}->[0], 'config files were re-read'); + ]]> + </command> + <command cmd="^;say (.*)" flag="t"> + <![CDATA[ + $conn->privmsg($event->{to}->[0], $1); + ]]> + </command> + <command cmd="^;do (.*)" flag="t"> + <![CDATA[ + $conn->me($event->{to}->[0], $1); + ]]> + </command> + <command cmd="^;exempt (.*)" flag="o"> + <![CDATA[ + push(@::eline, $1); + "$1\n" >> io 'exempt.txt'; + $conn->privmsg($event->{to}->[0], "$1 exempted"); + ]]> + </command> + <command cmd="^\!ops *(.*)"> + <![CDATA[ + my $hilite=commaAndify(getAlert($event->{to}->[0], 'opalert', 'hilights')); + $conn->privmsg($_, "$hilite: $event->{nick} wants your attention ($1) ") foreach getAlert($event->{to}->[0], 'opalert', 'msgs'); + ]]> + </command> + <command cmd="^;re(load|hash) exempts" flag="a"> + <![CDATA[ + @eline=io('exempt.txt')->getlines; + chomp @eline; + ]]> + </command> + <command cmd="^;remove (.*)$" flag="o"> + <![CDATA[ + $conn->sl("remove $event->{to}->[0] $1"); + ]]> + </command> +</commands> diff --git a/config-default/settings.xml b/config-default/settings.xml new file mode 100644 index 0000000..c7e693f --- /dev/null +++ b/config-default/settings.xml @@ -0,0 +1,36 @@ +<settings> + <altnicks> + <altnick>AntiSpamMeta_</altnick> + <altnick>AntiSpamMeta2</altnick> + </altnicks> + <autojoins> + <autojoin>##asb-nexus</autojoin> + <autojoin>##asb-testing</autojoin> + <autojoin>##asb-meta</autojoin> + <autojoin>#baadf00d</autojoin> + <autojoin>##linux</autojoin> + <autojoin>##linux-ops</autojoin> + <autojoin>#wikipedia</autojoin> + <autojoin>#wikimedia-ops</autojoin> + <autojoin>#wikipedia-en</autojoin> + <autojoin>#religion</autojoin> + <autojoin>#wikipedia-en-roads-us</autojoin> + <autojoin>##windows</autojoin> + <autojoin>#wikimedia-commons</autojoin> + </autojoins> + <log> + <dir>logs/$chan</dir> + <filefmt>$chan-%m-%d-%Y.log</filefmt> + <timefmt>%B %d %T </timefmt> + <zone>GMT</zone> + </log> + <nick>AntiSpamMeta</nick> + <pass></pass> + <port>6667</port> + <realname>I am a new AntiSpamBot in the making.</realname> + <server> + <host>irc.freenode.net</host> + </server> + <username>MetaBot</username> + <lookupexpire>2419200</lookupexpire> +</settings> diff --git a/config-default/users.xml b/config-default/users.xml new file mode 100644 index 0000000..7fcdd3b --- /dev/null +++ b/config-default/users.xml @@ -0,0 +1,9 @@ +<people> + <person id="afterdeath" flags="oda" host="atheme/troll/about.linux.afterdeath" /> + <person id="bumm13" flags="o" host="IDENTIFY" /> + <person id="filiated" flags="oda" host="IDENTIFY" /> + <person id="ocee" host="unaffiliated/ocee" /> + <person id="wildpikachu" host="about/linux/staff/wildpikachu" /> + <person id="slowking_man" host="IDENTIFY" flags="o" /> + <person id="crazytales2" host="IDENTIFY" flags="oda" /> +</people> @@ -0,0 +1,96 @@ +#!/usr/bin/perl -w + +use lib '/home/icxcnika/AntiSpamMeta'; +#use lib '/home/wheimbigner/perl/lib/perl5/site_perl/5.8.8'; +use warnings; +use strict; +use Net::IRC; +use Data::Dumper; +use IO::All; +use Getopt::Long; +use POSIX qw(strftime); + +@::eline=(); +$::pass = ''; + +my @modules = qw/Xml Util Inspect Services Log Command Event Classes Actions/; + +require 'modules/' . lc $_ . '.pl' foreach @modules; + +sub rePlug +{ + my ($conn) = @_; + foreach (@modules) { + eval $_ . '::killsub();'; + warn $@ if $@; + eval 'undef &' . $_ . '::killsub;'; + warn $@ if $@; + delete $INC{'modules/' . lc $_ . '.pl'}; + require 'modules/' . lc $_ . '.pl'; + } + registerHandlers($conn); # this is necessary in case event.pl has changed + # because handlers are registered via pointers +} + +sub init { + my ( $conn, $host ); + my $debug = 0; + my $irc = new Net::IRC; + $::cset = ''; + GetOptions( 'debug|d!' => \$debug, + 'pass|p:s' => \$::pass, + 'config|c:s' => \$::cset + ); + readXML(); + $::pass = $::settings->{pass} if $::pass eq ''; + $host = ${$::settings->{server}}[rand @{$::settings->{server}}]; + print "Connecting to $host\n"; + $irc->debug($debug); + $conn = $irc->newconn( Server => $host, + Port => $::settings->{port} || '6667', + Nick => $::settings->{nick}, + Ircname => $::settings->{realname}, + Username => $::settings->{username}, + Password => $::pass, + Pacing => 1 ); + $conn->debug($debug); + registerHandlers($conn); + @::eline=io('exempt.txt')->getlines; + chomp @::eline; + $irc->start(); +} + +sub registerHandlers { + my ($conn) = @_; + print "Installing handler routines...\n"; + $conn->add_default_handler(\&blah); + $conn->add_handler('bannedfromchan', \&on_bannedfromchan); + $conn->add_handler('mode', \&on_mode); + $conn->add_handler('join', \&on_join); + $conn->add_handler('part', \&on_part); + $conn->add_handler('quit', \&on_quit); + $conn->add_handler('nick', \&on_nick); + $conn->add_handler('notice', \&on_notice); + $conn->add_handler('caction', \&on_public); + $conn->add_handler('msg', \&on_msg); + $conn->add_handler('namreply', \&on_names); + $conn->add_handler('endofnames', \&on_names); + $conn->add_handler('public', \&on_public); + $conn->add_handler('376', \&on_connect); + $conn->add_handler('topic', \&irc_topic); + $conn->add_handler('topicinfo', \&irc_topic); + $conn->add_handler('nicknameinuse', \&on_errnickinuse); + $conn->add_handler('kick', \&on_kick); + $conn->add_handler('cping', \&on_ctcp); + $conn->add_handler('cversion', \&on_ctcp); + $conn->add_handler('csource', \&on_ctcp); + $conn->add_handler('ctime', \&on_ctcp); + $conn->add_handler('cdcc', \&on_ctcp); + $conn->add_handler('cuserinfo', \&on_ctcp); + $conn->add_handler('cclientinfo', \&on_ctcp); + $conn->add_handler('cfinger', \&on_ctcp); + $conn->add_handler('320', \&whois_identified); + $conn->add_handler('318', \&whois_end); +} + +init(); diff --git a/modules/actions.pl b/modules/actions.pl new file mode 100644 index 0000000..7d6717d --- /dev/null +++ b/modules/actions.pl @@ -0,0 +1,48 @@ +use strict; +use warnings; + +#package Actions; + +sub Actions::ban { + our ($conn, $event, $unmode, $chan, %dct, $id); + o_send( $conn, "mode $chan +b *!*\@$event->{host}" ); + $unmode="mode $chan -b *!*\@$event->{host}"; +} + +sub Actions::kban { + our ($conn, $event, $unmode, $chan, %dct, $id); + o_send($conn, "mode $chan +b *!*\@$event->{host}"); + o_send($conn, "kick $chan $event->{nick} :$dct{$id}{reason}"); + $unmode = "mode $chan -b *!*\@$event->{host}"; +} + +sub Actions::kick { + our ($conn, $event, $unmode, $chan, %dct, $id); + o_send($conn, "kick $chan $event->{nick} :$dct{$id}{reason}"); +} + +sub Actions::none { + return; +} + +sub Actions::quiet { + our ($conn, $event, $unmode, $chan, %dct, $id); + o_send( $conn, "mode $chan +b %*!*\@$event->{host}" ); + $unmode = "mode $chan -b %*!*\@$event->{host}"; +} + +sub Actions::fmod_wiki { + our ($conn, $event, $unmode, $chan, %dct, $id); + o_send( $conn, "mode $chan -b *!*\@$event->{host}" ); + o_send( $conn, "mode $chan +b *!*\@$event->{host}!#wikimedia-ops" ); +} + +sub Actions::killsub { + undef &Actions::ban; + undef &Actions::kban; + undef &Actions::kick; + undef &Actions::none; + undef &Actions::quiet; +} + +return 1; diff --git a/modules/classes.pl b/modules/classes.pl new file mode 100644 index 0000000..74b2899 --- /dev/null +++ b/modules/classes.pl @@ -0,0 +1,124 @@ +use strict; +use warnings; +use Data::Dumper; +#package Classes; + +sub Classes::dnsbl { + our (%aonx, $id, %dct, $event, $chan, $rev); + if (defined $rev) { + my $iaddr = hostip( "$rev$aonx{$id}{content}" ); + my @dnsbl = unpack( 'C4', $iaddr ) if defined $iaddr; + $dct{$id} = $aonx{$id} if (@dnsbl); + } +} + +sub Classes::floodqueue { + our (%aonx, $id, %dct, $event, $chan); + my @cut=split(/:/, $aonx{$id}{content}); + $dct{$id} = $aonx{$id} if ( flood_add( $chan, $id, $event->{host}, int($cut[1]) ) == int($cut[0]) ); +} + +sub Classes::nickspam { + our (%aonx, $id, %dct, $event, $chan); + my @cut = split(/:/, $aonx{$id}{content}); + if ( length $event->{args}->[0] >= int($cut[0]) ) { + %_ = map { $_=>$_ } lc keys %{$::sc{lc $chan}{users}}; + my @uniq = grep( $_{$_}, split( / /, lc $event->{args}->[0]) ); + $dct{$id} = $aonx{$id} if ( $#{ @uniq } >= int($cut[1]) ); + } +} + +my %cf=(); +my %bs=(); + +sub Classes::splitflood { + our (%aonx, $id, %dct, $event, $chan); + my $text; + my @cut = split(/:/, $aonx{$id}{content}); + $cf{$id}{timeout}=int($cut[1]); + if ($event->{type} =~ /^(public|notice|part|caction)$/) { + $text=$event->{args}->[0]; + } + return unless defined($text); + return unless length($text) >= 10; + if (defined($bs{$id}{$text}) && (time <= $bs{$id}{$text} + 600)) { + $dct{$id}=$aonx{$id}; + return; + } + push( @{$cf{$id}{$chan}{$text}}, time ); + foreach my $nid ( keys %cf ) { + foreach my $xchan ( keys %{$cf{$nid}} ) { + next if $xchan eq 'timeout'; + foreach my $host ( keys %{$cf{$nid}{$xchan}} ) { + next unless defined $cf{$nid}{$xchan}{$host}[0]; + while ( time >= $cf{$nid}{$xchan}{$host}[0] + $cf{$nid}{'timeout'} ) { + last if ( $#{ $cf{$nid}{$xchan}{$host} } == 0 ); + shift ( @{$cf{$nid}{$xchan}{$host}} ); + } + } + } + } + if ( $#{ @{$cf{$id}{$chan}{$text}}}+1 == int($cut[0]) ) { + $dct{$id}=$aonx{$id}; + $bs{$id}{$text} = time; + } +} + +sub Classes::re { + our (%aonx, $id, %dct, $event, $chan); + my $match = $event->{args}->[0]; + $match = $event->{nick} if ($event->{type} eq 'join'); + if ( (defined $aonx{$id}{nocase}) && ($aonx{$id}{nocase}) ) { + $dct{$id}=$aonx{$id} if ($match =~ /$aonx{$id}{content}/i); + } + else { + $dct{$id}=$aonx{$id} if ($match =~ /$aonx{$id}{content}/); + } +} + +sub Classes::nick { + our (%aonx, $id, %dct, $event, $chan); + if ( lc $event->{nick} eq lc $aonx{$id}{content} ) { + $dct{$id} = $aonx{$id}; + } +} + +sub Classes::ident { + our (%aonx, $id, %dct, $event, $chan); + if ( lc $event->{user} eq lc $aonx{$id}{content} ) { + $dct{$id} = $aonx{$id}; + } +} + +sub Classes::host { + our (%aonx, $id, %dct, $event, $chan); + if ( lc $event->{host} eq lc $aonx{$id}{content} ) { + $dct{$id} = $aonx{$id}; + } +} + +sub Classes::killsub { + undef &Classes::dnsbl; + undef &Classes::floodqueue; + undef &Classes::nickspam; + undef &Classes::re; +} +#$VAR1 = bless( { +# 'to' => [ +# '##asb-testing' +# ], +# 'format' => 'mode', +# 'from' => 'ChanServ!ChanServ@services.', +# 'user' => 'ChanServ', +# 'args' => [ +# '+o', +# 'AntiSpamMetaBeta', +# '' +# ], +# 'nick' => 'ChanServ', +# 'type' => 'mode', +# 'userhost' => 'ChanServ@services.', +# 'host' => 'services.' +# }, 'Net::IRC::Event' ); + +return 1; diff --git a/modules/command.pl b/modules/command.pl new file mode 100644 index 0000000..87c768f --- /dev/null +++ b/modules/command.pl @@ -0,0 +1,41 @@ +use warnings; +use strict; + +sub do_command +{ + my ($conn, $event) = @_; + my $args = $event->{args}->[0]; + my $from = $event->{from}; + my $cmd = $args; + my $d1; + my $nick = lc $event->{nick}; + foreach my $command ( @{$::commands->{command}} ) + { + if (defined($command->{flag})) { + next unless defined($::xusers->{$nick}); + next unless defined($::xusers->{$nick}->{flags}); + next unless defined(grep {$_ eq $command->{flag}} split('', $::xusers->{$nick}->{flags})); + if ($::xusers->{$nick}->{host} ne 'IDENTIFY') { + next unless leq($::xusers->{$nick}->{host}, $event->{host}); + } + else { + if ( $cmd =~ /$command->{cmd}/ ){ + push (@{$::idqueue{$nick}}, [$cmd, $command, $event]); + $conn->sl("whois $nick $nick"); + next; + } + } + } + if ($cmd=~/$command->{cmd}/) { + print "$event->{from} told me: $cmd \n"; + eval $command->{content}; + warn $@ if $@; + } + } +} + +sub Command::killsub { + undef &do_command; +} + +return 1; diff --git a/modules/event.pl b/modules/event.pl new file mode 100644 index 0000000..77583e4 --- /dev/null +++ b/modules/event.pl @@ -0,0 +1,264 @@ +use warnings; +use strict; + +use Text::LevenshteinXS qw(distance); + +sub on_connect { + my ($conn, $event) = @_; # need to check for no services + $conn->privmsg( 'NickServ', "ghost $::settings->{nick} $::pass" ) if lc $event->{args}->[0] ne lc $::settings->{nick}; +} + +my @leven = (); + +sub on_join { + my ($conn, $event) = @_; + my $nick = lc $event->{nick}; + my $chan = lc $event->{to}->[0]; + if ( leq($conn->{_nick}, $nick) ) { + $::sc{$chan} = {}; + $conn->privmsg('ChanServ', "op $chan" ) if (defined cs($chan)->{op}) && (cs($chan)->{op} eq 'yes'); + } + $::sc{$chan}{users}{$nick} = {}; + $::sc{$chan}{users}{$nick}{hostmask} = $event->{userhost}; + $::sc{$chan}{users}{$nick}{op} = 0; + $::sc{$chan}{users}{$nick}{voice} = 0; + inspect( $conn, $event ); + logg( $event ); + if ( $#leven ne -1 ) { + my $ld = ( ( maxlen($nick, $leven[0]) - distance($nick, $leven[0]) ) / maxlen($nick, $leven[0]) ); + my $mx = $leven[0]; + foreach my $item ( @leven ) { + next if $nick eq $item; # avoid dups + my $tld = ( ( maxlen($nick, $item) - distance($nick, $item) ) / maxlen($nick, $item) ); + if ($tld > $ld) { + $ld = $tld; + $mx = $item; + } + } + print "Best match for $nick was $mx with $ld\n" + } + push(@leven, $nick); + shift @leven if $#leven > 5; +} + +sub on_part +{ + my ($conn, $event) = @_; + inspect( $conn, $event ); + my $nick = lc $event->{nick}; + logg( $event ); + if ( leq( $conn->{_nick}, $nick ) ) + { + delete( $::sc{lc $event->{to}->[0]} ); + } + else + { + delete( $::sc{lc $event->{to}->[0]}{users}{$nick} ); + } +} + +sub on_msg +{ + my ($conn, $event) = @_; + do_command ($conn, $event) +} + +sub on_public +{ + my ($conn, $event) = @_; + inspect( $conn, $event ); + logg( $event ); + do_command( $conn, $event ) +} + +sub on_notice +{ + my ($conn, $event) = @_; + inspect( $conn, $event ); + logg( $event ); + doServices($conn, $event); +} + +sub on_errnickinuse +{ + my ($conn, $event) = @_; + $_ = ${$::settings->{altnicks}}[rand @{$::settings->{altnicks}}]; + print "Nick is in use, trying $_\n"; + $conn->nick($_); +} + +sub on_quit +{ + my ($conn, $event) = @_; + my @channels=(); + for ( keys %::sc ) { + push ( @channels, $_ ) if delete $::sc{lc $_}{users}{lc $event->{nick}}; + } + $event->{to} = \@channels; + inspect( $conn, $event ); + logg ( $event ); +} + +sub blah +{ + my ($self, $event) = @_; + inspect($self, $event); +} + +sub irc_users +{ + my ( $channel, @users ) = @_; + for (@users) + { + my ( $op, $voice ); + $op = 0; $voice = 0; + $op = 1 if s/^\@//; + $voice = 1 if s/^\+//; + $::sc{lc $channel}{users}{lc $_} = {}; + $::sc{lc $channel}{users}{lc $_}{op} = $op; + $::sc{lc $channel}{users}{lc $_}{voice} = $voice; + } +} + +sub on_names { + my ($conn, $event) = @_; + irc_users( $event->{args}->[2], split(/ /, $event->{args}->[3]) ) if ($event->{type} eq 'namreply'); +} + +sub irc_topic { + my ($conn, $event) = @_; + inspect($conn, $event) if ($event->{format} ne 'server'); + if ($event->{format} eq 'server') + { + if ($event->{type} eq 'topic') + { + $::sc{lc $event->{args}->[1]}{topic}{text} = $event->{args}->[2]; + } + elsif ($event->{type} eq 'topicinfo') + { + $::sc{lc $event->{args}->[1]}{topic}{time} = $event->{args}->[3]; + $::sc{lc $event->{args}->[1]}{topic}{by} = $event->{args}->[2]; + } + } + else + { + if ($event->{type} eq 'topic') + { + $::sc{lc $event->{to}->[0]}{topic}{text} = $event->{args}->[0]; + } + logg($event); + } +} + +sub on_nick { + my ($conn, $event) = @_; + my @channels=(); + for ( keys %::sc ) + { + if ( defined $::sc{lc $_}{users}{lc $event->{nick}} ) + { + $::sc{lc $_}{users}{lc $event->{args}->[0]} = $::sc{lc $_}{users}{lc $event->{nick}}; + delete( $::sc{lc $_}{users}{lc $event->{nick}} ); + push ( @channels, lc $_ ); + } + } + $event->{to} = \@channels; + inspect($conn, $event); + logg($event) +} + +sub on_kick { + my ($conn, $event) = @_; + if (lc $event->{to}->[0] eq lc $::settings->{nick}) { + $conn->join($event->{args}->[0]); + } + logg( $event ); +} + +sub on_mode +{ + my ($conn, $event) = @_; + my $chan = lc $event->{to}->[0]; + if ($chan =~ /^#/) { + my @modes = @{parse_modes($event->{args})}; + foreach my $line ( @modes ) { + my @ex = @{$line}; + if ( $ex[0] eq '+o' ) { + $::sc{$chan}{users}{lc $ex[1]}{op}=1; + if (lc $ex[1] eq lc $::settings->{nick}) { + doQueue($conn, $chan); + if ( $::channels->{channel}->{$chan}->{op} eq "when" ) { + $conn->schedule(600, sub { print "Deop timer called!\n"; $conn->privmsg('ChanServ', "op $chan -". $::settings->{nick})}); + } + } + } + elsif ( $ex[0] eq '-o' ) { + $::sc{$chan}{users}{lc $ex[1]}{op}=0; + } + elsif ( $ex[0] eq '+v' ) { + $::sc{$chan}{users}{lc $ex[1]}{voice}=1; + } + elsif ( $ex[0] eq '-v' ) { + $::sc{$chan}{users}{lc $ex[1]}{voice}=0; + } + } + logg($event); + } +} + +sub on_ctcp +{ + my ($conn, $event) = @_; + inspect($conn, $event); +} + +sub whois_identified { + my ($conn, $event2) = @_; + my $who = lc $event2->{args}->[1]; + if ( (defined( $::idqueue{$who} )) && ( @{$::idqueue{$who}} ) ) { + foreach my $item (@{$::idqueue{$who}}) { + my ($cmd, $command, $event) = @{$item}; + if ( $cmd =~ /$command->{cmd}/ ){ + print "$event->{from} told me $cmd \n"; + eval $command->{content}; + warn $@ if $@; + } + } + $::idqueue{$who} = []; + } +} + +sub whois_end { + my ($conn, $event) = @_; + my $who = lc $event->{args}->[1]; + $::idqueue{$who} = []; +} + +sub on_bannedfromchan { + my ($conn, $event) = @_; + $conn->privmsg('ChanServ', "unban $event->{args}->[1]"); +} + +sub Event::killsub { + undef &on_connect; + undef &on_join; + undef &on_part; + undef &on_msg; + undef &on_notice; + undef &on_errnickinuse; + undef &on_quit; + undef &on_names; + undef &on_nick; + undef &on_kick; + undef &on_mode; + undef &on_ctcp; + undef &on_bannedfromchan; + undef &blah; + undef &irc_users; + undef &irc_topic; + undef &whois_identified; + undef &whois_end; + undef &on_public; +} + +return 1; diff --git a/modules/inspect.pl b/modules/inspect.pl new file mode 100644 index 0000000..3837ba9 --- /dev/null +++ b/modules/inspect.pl @@ -0,0 +1,72 @@ +use warnings; +use strict; + +use List::Util qw(first); + +#my @ignored = (); + +sub inspect { + our ($conn, $event) = @_; + my (%conx, %monx); + our (%aonx, %dct, $rev, $chan, $id); + %aonx=(); %dct=(); $rev=""; $chan=""; $id=""; + my (@dnsbl, @unpakt, @uniq, @cut); + my ($match, $txtz, $iaddr); + my @override = []; + our $unmode=''; + return if (defined(first { ( lc $event->{nick} eq lc $_ ) } @::eline)); + return if (defined(first { ( lc $event->{user} eq lc $_ ) } @::eline)); + return if (defined(first { ( lc $event->{host} eq lc $_ ) } @::eline)); + $iaddr = hostip($event->{host}); + $rev = join('.', reverse(unpack('C4', $iaddr))).'.' if (defined $iaddr); + %monx = defined($::channels->{channel}->{master}->{event}) ? %{$::channels->{channel}->{master}->{event}} : (); + ## NB: isn't there a better way to do this with grep, somehow? +# foreach ( @ignored ) { +# return if (lc $event->{nick} eq $_); +# } + foreach $chan ( @{$event->{to}} ) { + next unless $chan =~ /^#/; + %conx = defined($::channels->{channel}->{lc $chan}->{event}) ? %{$::channels->{channel}->{lc $chan}->{event}} : (); + %aonx = (%monx, %conx); + foreach $id (keys %aonx) { + next unless ( defined(first { lc $_ eq $event->{type} } split(/[,:; ]+/, $aonx{$id}{type}) ) ) + || ( lc $event->{type} eq lc $aonx{$id}{type} ); +# next unless ( defined($::classes->{class}->{$aonx{$id}{class}})); + eval "Classes::" . $aonx{$id}{class} . "();"; + warn $@ if $@; + } + } + foreach ( keys %dct ) { + push( @override, split( /[ ,;]+/, $dct{$_}{override} ) ) if ( defined $dct{$_}{override} ); + } + delete $dct{$_} foreach @override; + foreach $chan (@{$event->{to}}) { + foreach $id ( keys %dct ) { + $txtz = "$dct{$id}{risk} risk threat: ". + "Detected $event->{nick} $dct{$id}{reason} in $chan "; + $txtz = $txtz . commaAndify(getAlert(lc $chan, $dct{$id}{risk}, 'hilights')) if (getAlert(lc $chan, $dct{$id}{risk}, 'hilights')); + if (cs(lc $chan)->{op} ne 'no') { + if ($event->{type} eq 'topic') { #restore old topic + my $oldtopic = $::sc{lc $event->{to}->[0]}{topic}{text}; + o_send( $conn, "topic $chan :$oldtopic"); + o_send( $conn, "mode $chan +t"); + } + eval "Actions::" . $dct{$id}{action} . "();"; + warn $@ if $@; + my $lconn=$conn; my $lunmode = $unmode; + if ((int($dct{$id}{time}) ne 0) && ($unmode ne '')) { + $conn->schedule(int($dct{$id}{time}), sub { print "Timer called!\n"; o_send($lconn,$lunmode); }); + } + } + $conn->privmsg($_, $txtz) foreach getAlert($chan, $dct{$id}{risk}, 'msgs'); +# push(@ignored, lc $event->{nick}); +# $conn->schedule(10, sub { @ignored = grep { $_ ne lc $event->{nick} } @ignored; }); + } + } +} + +sub Inspect::killsub { + undef &inspect; +} + +return 1; diff --git a/modules/log.pl b/modules/log.pl new file mode 100644 index 0000000..6381110 --- /dev/null +++ b/modules/log.pl @@ -0,0 +1,38 @@ +use warnings; +use strict; + +use String::Interpolate qw(interpolate); + +sub logg +{ + my ($event) = @_; + my @chans = @{$event->{to}}; + my $fh; + @chans = ( $event->{args}->[0] ) if ($event->{type} eq 'kick'); + my @time = ($::settings->{log}->{zone} eq 'local') ? localtime : gmtime; + foreach my $chan ( @chans) + { + $chan = lc $chan; + io(interpolate($::settings->{log}->{dir}))->mkpath; + $_=''; + $_ = "<$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" 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 IRC" 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'; + print Dumper($event) if ($_ eq ''); + $_ = interpolate(strftime($::settings->{log}->{timefmt}, @time)) . $_ . "\n" unless $_ eq ''; + $_ >> io(interpolate($::settings->{log}->{dir}).'/'.interpolate(strftime($::settings->{log}->{filefmt}, @time))) unless ($_ eq ''); + } +} + +sub Log::killsub { + undef &logg; +} + +return 1; diff --git a/modules/services.pl b/modules/services.pl new file mode 100644 index 0000000..d110b60 --- /dev/null +++ b/modules/services.pl @@ -0,0 +1,44 @@ +use warnings; +use strict; + +sub doServices { + my ($conn, $event) = @_; + if ($event->{from} eq 'NickServ!NickServ@services.') + { + print "NickServ: $event->{args}->[0]\n"; + if ( $event->{args}->[0] eq 'This nickname is owned by someone else' ) + { + $conn->privmsg( 'NickServ', "identify $::pass" ); + } + elsif ( $event->{args}->[0] eq 'Password accepted - you are now recognized' ) + { + $conn->join($_) foreach ( @{$::settings->{autojoins}} ); + } + elsif ($event->{args}->[0] =~ /has been killed$/ ) + { + $conn->nick( $::settings->{nick} ); + } + elsif ($event->{args}->[0] =~ /Password Incorrect/ ) + { + $conn->join($_) foreach ( @{$::settings->{autojoins}} ); + } + } + elsif ($event->{from} eq 'ChanServ!ChanServ@services.') + { + print "ChanServ: $event->{args}->[0] \n"; + if ($event->{args}->[0] =~ /You are already opped on \[.(.*).\]/) + { + doQueue($conn, $1); + } + elsif ( $event->{args}->[0] =~ /^All.*bans matching.*have been cleared on(.*)/) + { + $conn->join($1); + } + } +} + +sub Services::killsub { + undef &doServices; +} + +return 1; diff --git a/modules/util.pl b/modules/util.pl new file mode 100644 index 0000000..060eee0 --- /dev/null +++ b/modules/util.pl @@ -0,0 +1,165 @@ +#warning: if you add a function, put it into killsub! + +use warnings; +use strict; + +my %sf; +my %oq; + +my %RISKS = +( + 'debug' => 10, + 'info' => 20, + 'low' => 30, + 'medium' => 40, + 'high' => 50, + 'opalert'=> 9001 #OVER NINE THOUSAND!!! +); +#leaves room for more levels if for some reason we end up needing more +#theoretically, you should be able to change those numbers without any damage + +sub maxlen { + my ($a, $b) = @_; + my ($la, $lb) = (length($a), length($b)); + return $la if ($la > $lb); + return $lb; +} + +#cs: returns the xml settings for the specified chan, or default if there aren't any settings for that chan +sub cs { + my ($chan) = @_; + $chan = lc $chan; + return $::channels->{channel}->{$chan} if ( defined($::channels->{channel}->{$chan}) ); + return $::channels->{channel}->{default}; +} + +#this item is a stub, dur +sub hostip { + return gethostbyname($_[0]); +} + +# Send something that requires ops +sub o_send { + my ( $conn, $send ) = @_; + my @splt = split(/ /, $send); + my $chan = lc $splt[1]; + $oq{$chan} = [] unless defined($oq{$chan}); + if ( cs($chan)->{op} ne 'no' ) { + print Dumper(lc $::settings->{nick}, $::sc{$chan}{users}{lc $::settings->{nick}}); + print Dumper($send, $chan); + if ( $::sc{$chan}{users}{lc $::settings->{nick}}{op} eq 1) { + $conn->sl($send); + } + else { + push( @{$oq{$chan}},$send ); + $conn->privmsg( 'chanserv', "op $chan" ); + } + } +} + +sub doQueue { + my ( $conn, $chan ) = @_; + return unless defined $oq{$chan}; + $conn->sl(shift(@{$oq{$chan}})) while (@{$oq{$chan}}); +} + +sub flood_add { + my ( $chan, $id, $host, $to ) = @_; + push( @{$sf{$id}{$chan}{$host}}, time ); + while ( time >= $sf{$id}{$chan}{$host}[0] + $to ) { + last if ( $#{ $sf{$id}{$chan}{$host} } == 0 ); + shift( @{$sf{$id}{$chan}{$host}} ); + } + return $#{ @{$sf{$id}{$chan}{$host}}}+1; +} + +sub flood_process { + for my $id ( keys %sf ) { + for my $chan ( keys %{$sf{$id}} ) { + for my $host ( keys %{$sf{$id}{$chan}} ) { + next unless defined $sf{$id}{$chan}{$host}[0]; + while ( time >= $sf{$id}{$chan}{$host}[0] + $sf{$id}{'timeout'} ) { + last if ( $#{ $sf{$id}{$chan}{$host} } == 0 ); + shift ( @{$sf{$id}{$chan}{$host}} ); + } + } + } + } +} + +sub getAlert { + my ($c, $risk, $t) = @_; + @_ = (); + $c = lc $c; + foreach my $prisk ( keys %RISKS) { + if ( $RISKS{$risk} >= $RISKS{$prisk} ) { + push( @_, @{$::channels->{channel}->{master}->{$t}->{$prisk}} ) if defined $::channels->{channel}->{master}->{$t}->{$prisk}; + push( @_, @{cs($c)->{$t}->{$prisk}} ) if defined cs($c)->{$t}->{$prisk}; + } + } + return @_; +} + +sub commaAndify { + my @seq = @_; + my $len = ($#seq); + my $last = $seq[$len]; + return '' if $len eq -1; + return $seq[0] if $len eq 0; + return join( ' and ', $seq[0], $seq[1] ) if $len eq 1; + return join( ', ', splice(@seq,0,$len) ) . ', and ' . $last; +} + +sub parse_modes +{ + my ( $n ) = @_; + my @args = @{$n}; + my @modes = split '', shift @args; + my @new_modes=(); + my $t; + foreach my $c ( @modes ) { + if (($c eq '-') || ($c eq '+')) { + $t=$c; + } + else { + if ( defined( grep( /[abdefhIJkloqv]/,($c) ) ) ) { #modes that take args + push (@new_modes, [$t.$c, shift @args]); + } + elsif ( defined( grep( /[cgijLmnpPQrRstz]/, ($c) ) ) ) { + push (@new_modes, [$t.$c]); + } + else { + die "Unknown mode $c !\n"; + } + } + } + return \@new_modes; +} + +sub leq { + my ($s1, $s2) = @_; + return (lc $s1 eq lc $s2); +} + +sub seq { + my ($n1, $n2) = @_; + return 0 unless defined($n1); + return 0 unless defined($n2); + return ($n1 eq $n2); +} + +sub Util::killsub { + undef &cs; + undef &hostip; + undef &o_send; + undef &doQueue; + undef &flood_add; + undef &flood_process; + undef &getAlert; + undef &commaAndify; + undef &parse_modes; + undef ≤ + undef &seq; +} + +return 1; diff --git a/modules/xml.pl b/modules/xml.pl new file mode 100644 index 0000000..2cb2505 --- /dev/null +++ b/modules/xml.pl @@ -0,0 +1,39 @@ +use warnings; +use strict; + +use XML::Simple qw(:strict); + +my $xs1 = XML::Simple->new( KeyAttr => ['id'], Cache => [ qw/storable memcopy/ ]); + +sub readXML { + my ( $p ) = $::cset; #@_; + $p = 'default' if $p eq ''; + $p = "config-$p"; + $::settings = $xs1->XMLin( "$p/settings.xml", ForceArray => [qw/host/], + GroupTags => { altnicks => 'altnick', server => 'host', autojoins=> 'autojoin' }); + $::channels = $xs1->XMLin( "$p/channels.xml", ForceArray => [qw/event debug info low medium high/] ); + $::users = $xs1->XMLin( "$p/users.xml", ForceArray => 'person' ); + $::xusers = $::users->{person}; + $::commands = $xs1->XMLin( "$p/commands.xml", ForceArray => [qw/command/]); +} + +sub writeXML { + my ( $p ) = @_; + $p = 'default' if $p eq ''; + $p = "config-$p"; + $xs1->XMLout($::settings, RootName => 'settings', KeyAttr => ['id'], + GroupTags => { altnicks => 'altnick', server => 'host', autojoins => 'autojoin' }, + ValueAttr => { debug => 'content', nick => 'content', port => 'content', + realname => 'content', username => 'content', dir => 'content', + zone => 'content', filefmt => 'content', timefmt => 'content'}) > io("$p/settings.xml"); + $xs1->XMLout($::channels, RootName => 'channels', KeyAttr => ['id']) > io("$p/channels.xml"); + $xs1->XMLout($::users, RootName => 'people', KeyAttr => ['id']) > io("$p/users.xml"); + $xs1->XMLout($::commands, RootName => 'commands', KeyAttr => ['id']) > io("$p/commands.xml"); +} + +sub Xml::killsub { + undef &readXML; + undef &writeXML; +} + +return 1; |
