summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravataricxcnika <icxcnika@antispammeta.net>2014-04-22 23:46:10 +0400
committerLibravataricxcnika <icxcnika@antispammeta.net>2014-04-22 23:46:10 +0400
commitac56812a5a79b5187a89cb7dcd9078c11ffed54e (patch)
tree8b0cd4718ec93d76127793c2ee2dc2914253b9c0
parent02ce9f49538d6a89c6562708a0ac2871b2d46dc3 (diff)
added some things in gitignore,
added connection tx/rx info to status reports, added exit, sync, and ping commands, bot says which channel a restricted person tries to use ops command on, blacklist system is more intelligent (reason, person adding, can be removed), added functionality for working with URLs, automatically retries to join channels that are throttled, make sure we send passwords to NickServ@services., fixed a scheduling loophole that was exploitable, greatly reduced startup warnings due to slow syncing
-rw-r--r--.gitignore2
-rw-r--r--config-default/commands.xml66
-rwxr-xr-xmeta.pl7
-rw-r--r--modules/classes.pl40
-rw-r--r--modules/event.pl57
-rw-r--r--modules/inspect.pl15
-rw-r--r--modules/log.pl4
-rw-r--r--modules/mysql.pl8
-rw-r--r--modules/services.pl10
-rw-r--r--modules/util.pl5
-rw-r--r--modules/xml.pl7
11 files changed, 195 insertions, 26 deletions
diff --git a/.gitignore b/.gitignore
index 7b17a3f..2a08b2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
/config
/config-main
/config-backup
+/actionlogs
+HTTP_ACCESS*
*.stor
*~
diff --git a/config-default/commands.xml b/config-default/commands.xml
index 9cffe50..83a583f 100644
--- a/config-default/commands.xml
+++ b/config-default/commands.xml
@@ -68,11 +68,23 @@
$upstr = $upstr . int($up/1) . 's';
$up = $up % 1;
}
+ my ($tx, $rx);
+ if ($conn->{_tx}/1024 > 1024) {
+ $tx = sprintf("%.2fMB", $conn->{_tx}/(1024*1024));
+ } else {
+ $tx = sprintf("%.2fKB", $conn->{_tx}/1024);
+ }
+ if ($conn->{_rx}/1024 > 1024) {
+ $rx = sprintf("%.2fMB", $conn->{_rx}/(1024*1024));
+ } else {
+ $rx = sprintf("%.2fKB", $conn->{_rx}/1024);
+ }
$conn->privmsg($event->replyto, "This bot has been running for " . $upstr .
", is tracking " . (scalar (keys %::sn)) . " nicks" .
" across " . (scalar (keys %::sc)) . " tracked channels." .
" It is using " . $size . "KB of RAM" .
- " and has used " . $cputime . " of CPU time.");
+ ", has used $cputime of CPU time" .
+ ", has sent $tx of data, and received $rx of data.");
]]>
</command>
<command cmd="^;mship (\S+)$" flag="s">
@@ -462,6 +474,11 @@
$conn->quit($1);
]]>
</command>
+ <command cmd="^;exit ?(.*)" flag="a">
+ <![CDATA[
+ $conn->quit($1);
+ ]]>
+ </command>
<command cmd="^;ev (.*)" flag="d">
<![CDATA[
eval $1; warn $@ if $@;
@@ -561,7 +578,7 @@
my $txtz = "[\x02$tgt\x02] - $event->{nick} wants op attention";
if ((time-$::sc{$tgt}{users}{lc $event->{nick}}{jointime}) > 90) {
# return; #they've been on the channel for less than 90 seconds, probably nuisance botspam
- $txtz = $txtz . " ($msg) " . $hilite;
+ $txtz = "$txtz ($msg) $hilite !att-$tgt-opalert";
}
my @tgts = ASM::Util->getAlert($tgt, 'opalert', 'msgs');
ASM::Util->sendLongMsg($conn, \@tgts, $txtz);
@@ -586,7 +603,7 @@
else {
my @tgts = ASM::Util->getAlert($tgt, 'opalert', 'msgs');
foreach my $chan (@tgts) {
- $conn->privmsg($chan, $event->{nick} . " tried to use the ops trigger but is restricted from doing so.");
+ $conn->privmsg($chan, $event->{nick} . " tried to use the ops trigger for $tgt but is restricted from doing so.");
}
}
]]>
@@ -594,9 +611,23 @@
<command cmd="^;blacklist (.*)" flag="o">
<![CDATA[
my $str = lc $1;
- push(@::string_blacklist, $str);
- "$str\n" >> io 'string_blacklist.txt';
- $conn->privmsg($event->replyto, "$str blacklisted");
+# push(@::string_blacklist, $str);
+# "$str\n" >> io 'string_blacklist.txt';
+ use String::CRC32;
+ my $id = sprintf("%08x", crc32($str));
+ $::blacklist->{string}->{$id} = { "content" => $str, "setby" => $event->nick };
+ ASM::XML->writeBlacklist();
+ $conn->privmsg($event->replyto, "$str blacklisted with id $id, please use ;blreason $id reasonGoesHere to set a reason");
+ ]]>
+ </command>
+ <command cmd="^;unblacklist ([0-9a-f]+)$" flag="o">
+ <![CDATA[
+ my $id = $1;
+ if (defined($::blacklist->{string}->{$id})) {
+ delete $::blacklist->{string}->{$id};
+ $conn->privmsg($event->replyto, "blacklist id $id removed");
+ ASM::XML->writeBlacklist();
+ } else { $conn->privmsg($event->replyto, "invalid id"); }
]]>
</command>
<command cmd="^;plugin (\S+) (\S+) (.*)" flag="p">
@@ -618,4 +649,27 @@
}
]]>
</command>
+ <command cmd="^;sync (\S+)" flag="a">
+ <![CDATA[
+ my $chan = $1;
+ $conn->sl("MODE $chan bq");
+ $conn->sl("MODE $chan");
+ $conn->sl("WHO $chan %tcnuhra,314");
+ ]]>
+ </command>
+ <command cmd="^;ping$">
+ <![CDATA[
+ $conn->privmsg($event->replyto, "pong");
+ ]]>
+ </command>
+ <command cmd="^;blreason ([0-9a-f]+) (.*)" flag="o">
+ <![CDATA[
+ my $id = $1; my $reason = $2;
+ if (defined($::blacklist->{string}->{$id})) {
+ $::blacklist->{string}->{$id}->{reason} = $reason;
+ $conn->privmsg($event->replyto, "Reason set");
+ ASM::XML->writeBlacklist();
+ } else { $conn->privmsg($event->replyto, "ID is invalid"); }
+ ]]>
+ </command>
</commands>
diff --git a/meta.pl b/meta.pl
index aa38fc4..356b6a1 100755
--- a/meta.pl
+++ b/meta.pl
@@ -15,6 +15,7 @@ use POSIX qw(strftime);
use Term::ANSIColor qw(:constants);
use File::Monitor;
use feature qw(say);
+use HTTP::Async;
$Data::Dumper::Useqq=1;
@@ -28,12 +29,13 @@ $::cset = '';
$::pacealerts = 1;
$::settingschanged = 0;
%::wordlist = ();
+%::httpRequests = ();
## debug variables. 0 to turn off debugging, else set it to a Term::ANSIColor constant.
%::debugx = (
"dnsbl" => 0,
"pingpong" => 0, #BLUE,
- "services" => YELLOW,
+ "snotice" => YELLOW,
"sync" => CYAN,
"chanstate" => MAGENTA,
"restrictions" => BLUE,
@@ -87,6 +89,7 @@ sub init {
mkdir($::settings->{log}->{dir});
$::log = ASM::Log->new($::settings->{log});
$::pass = $::settings->{pass} if $::pass eq '';
+ $::async = HTTP::Async->new();
$host = ${$::settings->{server}}[rand @{$::settings->{server}}];
ASM::Util->dprint( "Connecting to $host", "startup");
$irc->debug($::debug);
@@ -132,7 +135,7 @@ sub init {
$::wordlist{lc $item} = 1;
}
$::fm = File::Monitor->new();
- foreach my $file ("channels", "commands", "dnsbl", "mysql", "restrictions", "rules", "settings", "users") {
+ foreach my $file ("channels", "commands", "dnsbl", "mysql", "restrictions", "rules", "settings", "users", "blacklist") {
$::fm->watch("./" . $::cset . '/' . $file . ".xml");
}
$::fm->watch("string_blacklist.txt");
diff --git a/modules/classes.pl b/modules/classes.pl
index 4fbfb94..5cafdbd 100644
--- a/modules/classes.pl
+++ b/modules/classes.pl
@@ -14,6 +14,7 @@ sub new
my $self = {};
my $tbl = {
"strbl" => \&strbl,
+ "strblnew" => \&strblnew,
"dnsbl" => \&dnsbl,
"floodqueue" => \&floodqueue,
"floodqueue2" => \&floodqueue2,
@@ -34,7 +35,8 @@ sub new
"joinmsgquit" => \&joinmsgquit,
"garbagemeter" => \&garbagemeter,
"cyclebotnet" => \&cyclebotnet,
- "banevade" => \&banevade
+ "banevade" => \&banevade,
+ "urlcrunch" => \&urlcrunch
};
$self->{ftbl} = $tbl;
bless($self);
@@ -76,6 +78,20 @@ sub joinmsgquit
return 1;
}
+sub urlcrunch
+{
+ my ($chk, $id, $event, $chan, $response) = @_;
+ return 0 unless defined($response);
+ return 0 unless ref($response);
+ return 0 unless defined($response->{_previous});
+ return 0 unless defined($response->{_previous}->{_headers});
+ return 0 unless defined($response->{_previous}->{_headers}->{location});
+ if ($response->{_previous}->{_headers}->{location} =~ /$chk->{content}/i) {
+ return 1;
+ }
+ return 0;
+}
+
sub check
{
my $self = shift;
@@ -367,6 +383,23 @@ sub strbl {
return 0;
}
+sub strblnew {
+ my ($chk, $xid, $event, $chan) = @_;
+ my $match = lc $event->{args}->[0];
+ foreach my $id (keys %{$::blacklist->{string}}) {
+ my $line = lc $::blacklist->{string}->{$id}->{content};
+ my $idx = index $match, $line;
+ if ( $idx != -1 ) {
+ my $setby = $::blacklist->{string}->{$id}->{setby};
+ $setby = substr($setby, 0, 1) . "\x02\x02" . substr($setby, 1);
+ return defined($::blacklist->{string}->{$id}->{reason}) ?
+ "id $id added by $setby because $::blacklist->{string}->{$id}->{reason}" :
+ "id $id added by $setby for no reason";
+ }
+ }
+ return 0;
+}
+
sub nick {
my ($chk, $id, $event, $chan) = @_;
if ( lc $event->{nick} eq lc $chk->{content} ) {
@@ -407,6 +440,11 @@ sub nuhg {
return 0;
}
+sub invite {
+ my ( $chk, $id, $event, $chan) = @_;
+ return 1;
+}
+
my $sfc = 0;
sub flood_add
diff --git a/modules/event.pl b/modules/event.pl
index 3c41df4..d3e3fa5 100644
--- a/modules/event.pl
+++ b/modules/event.pl
@@ -7,6 +7,7 @@ use Text::LevenshteinXS qw(distance);
use IO::All;
use POSIX qw(strftime);
use Regexp::Wildcards;
+use HTTP::Request;
sub cs {
my ($chan) = @_;
@@ -71,6 +72,8 @@ sub new
$conn->add_handler('statsdebug', \&on_statsdebug);
$conn->add_handler('endofstats', \&on_endofstats);
$conn->add_handler('channelurlis', \&on_channelurlis);
+ $conn->add_handler('480', \&on_jointhrottled);
+ $conn->add_handler('invite', \&blah); # This doesn't need to be fancy; I just need it to go through inspect
bless($self);
return $self;
}
@@ -79,6 +82,16 @@ my $clearstatsp = 1;
my %statsp = ();
my %oldstatsp = ();
+sub on_jointhrottled
+{
+ my ($conn, $event) = @_;
+ my $chan = $event->{args}->[1];
+ ASM::Util->dprint("$event->{nick}: $chan: $event->{args}->[2]", 'snotice');
+ if ($event->{args}->[2] =~ /throttle exceeded, try again later/) {
+ $conn->schedule(5, sub { $conn->join($chan); });
+ }
+}
+
sub on_statsdebug
{
my ($conn, $event) = @_;
@@ -222,7 +235,7 @@ sub on_connect {
$conn->sl("MODE $event->{args}->[0] +Q-i");
if (lc $event->{args}->[0] ne lc $::settings->{nick}) {
ASM::Util->dprint('Attempting to regain my main nick', 'startup');
- $conn->privmsg( 'NickServ', "regain $::settings->{nick} $::settings->{pass}" );
+ $conn->privmsg( 'NickServ@services.', "regain $::settings->{nick} $::settings->{pass}" );
}
$conn->sl('CAP REQ :extended-join multi-prefix account-notify'); #god help you if you try to use this bot off freenode
}
@@ -325,10 +338,38 @@ sub on_public
$::sc{lc $event->{to}->[0]}{users}{lc $event->{nick}}{msgtime} = time;
$::log->logg( $event );
$::db->logg( $event );
+# if ($event->{args}->[0] =~ /(https?:\/\/bitly.com\/\w+)|(https?:\/\/bit.ly\/\w+)|(https?:\/\/j.mp\/\w+)/i) {
+# my $reqid = $::async->add( HTTP::Request->new( GET => $1 ) );
+# $::httpRequests{$reqid} = $event;
+# my ($response, $id) = $::async->wait_for_next_response( 1 );
+# if (defined($response)) {
+# on_httpResponse($conn, $id, $response);
+# }
+# else { $conn->schedule( 1, sub { checkHTTP($conn); } ); }
+# }
$::inspector->inspect( $conn, $event );
$::commander->command( $conn, $event );
}
+sub checkHTTP
+{
+ my ($conn) = @_;
+ my ($response, $id) = $::async->next_response();
+ if (defined ($response)) {
+ on_httpResponse($conn, $id, $response);
+ }
+ $conn->schedule( 1, sub { checkHTTP($conn); } );
+}
+
+sub on_httpResponse
+{
+ my ($conn, $id, $response) = @_;
+ my $event = $::httpRequests{$id};
+ delete $::httpRequests{$id};
+ $::inspector->inspect( $conn, $event, $response );
+}
+# if ($response->{_previous}->{_headers}->{location} =~ /^https?:\/\/bitly.com\/a\/warning/)
+
sub on_notice
{
my ($conn, $event) = @_;
@@ -778,11 +819,23 @@ sub on_whoxover
my $size = `ps -p $$ h -o size`;
my $cputime = `ps -p $$ h -o time`;
chomp $size; chomp $cputime;
+ my ($tx, $rx);
+ if ($conn->{_tx}/1024 > 1024) {
+ $tx = sprintf("%.2fMB", $conn->{_tx}/(1024*1024));
+ } else {
+ $tx = sprintf("%.2fKB", $conn->{_tx}/1024);
+ }
+ if ($conn->{_rx}/1024 > 1024) {
+ $rx = sprintf("%.2fMB", $conn->{_rx}/(1024*1024));
+ } else {
+ $rx = sprintf("%.2fKB", $conn->{_rx}/1024);
+ }
$conn->privmsg($::settings->{masterchan}, "Finished syncing after " . (time - $::starttime) . " seconds. " .
"I'm tracking " . (scalar (keys %::sn)) . " nicks" .
" across " . (scalar (keys %::sc)) . " tracked channels." .
" I'm using " . $size . "KB of RAM" .
- " and have used " . $cputime . " of CPU time.");
+ ", have used " . $cputime . " of CPU time" .
+ ", have sent $tx of data, and received $rx of data.");
my %x = ();
foreach my $c (@{$::settings->{autojoins}}) { $x{$c} = 1; }
foreach my $cx (keys %::sc) { delete $x{$cx}; }
diff --git a/modules/inspect.pl b/modules/inspect.pl
index 3b72a3a..f5914cb 100644
--- a/modules/inspect.pl
+++ b/modules/inspect.pl
@@ -17,7 +17,7 @@ sub new
}
sub inspect {
- our ($self, $conn, $event) = @_;
+ our ($self, $conn, $event, $response) = @_;
my (%aonx, %dct, $rev, $chan, $id);
%aonx=(); %dct=(); $chan=""; $id="";
my (@dnsbl, @uniq);
@@ -43,7 +43,14 @@ sub inspect {
foreach $id (keys %aonx) {
next unless ( grep { $event->{type} eq $_ } split(/[,:; ]+/, $aonx{$id}{type}) );
next if ($aonx{$id}{class} eq 'dnsbl') && ($event->{host} =~ /(fastwebnet\.it|fastres\.net)$/); #this is a bad hack
- $xresult = $::classes->check($aonx{$id}{class}, $aonx{$id}, $id, $event, $chan, $rev); # this is another bad hack done for dnsbl-related stuff
+ if (defined($response)) {
+ print Dumper($response);
+ if ($aonx{$id}{class} ne 'urlcrunch') { next; } #don't run our regular checks if this is being called from a URL checking function
+ else { $xresult = $::classes->check($aonx{$id}{class}, $aonx{$id}, $id, $event, $chan, $response); }
+ }
+ else {
+ $xresult = $::classes->check($aonx{$id}{class}, $aonx{$id}, $id, $event, $chan, $rev); # this is another bad hack done for dnsbl-related stuff
+ }
next unless (defined($xresult)) && ($xresult ne 0);
ASM::Util->dprint(Dumper($xresult), 'inspector');
$dct{$id} = $aonx{$id};
@@ -81,10 +88,10 @@ sub inspect {
) {
my @tgts = ASM::Util->getAlert($chan, $dct{$id}{risk}, 'msgs');
ASM::Util->sendLongMsg($conn, \@tgts, $txtz);
+ $conn->schedule(45, sub { delete($::ignored{$chan})}) unless defined($::ignored{$chan});
$::ignored{$chan} = $::RISKS{$dct{$id}{risk}};
- $conn->schedule(45, sub { delete($::ignored{$chan})});
}
- $::log->incident($chan, "$chan: $dct{$id}{risk} risk: $event->{nick} - $nicereason\n");
+# $::log->incident($chan, "$chan: $dct{$id}{risk} risk: $event->{nick} - $nicereason\n");
delete $dct{$id}{xresult};
}
}
diff --git a/modules/log.pl b/modules/log.pl
index fe92159..72e0972 100644
--- a/modules/log.pl
+++ b/modules/log.pl
@@ -63,6 +63,10 @@ sub logg
if (substr($chan, 0, 1) eq '@') {
$chan = substr($chan, 1);
}
+ if ($chan eq '*') {
+ ASM::Util->dprint("$event->{nick}: $event->{args}->[0]", 'snotice');
+ next;
+ }
my $path = ">>$cfg->{dir}${chan}/${chan}" . strftime($cfg->{filefmt}, @time);
$_ = '';
$_ = "<$event->{nick}> $event->{args}->[0]" if $event->{type} eq 'public';
diff --git a/modules/mysql.pl b/modules/mysql.pl
index 4ff4441..85e0c6f 100644
--- a/modules/mysql.pl
+++ b/modules/mysql.pl
@@ -196,10 +196,10 @@ sub logg
$string = 'INSERT INTO `quit` (nick, user, host, geco, ip, account, content1) VALUES (' .
$dbh->quote($event->{nick}) . ',' . $dbh->quote($event->{user}) . ',' .
$dbh->quote($event->{host}) . ',' . $dbh->quote($::sn{lc $event->{nick}}->{gecos}) . ',';
- my $ip = ASM::Util->getNickIP(lc $event->{nick});
+ my $ip = ASM::Util->getNickIP(lc $event->{nick}, $event->{host});
if (defined($ip)) { $ip = $dbh->quote($ip); } else { $ip = 'NULL'; }
my $account = $::sn{lc $event->{nick}}->{account};
- if (($account eq '0') or ($account eq '*')) {
+ if (!defined($account) or ($account eq '0') or ($account eq '*')) {
$account = 'NULL';
} else {
$account = $dbh->quote($account);
@@ -213,10 +213,10 @@ sub logg
$dbh->quote($event->{to}->[0]) . ',' .
$dbh->quote($event->{nick}) . ',' . $dbh->quote($event->{user}) . ',' .
$dbh->quote($event->{host}) . ',' . $dbh->quote($::sn{lc $event->{nick}}->{gecos}) . ',';
- my $ip = ASM::Util->getNickIP(lc $event->{nick});
+ my $ip = ASM::Util->getNickIP(lc $event->{nick}, $event->{host});
if (defined($ip)) { $ip = $dbh->quote($ip); } else { $ip = 'NULL'; }
my $account = $::sn{lc $event->{nick}}->{account};
- if (($account eq '0') or ($account eq '*')) {
+ if (!defined($account) or ($account eq '0') or ($account eq '*')) {
$account = 'NULL';
} else {
$account = $dbh->quote($account);
diff --git a/modules/services.pl b/modules/services.pl
index 4783d2b..528901d 100644
--- a/modules/services.pl
+++ b/modules/services.pl
@@ -17,10 +17,10 @@ sub doServices {
my $i = 1;
if ($event->{from} eq 'NickServ!NickServ@services.')
{
- ASM::Util->dprint("NickServ: $event->{args}->[0]", 'services');
+ ASM::Util->dprint("NickServ: $event->{args}->[0]", 'snotice');
if ( $event->{args}->[0] =~ /^This nickname is registered/ )
{
- $conn->privmsg( 'NickServ', "identify $::settings->{nick} $::settings->{pass}" );
+ $conn->privmsg( 'NickServ@services.', "identify $::settings->{nick} $::settings->{pass}" );
}
elsif ( $event->{args}->[0] =~ /^You are now identified/ )
{
@@ -41,12 +41,12 @@ sub doServices {
}
elsif ($event->{args}->[0] =~ /has been (killed|released)/ )
{
- ASM::Util->dprint('Got kill/release successful from NickServ!', 'services');
+# ASM::Util->dprint('Got kill/release successful from NickServ!', 'snotice');
$conn->nick( $::settings->{nick} );
}
elsif ($event->{args}->[0] =~ /has been regained/ )
{
- ASM::Util->dprint('Got regain successful from nickserv!', 'services');
+# ASM::Util->dprint('Got regain successful from nickserv!', 'snotice');
}
elsif ($event->{args}->[0] =~ /Password Incorrect/ )
{
@@ -58,7 +58,7 @@ sub doServices {
if ( $event->{args}->[0] =~ /^\[#/ ) {
return;
}
- ASM::Util->dprint('ChanServ: '. Dumper($event->{args}->[0]), 'services');
+ ASM::Util->dprint("ChanServ: $event->{args}->[0]", 'snotice');
if ( $event->{args}->[0] =~ /^All.*bans matching.*have been cleared on(.*)/)
{
$conn->join($1);
diff --git a/modules/util.pl b/modules/util.pl
index 35a66ae..375d2e5 100644
--- a/modules/util.pl
+++ b/modules/util.pl
@@ -5,6 +5,7 @@ use warnings;
use strict;
use Term::ANSIColor qw (:constants);
use Socket qw( inet_aton inet_ntoa );
+use Data::Dumper;
%::RISKS =
(
@@ -225,13 +226,13 @@ sub getHostIP
sub getNickIP
{
- my ($module, $nick) = @_;
+ my ($module, $nick, $host) = @_;
$nick = lc $nick;
return unless defined($::sn{$nick});
if (defined($::sn{$nick}{ip})) {
return $::sn{$nick}{ip};
}
- my $host = $::sn{$nick}{host};
+ $host = $::sn{$nick}{host} if (!defined($host));
my $ip = getHostIP(undef, $host);
if (defined($ip)) {
$::sn{$nick}{ip} = $ip;
diff --git a/modules/xml.pl b/modules/xml.pl
index 551dc3f..a3c6e85 100644
--- a/modules/xml.pl
+++ b/modules/xml.pl
@@ -20,6 +20,7 @@ sub readXML {
$::dnsbl = $::xs1->XMLin( "$p/dnsbl.xml", ForceArray => []);
$::rules = $::xs1->XMLin( "$p/rules.xml", ForceArray => []);
$::restrictions = $::xs1->XMLin( "$p/restrictions.xml", ForceArray => ['host', 'nick', 'account']);
+ $::blacklist = $::xs1->XMLin( "$p/blacklist.xml", ForceArray => 'string');
}
sub writeXML {
@@ -27,6 +28,7 @@ sub writeXML {
writeChannels();
writeUsers();
writeRestrictions();
+ writeBlacklist();
# $::xs1->XMLout($::commands, RootName => 'commands', KeyAttr => ['id']) > io("$::cset/commands.xml");
}
@@ -52,4 +54,9 @@ sub writeRestrictions {
GroupTags => { hosts => "host", nicks => "nick", accounts => "account"}) > io("$::cset/restrictions.xml");
}
+sub writeBlacklist {
+ $::settingschanged=1;
+ $::xs1->XMLout($::blacklist, RootName => 'blacklist', KeyAttr => ['id']) > io("$::cset/blacklist.xml");
+}
+
return 1;