From 0c1b6cc2808b4fd45779cce4835a6a80eae48265 Mon Sep 17 00:00:00 2001 From: Doug Freed Date: Fri, 4 Dec 2015 12:12:28 +0400 Subject: Initial commit --- cgi-bin/query.pl | 130 +++++++++++++++++++++++++++ cgi-bin/secret/.htaccess | 6 ++ cgi-bin/secret/investigate.pl | 203 ++++++++++++++++++++++++++++++++++++++++++ cgi-bin/secret/logs.pl | 34 +++++++ cgi-bin/showUsers.pl | 75 ++++++++++++++++ checks.txt | 28 ++++++ index.html | 37 ++++++++ investigate.html | 18 ++++ query.html | 50 +++++++++++ syntax.txt | 175 ++++++++++++++++++++++++++++++++++++ 10 files changed, 756 insertions(+) create mode 100755 cgi-bin/query.pl create mode 100644 cgi-bin/secret/.htaccess create mode 100755 cgi-bin/secret/investigate.pl create mode 100755 cgi-bin/secret/logs.pl create mode 100755 cgi-bin/showUsers.pl create mode 100644 checks.txt create mode 100644 index.html create mode 100644 investigate.html create mode 100644 query.html create mode 100644 syntax.txt diff --git a/cgi-bin/query.pl b/cgi-bin/query.pl new file mode 100755 index 0000000..4a9ed26 --- /dev/null +++ b/cgi-bin/query.pl @@ -0,0 +1,130 @@ +#!/usr/bin/perl + +#use warnings; +use Data::Dumper; +use strict; +use DBI; + +use CGI_Lite; + +my $dbh = DBI->connect("DBI:mysql:database=asm_main;host=localhost;port=3306", 'USER', 'PASSWORD'); + +my $debug = 0; + +sub esc +{ + my ($arg) = @_; + $arg = $dbh->quote($arg); + $arg =~ s/\*/%/g; + $arg =~ s/_/\\_/g; + $arg =~ s/\?/_/g; + return $arg; +} + +my $cgi = new CGI_Lite; +my %data = $cgi->parse_form_data; + +$debug = int($data{debug}) if (defined($data{debug})); + +if ($debug) { + print "Content-type: text/plain", "\n\n"; + print Dumper(\%data); +} else { + print "Content-type: text/html", "\n\n"; + print "Query results\n"; +} + +my ($channel, $nick, $user, $host); +my ($level, $id, $reason); + +my $qry = "SELECT time, channel, nick, user, host, gecos, level, id, reason FROM alertlog WHERE "; + +if (defined($data{channel})) { + $qry = $qry . "channel like " . esc($data{channel}); +} else { die "channel not defined!\n"; } + +if (defined($data{nick}) && ($data{nick} ne "*") && ($data{nick} ne "")) { + $qry .= " and nick like " . esc($data{nick}); +} + +if (defined($data{user}) && ($data{user} ne "*") && ($data{user} ne "")) { + $qry .= " and user like " . esc($data{user}); +} + +if (defined($data{host}) && ($data{host} ne "*") && ($data{host} ne "")) { + $qry .= " and host like " . esc($data{host}); +} + +if (defined($data{gecos}) && ($data{gecos} ne "*") && ($data{gecos} ne "")) { + $qry .= " and gecos like " . esc($data{gecos}); +} + +if (defined($data{since})) { + $qry .= sprintf("and time > '%04d-%02d-%02d %02d:%02d:%02d'", + int($data{syear}), int($data{smonth}), int($data{sday}), + int($data{shour}), int($data{smin}), int($data{ssec})); +} + +if (defined($data{before})) { + $qry .= sprintf("and time < '%04d-%02d-%02d %02d:%02d:%02d'", + int($data{byear}), int($data{bmonth}), int($data{bday}), + int($data{bhour}), int($data{bmin}), int($data{bsec})); +} + +#if (defined($data{id})) { +# $qry .= " and id = " . $dbh->quote($data{id}); +#} + +if (defined($data{level}) && ($data{level} ne "any")) { + $qry .= " and level = " . $dbh->quote($data{level}); +} + +if (defined($data{reason})) { + $qry .= " and reason like " . esc($data{reason}); +} + +if (defined($data{sort}) && defined($data{order}) && ($data{order} =~ /^[ad]$/ ) && + ( $data{sort} =~ /^(time|nick|user|host|level|id|reason)$/ ) ) { + $qry .= " order by " . $data{sort}; + $qry .= " desc" if $data{order} eq "d"; +} + +if ($debug) { + print "Querying: "; + print Dumper($qry); +} + +my $sth = $dbh->prepare($qry); +$sth->execute; +my $names = $sth->{'NAME'}; +my $numFields = $sth->{'NUM_OF_FIELDS'}; + + print "" unless $debug; + +for (my $i = 0; $i < $numFields; $i++) { + if ($debug) { + printf("%s%s", $i ? "," : "", $$names[$i]); + } else { + print ""; + } +} + +print "" unless $debug; +print "\n"; + +while (my $ref = $sth->fetchrow_arrayref) { + print "" unless $debug; + + for (my $i = 0; $i < $numFields; $i++) { + if ($debug) { + printf("%s%s", $i ? "," : "", $$ref[$i]); + } else { + print ""; + } + } + print "" unless $debug; + print "\n"; +} +unless ($debug) { + print "
" . $$names[$i] . "
" . $$ref[$i] . "
"; +} diff --git a/cgi-bin/secret/.htaccess b/cgi-bin/secret/.htaccess new file mode 100644 index 0000000..14ea91d --- /dev/null +++ b/cgi-bin/secret/.htaccess @@ -0,0 +1,6 @@ +AuthType Basic +AuthName "Restricted Files" +AuthUserFile /home/icxcnika/AntiSpamMeta/HTTP_ACCESS_USER +AuthGroupFile /home/icxcnika/AntiSpamMeta/HTTP_ACCESS_GROUP +Require group actionlogs +#Require user icxcnika diff --git a/cgi-bin/secret/investigate.pl b/cgi-bin/secret/investigate.pl new file mode 100755 index 0000000..0716480 --- /dev/null +++ b/cgi-bin/secret/investigate.pl @@ -0,0 +1,203 @@ +#!/usr/bin/perl + +#use warnings; +use Data::Dumper; +use strict; +use DBI; + +use CGI_Lite; + +my $dbh = DBI->connect("DBI:mysql:database=asm_main;host=localhost;port=3306", 'USER', 'PASSWORD'); + +my $debug = 0; + +sub esc +{ + my ($arg) = @_; + $arg = $dbh->quote($arg); + $arg =~ s/\*/%/g; + $arg =~ s/_/\\_/g; + $arg =~ s/\?/_/g; + return $arg; +} + +sub dottedQuadToInt +{ + my ($dottedquad) = @_; + my $ip_number = 0; + my @octets = split(/\./, $dottedquad); + foreach my $octet (@octets) { + $ip_number <<= 8; + $ip_number |= $octet; + } + return $ip_number; +} + +my $cgi = new CGI_Lite; +my %data = $cgi->parse_form_data; + +$debug = int($data{debug}) if (defined($data{debug})); + +if ( !defined($data{query}) ) { +print "Content-type: text/html", "\n\n"; +print < + + AntiSpamMeta database query page + + +

Maintaining AntiSpamMeta takes work! Please +
+ + + + +

+

Matching is done based on field1 OR field2 OR field3 etc. Wildcards are supported, +except for the realIP field, which must be blank or an IPv4 dotted quad.

+
+ +HTML +print ' Nickname:
\n"; +print ' User:
\n"; +print ' Hostname:
\n"; +print ' Gecos:
\n"; +print ' Account:
\n"; +print ' Real IP: \n"; +print <
+ + + +HTML +exit 0; +} + +if ($debug) { + print "Content-type: text/plain", "\n\n"; + print Dumper(\%data); +} else { + print "Content-type: text/html", "\n\n"; + print < + +Query results + + + +HTML +} + + +##Queryable items: +## nick, user, host, realip, gecos, account +my $qry = 'SELECT * FROM actionlog WHERE '; + +if (defined($data{nick}) && ($data{nick} ne "*") && ($data{nick} ne "")) { + $qry .= " nick like " . esc($data{nick}) . ' or '; +} + +if (defined($data{user}) && ($data{user} ne "*") && ($data{user} ne "")) { + $qry .= ' user like ' . esc($data{user}) . ' or '; +} + +if (defined($data{host}) && ($data{host} ne "*") && ($data{host} ne "")) { + $qry .= ' host like ' . esc($data{host}) . ' or '; +} + +if (defined($data{gecos}) && ($data{gecos} ne "*") && ($data{gecos} ne "")) { + $qry .= ' gecos like ' . esc($data{gecos}) . ' or '; +} + +if (defined($data{account}) && ($data{account} ne "*") && ($data{account} ne "")) { + $qry .= ' account like ' . esc($data{account}) . ' or '; +} + +if (defined($data{realip}) && ($data{realip} =~ /^\d+\.\d+\.\d+\.\d+$/)) { + $qry .= ' ip = ' . dottedQuadToInt($data{realip}) . ' or '; +} + +$qry .= '(1 = 0)'; # rather than trying to get rid of a trailing 'or ' + +if ($debug) { + print "Querying: "; + print Dumper($qry); +} + +my $sth = $dbh->prepare($qry); +$sth->execute; +my $names = $sth->{'NAME'}; +my $numFields = $sth->{'NUM_OF_FIELDS'}; + +#fields are index,time,action,reason,channel,nick,user,host,ip,gecos,account,bynick,byuser,byhost,bygecos,byaccount +my %f = ( + "index" => 0, + "time" => 1, + "action" => 2, + "reason" => 3, + "channel" => 4, + "nick" => 5, + "user" => 6, + "host" => 7, + "ip" => 8, + "gecos" => 9, + "account" => 10, + "bynick" => 11, + "byuser" => 12, + "byhost" => 13, + "bygecos" => 14, + "byaccount" => 15 +); + +print "" unless $debug; +if ($debug) { + for (my $i = 0; $i < $numFields; $i++) { + printf("%s%s", $i ? "," : "", $$names[$i]); + } +} +#print "" unless $debug; +print "\n"; + +while (my $ref = $sth->fetchrow_arrayref) { +#fields are index,time,action,reason,channel,nick,user,host,ip,gecos,account,bynick,byuser,byhost,bygecos,byaccount + unless ($debug) { + print ''; + print ''; + print ''; + + print ''; +# print ''; + print ''; + } else { + for (my $i = 0; $i < $numFields; $i++) { + printf("%s%s", $i ? "," : "", $$ref[$i]); + } + } + print "\n"; +} +unless ($debug) { + print "
#' . $$ref[$f{'index'}] . ':' . $$ref[$f{'time'}] . '' . $$ref[$f{'nick'}] . ''; + print '!' . $$ref[$f{'user'}] . '@' . $$ref[$f{'host'}] . ' (' . $$ref[$f{'gecos'}] . ')'; + print ' [' . $$ref[$f{'account'}] . ']' if ($$ref[$f{'account'}] ne ''); + print ''; + print ' received ' . $$ref[$f{'action'}] . ''; + print ' (' . $$ref[$f{'reason'}] . ')' if ($$ref[$f{'reason'}] ne ''); + print ' on ' . $$ref[$f{'channel'}] if ($$ref[$f{'channel'}] ne ''); + print ' '; +# print ''; + if ($$ref[$f{'bynick'}] ne '') { + print 'by ' . $$ref[$f{'bynick'}]; + print '!' . $$ref[$f{'byuser'}] . '@' . $$ref[$f{'byhost'}] . ' (' . $$ref[$f{'bygecos'}] . ')'; + print ' [' . $$ref[$f{'byaccount'}] . ']' if ($$ref[$f{'byaccount'}] ne ''); + print ''; + } + print '
"; +} diff --git a/cgi-bin/secret/logs.pl b/cgi-bin/secret/logs.pl new file mode 100755 index 0000000..419f450 --- /dev/null +++ b/cgi-bin/secret/logs.pl @@ -0,0 +1,34 @@ +#!/usr/bin/perl + +#use warnings; +use Data::Dumper; +use strict; +use DBI; + +use CGI_Lite; +my $cgi = new CGI_Lite; +my %data = $cgi->parse_form_data; +my $index = $data{index}; +print "Content-type: text/plain", "\n\n"; +if ( !defined($index) ) { + print "Nice hax!\n"; + exit 0; +} +$index = int $index; + +if ( $index < 50000) { + my $block; + $block = "50K" if $index < 50000; + $block = "40K" if $index < 40000; + $block = "30K" if $index < 30000; + $block = "20K" if $index < 20000; + $block = "10K" if $index < 10000; + print "tar -Oxf /var/www/actionlogs/$block.tar.gz $index.txt\n\n"; + print `tar -Oxf /var/www/actionlogs/$block.tar.gz $index.txt`; +} elsif ( -e "/var/www/actionlogs/$index.txt.lzma" ) { + print `lzcat /var/www/actionlogs/$index.txt.lzma`; +} elsif ( -e "/var/www/actionlogs/$index.txt" ) { + print `cat /var/www/actionlogs/$index.txt`; +} else { + print "u wot m8?\n"; +} diff --git a/cgi-bin/showUsers.pl b/cgi-bin/showUsers.pl new file mode 100755 index 0000000..913ee22 --- /dev/null +++ b/cgi-bin/showUsers.pl @@ -0,0 +1,75 @@ +#!/usr/bin/perl + +#use warnings; +use Data::Dumper; +use strict; +use DBI; +use XML::Simple qw(:strict); + + +print "Content-type: text/html", "\n\n"; +print < + + AntiSpamMeta User List + + +

Maintaining AntiSpamMeta takes work! Please +
+ + + + +

+ + +HTML + +my $xs1 = XML::Simple->new( KeyAttr => ['id'], Cache => [ qw/memcopy/ ]); +my $users = $xs1->XMLin( "/home/icxcnika/AntiSpamMeta/config-main/users.xml", ForceArray => 'person'); + +sub printout +{ + my ($user) = @_; + print ""; + print ""; + print ""; + print ""; + print ""; + print "\n"; +} + +foreach my $user (keys %{$users->{person}}) { + if (index($users->{person}->{$user}->{flags}, 'd') != -1) { + printout($user); + delete $users->{person}->{$user}; + } +} +foreach my $user (keys %{$users->{person}}) { + if (index($users->{person}->{$user}->{flags}, 'a') != -1) { + printout($user); + delete $users->{person}->{$user}; + } +} +foreach my $user (keys %{$users->{person}}) { + if (index($users->{person}->{$user}->{flags}, 's') != -1) { + printout($user); + delete $users->{person}->{$user} + } +} + +foreach my $user (keys %{$users->{person}}) { + printout($user); +} +print "
NickServ accounthsad
$user"; + print "x" if (index($users->{person}->{$user}->{flags}, 'h') != -1); + print ""; + print "x" if (index($users->{person}->{$user}->{flags}, 's') != -1); + print ""; + print "x" if (index($users->{person}->{$user}->{flags}, 'a') != -1); + print ""; + print "x" if (index($users->{person}->{$user}->{flags}, 'd') != -1); + print "
"; + +exit 0; diff --git a/checks.txt b/checks.txt new file mode 100644 index 0000000..fed5951 --- /dev/null +++ b/checks.txt @@ -0,0 +1,28 @@ +Overall alert system workings: +There are 6 alert levels. In order of least significant -> most significant, they are: + +debug: New rules that are in need of testing. These can be extremely useful, or extremely spammy. +info: This is not meant to alert about a malicious action, rather to be a "heads up, keep an eye out because..." +low: These are various rules that may have some false positives. The importance of response time to these varies. +medium: These are rules that are not likely to be a false positive, and very likely mean the channel should receive immediate attention. +high: These are rules with a 99% certainty that a user is attempting to be malicious. +"opalert": The bot will never show an "opalert risk threat", rather, this level indicates who to ping when someone calls !ops + +A user set to be pinged for any given level will be pinged for all higher levels. +For example, if a user is set to be pinged for "low", they'll be pinged for "opalert", but not "info". + +In no particular order, the bot: + +* Checks for what it thinks is a botnet cycling in a channel to spam +* Checks for nicks that join just to spam something and then leave +* Checks for various kinds of flooding - even distributed over multiple nicks - and has anti-anti-detection measures +* Checks for ascii-art pasting +* Checks channel messages against a large array of blacklisted strings +* Detects several IRC exploits +* Detects channel-ctcps (mostly deprecated thanks to cmode +C) +* Detects channel-notices +* Checks channel messages against a few regexes that are always spammy +* Detects some phishing attempts +* Detects some types of attempted ban evasion +* Detects some malicious shorturls + diff --git a/index.html b/index.html new file mode 100644 index 0000000..696fabe --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ +AntiSpamMeta homepage + + + +

AntiSpamMeta is written in my free time. Feel free to give me more monies.
+
+ + + + +
+

If you want, I'll even put your name or nick up here as extra thanks.
+Thanks is also due to dwfreed, who helps me with some of the coding,
+WildPikachu, who has provided reliable servers for ASM since its inception,
+DLange, for hosting domain/DNS things,
+and tomaw, who puts up with a lot of my crap.

+

+
+

About AntiSpamMeta:

+ASM was born on about June 14th, 2006.
+It started out as "AntiSpamBot", a supybot plugin for some rudimentary spam detection.
+It only monitored ##linux, which at the time was a constant target for spam and troll groups.
+However, it quickly outgrew supybot's API, as well as needing to be able to run and process things *much* faster.
+In response to this, the supybot was ditched entirely, and it was re-written using the (legacy) Net::IRC perl framework.
+Despite being a decade old, the framework has proven to be very reliable and only needed a few small changes over time.
+On February 28, 2012, AntiSpamMeta was officially recognized as a freenode FOSS project, and moved base from ##asb-nexus, to #antispammeta.
+As of April 22, 2014, the bot keeps state tracking data for the 58 channels it is in, and the ~11,000 nicks it sees.
+As of September 16, 2015, the bot is in 91 channels, sees ~13,200 nicks, and uses around 50MB of ram.
+It monitors nearly every large channel on freenode, but is still just a client-connection, run as a side project (i.e. not a part of the freenode infrastructure). +

+

Looking for someone to bug that has a specific amount of access? Look no further!

+

For syntax / command info, go here.

+

You can find a general idea of what AntiSpamMeta looks for here.

+

Source code can be found here.

+ + diff --git a/investigate.html b/investigate.html new file mode 100644 index 0000000..e5f0694 --- /dev/null +++ b/investigate.html @@ -0,0 +1,18 @@ + + + AntiSpamMeta database query page + + +

Matching is done based on field1 OR field2 OR field3 etc. Wildcards are supported, +except for the realIP field, which must be blank or an IPv4 dotted quad.

+
+ Nickname: + User: + Hostname:
+ Gecos:
+ Account:
+ Real IP:
+

+
+ + diff --git a/query.html b/query.html new file mode 100644 index 0000000..74f98ff --- /dev/null +++ b/query.html @@ -0,0 +1,50 @@ + + + AntiSpamMeta database query page + + +
+ Channel:
+ Nickname: + User: + Hostname/IP:
+ Gecos:
+ Since time: + - + - + -  + : + : +
+ Before time: + - + - + -  + : + : +
+ Threat level: +   Threat reason:1
+ Sort by:        +

+
+ + diff --git a/syntax.txt b/syntax.txt new file mode 100644 index 0000000..0eecddf --- /dev/null +++ b/syntax.txt @@ -0,0 +1,175 @@ +List of ASM's commands. + +!ops + Generates an alert, and includes the message in that alert. So, via channel message or PM, + you can do "!ops Hey Ops you need to see this" or "!ops #spammychannel bad stuff goin on" + or "!ops" or "!ops #spammychannel". You have to be on the channel in order to call this function, + and there are some stopgaps against abuse. + +;teredo + This will "unmask" an IPv4 connection pretending to be IPv6 - it will report the user's "real" IP. + Use if their IP starts with "2001:0:" + +;status + Reports the bot's memory/cpu/network usage and more. + +;source + Responds with a URL to the bot's source + +;help + Refers you to ASM's website + +;ping + Replies "pong". + +*** These are commands used for check to see if a user has a "bad past" *** + +;db + Returns a link to a page where you can query ASM's alertlog database. + +;query + nick/user/host takes standard wildcards (e.g. AfterDeat*!*@*). + If channel is not specified, checks against all channels. + Responds with how many matches there are for this in ASM's alertlog database. + +;investigate + MUST BE A NICK THAT ANTISPAMMETA CAN SEE. + Returns with the number of how many "incidents", e.g. quiets/kills/klines/bans/removes/etc + against that person that ASM is aware of. + +;investigate2 Restricted to flag "s" + Like ;investigate, except ASM will PM you the details of the most recent 10 incidents, + unless you provide a skip number, in which case it will skip the first X incidents. + There are index numbers attached to the lines, which correspond with logfiles that may + explain why said person got banned/klined/whatever. These logfiles are stored in a password- + protected directory. See AfterDeath/the url provided/;;addwebuser for access. + +;mship Restricted to flag "s" + Reports channels that both ASM and is on. + +;;addwebuser Restricted to flag "s" + THIS COMMAND MUST BE SENT IN PM, WITH TWO SEMICOLONS. + This will give you access to the restricted areas of ASM's database, using an http login of + [yourIRCnick] and [password]. + No assurance of privacy is made with regards to your password. Make it secure, so that someone + else won't use your login, and make it unique - i.e. not something you use for /ns identify. + + + +*** These commands are for managing who has access to the bot *** + +;userx add Restricted to flag "a" + Adds to ASM's config and gives them the specified flags. + BE SURE to use the person's nickserv account, WHICH IS NOT NECESSARILY the nick they're using. + /whois them to be sure. You can't give them a flag you don't have yourself, and for extra security, + you can't ever give them the 'd' flag - this flag has to be assigned via manual config edit. + This means if someone has the 'd' flag, they can't have their flags changed to a new set that + still includes 'd' without a manual edit. + +;userx flags + Shows what flags that nickserv account has access to. + +;userx flags Restricted to flag "a" + Sets the flags for that nickserv account - NOT NECESSARILY THE SAME AS THEIR NICK - to the + provided set of flags. Once again, you can't give flags you don't already have, and you can't + ever give the 'd' flag. + +;userx del Restricted to flag "a" + Removes the nickserv account from ASM's list of authorized users. + + + +*** These commands are for managing who/where the bot notifies of bad stuff *** + +;target Restricted to flag "a" + Adds target to the list of places notified for source channel, with the optional level. + So, to send low-risk alerts and above concerning #spammychannel to #opschannel, you'd do + ;target #spammychannel #opschannel info + If a level isn't specified, it defaults to "debug". + +;detarget Restricted to flag "a" + Stops sending messages concerning to . + +;showhilights Restricted to flag "h" + shows all the channels that it's configured to hilight on, and what level it's for. + +;hilight Restricted to flag "h" + Adds to the list of hilights for for risks of and above. + If is unspecified, it defaults to "info". + +;dehilight Restricted to flag "h" + Removes the list of nicks from the list of hilights for . + + +*** Blacklist management commands *** + +;blacklist Restricted to flag "s" + Adds to the list of strings ASM will watch out for. No pattern matching or anything + like that is done, although it is case insensitive. Please for the love of everything that + is holy use common sense with this. Don't blacklist "nigger" or something like that... + but definitely blacklist "http://spammyurl.com" etc. + +;blreason Restricted to flag "s" + use the ID returned by ;blacklist to set a reason for why you blacklisted it. + +;unblacklist Restricted to flag "s" + fix an oopsie + +;bllookup Restricted to flag "s" + Looks up the blacklisted string represented by a given ID and sends details in PM. + + +*** Administrative commands *** + +;join Restricted to flag "a" + Tells the bot to join said channel. + +;part Restricted to flag "a" + Tells the bot to part said channel. + +;rehash Restricted to flag "a" + Re-reads the string blacklist files and config files etc etc. + +;monitor (yes|no) Restricted to flag "a" + Sets whether or not the channel is monitored for spam stuffs. + This is obviously "yes" by default, but some places turn it off for their #blah-ops channel. + +;silence (yes|no) Restricted to flag "a" + Sets whether or not ASM ignores commands (other than !ops) in the channel. + "no" by default; public channels often want this. + +;quit Restricted to flag "a" + Quits IRC using reason as the quit message. ASM is run in a while loop, so it'll probably + come right back. + +;sync Restricted to flag "a" + sends data to gather information about , in case a sync failed and needs to be + manually forced. + +;restrict (nick|account|host) (+|-) Restricted to flag "a" + Adds a restriction for the specified nick or nickserv account or hostname. + Useful restrictions are: + notrigger - prevents the target from causing the bot to generate a spam alert. + nomsgs - don't relay private messages from target to the master channel + nocommands - prevent target from sending the bot commands. + + +*** Highly restricted debug commands *** + +;sql (main|log) Restricted to flag "d" + No idea what this does right now. + +;sl Restricted to flag "d" + Sends to the IRC server. + +;ev Restricted to flag "d" + Evaluates raw perl code - output is sent to STDOUT, not to IRC. + + +*** Plugin interface *** + +;plugin Restricted to flag "p" + This is used so another bot can have ASM generate alerts. E.G. + ;plugin #spammychannel debug Magic Bad Stuff Detection Algorithm 1 -> + debug risk threat [#spammychannel] - (magicbot plugin) - Magic Bad Stuff + Detection Algorithm 1 - ping lotsofpeople -- cgit v1.2.3