summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/classes.pl49
-rw-r--r--modules/event.pl145
-rw-r--r--modules/inspect.pl18
-rw-r--r--modules/services.pl8
-rw-r--r--modules/util.pl41
-rw-r--r--modules/xml.pl40
6 files changed, 254 insertions, 47 deletions
diff --git a/modules/classes.pl b/modules/classes.pl
index fc80da1..c8dcc54 100644
--- a/modules/classes.pl
+++ b/modules/classes.pl
@@ -25,7 +25,8 @@ sub new
"gecos" => \&gecos,
"nuhg" => \&nuhg,
"levenflood" => \&levenflood,
- "proxy" => \&proxy
+ "proxy" => \&proxy,
+ "nickbl" => \&nickbl
};
$self->{ftbl} = $tbl;
bless($self);
@@ -84,6 +85,21 @@ sub levenflood
return $ret;
}
+sub nickbl
+{
+ my ($chk, $id, $event, $chan) = @_;
+ my $nick = $event->{nick};
+ $nick = $event->{args}->[0] if ($event->{type} eq 'nick');
+ my ($fuzzy, $match) = split(/:/, $chk->{content});
+ my @nicks = split(/,/, $match);
+ foreach my $item (@nicks) {
+ if (distance(lc $nick, lc $item) <= $fuzzy) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
sub dnsbl
{
my ($chk, $id, $event, $chan, $rev) = @_;
@@ -147,8 +163,13 @@ sub process_cf
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 ( (scalar @{$cf{$nid}{$xchan}{$host}}) == 0 ) {
+ delete $cf{$nid}{$xchan}{$host};
+ last;
+ }
+# last if ( $#{ $cf{$nid}{$xchan}{$host} } == 0 );
+# shift ( @{$cf{$nid}{$xchan}{$host}} );
}
}
}
@@ -305,12 +326,34 @@ sub flood_process
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}} );
+ if ( (scalar @{$sf{$id}{$chan}{$host}}) == 0 ) {
+ delete $sf{$id}{$chan}{$host};
+ last;
+ }
+# last if ( $#{ $sf{$id}{$chan}{$host} } == 0 );
+# shift ( @{$sf{$id}{$chan}{$host}} );
}
}
}
}
}
+sub dump
+{
+ #%sf, %ls, %cf, %bs
+ open(FH, ">", "sf.txt");
+ print FH Dumper(\%sf);
+ close(FH);
+ open(FH, ">", "ls.txt");
+ print FH Dumper(\%ls);
+ close(FH);
+ open(FH, ">", "cf.txt");
+ print FH Dumper(\%cf);
+ close(FH);
+ open(FH, ">", "bs.txt");
+ print FH Dumper(\%bs);
+ close(FH);
+}
+
1;
diff --git a/modules/event.pl b/modules/event.pl
index f9888e5..a0b3385 100644
--- a/modules/event.pl
+++ b/modules/event.pl
@@ -70,6 +70,8 @@ sub new
$conn->add_handler('banlist', \&on_banlist);
$conn->add_handler('dcc_open', \&dcc_open);
$conn->add_handler('chat', \&on_dchat);
+ $conn->add_handler('channelmodeis', \&on_channelmodeis);
+ $conn->add_handler('quietlist', \&on_quietlist);
$conn->add_handler('pong', \&on_pong);
bless($self);
return $self;
@@ -132,8 +134,9 @@ sub on_connect {
my ($conn, $event) = @_; # need to check for no services
$conn->sl('MODE AntiSpamMeta +Q');
if (lc $event->{args}->[0] ne lc $::settings->{nick}) {
- $conn->privmsg( 'NickServ', "ghost $::settings->{nick} $::settings->{pass}" );
- $conn->privmsg( 'NickServ', "release $::settings->{nick} $::settings->{pass}" );
+ $conn->privmsg( 'NickServ', "regain $::settings->{nick} $::settings->{pass}" );
+# $conn->privmsg( 'NickServ', "ghost $::settings->{nick} $::settings->{pass}" );
+# $conn->privmsg( 'NickServ', "release $::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
}
@@ -150,6 +153,8 @@ sub on_join {
$::synced{$chan} = 0;
unless ( @::syncqueue ) {
$conn->sl('who ' . $chan . ' %tcnuhra,314');
+ $conn->sl('mode ' . $chan);
+ $conn->sl('mode ' . $chan . ' bq');
}
push @::syncqueue, $chan;
}
@@ -211,7 +216,9 @@ sub on_msg
my ($conn, $event) = @_;
$::commander->command($conn, $event);
print strftime("%F %T ", gmtime) . "(msg) " . $event->{from} . " - " . $event->{args}->[0] . "\n";
- $conn->privmsg('#antispammeta', $event->{from} . ' told me: ' . $event->{args}->[0]);
+ if (ASM::Util->notRestricted($event->{nick}, "nomsgs")) {
+ $conn->privmsg('#antispammeta', $event->{from} . ' told me: ' . $event->{args}->[0]);
+ }
}
sub on_public
@@ -259,7 +266,7 @@ sub on_quit
}
$event->{to} = \@channels;
$::db->logg( $event );
- if (($::netsplit == 0) && ($event->{args}->[0] eq "*.net *.split")) { #special, netsplit situation
+ if (($::netsplit == 0) && ($event->{args}->[0] eq "*.net *.split") && (lc $event->{nick} ne 'chanserv')) { #special, netsplit situation
$conn->privmsg("#antispammeta", "Entering netsplit mode - JOIN and QUIT inspection will be disabled for 60 minutes");
$::netsplit = 1;
$conn->schedule(60*60, sub { $::netsplit = 0; $conn->privmsg('#antispammeta', 'Returning to regular operation'); });
@@ -301,21 +308,25 @@ sub irc_topic {
$::inspector->inspect($conn, $event) if ($event->{format} ne 'server');
if ($event->{format} eq 'server')
{
+ my $chan = lc $event->{args}->[1];
if ($event->{type} eq 'topic')
{
- $::sc{lc $event->{args}->[1]}{topic}{text} = $event->{args}->[2];
+ $::sc{$chan}{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];
+ $::sc{$chan}{topic}{time} = $event->{args}->[3];
+ $::sc{$chan}{topic}{by} = $event->{args}->[2];
}
}
else
{
if ($event->{type} eq 'topic')
{
- $::sc{lc $event->{to}->[0]}{topic}{text} = $event->{args}->[0];
+ my $chan = lc $event->{args}->[1];
+ $::sc{$chan}{topic}{text} = $event->{args}->[0];
+ $::sc{$chan}{topic}{time} = time;
+ $::sc{$chan}{topic}{by} = $event->{from};
}
$::log->logg($event);
$::db->logg( $event );
@@ -381,11 +392,11 @@ sub parse_modes
if (($c eq '-') || ($c eq '+')) {
$t=$c;
}
- else {
- if ( defined( grep( /[abdefhIJkloqv]/,($c) ) ) ) { #modes that take args
+ else { #eIbq,k,flj,CFLMPQcgimnprstz
+ if ( grep( /[eIbqkfljov]/,($c) ) ) { #modes that take args
push (@new_modes, [$t.$c, shift @args]);
}
- elsif ( defined( grep( /[cgijLmnpPQrRstz]/, ($c) ) ) ) {
+ elsif ( grep( /[CFLMPQcgimnprstz]/, ($c) ) ) {
push (@new_modes, [$t.$c]);
}
else {
@@ -396,6 +407,29 @@ sub parse_modes
return \@new_modes;
}
+sub on_channelmodeis
+{
+ my ($conn, $event) = @_;
+ my $chan = lc $event->{args}->[1];
+ my @temp = @{$event->{args}};
+ shift @temp; shift @temp;
+ my @modes = @{parse_modes(\@temp)};
+ foreach my $line ( @modes ) {
+ my @ex = @{$line};
+ my ($what, $mode) = split (//, $ex[0]);
+ if ($what eq '+') {
+ if (defined($ex[1])) {
+ push @{$::sc{$chan}{modes}}, $mode . ' ' . $ex[1];
+ } else {
+ push @{$::sc{$chan}{modes}}, $mode;
+ }
+ } else {
+ my @modes = grep {!/^$mode/} @{$::sc{$chan}{modes}};
+ $::sc{$chan}{modes} = \@modes;
+ }
+ }
+}
+
sub on_mode
{
my ($conn, $event) = @_;
@@ -416,11 +450,79 @@ sub on_mode
elsif ( $ex[0] eq '-v' ) {
$::sc{$chan}{users}{lc $ex[1]}{voice}=0;
}
+ elsif ( $ex[0] eq '+b') {
+ $::sc{$chan}{bans}{$ex[1]} = { bannedBy => $event->{from}, bannedOn => time };
+ }
+ elsif ( $ex[0] eq '-b') {
+ delete $::sc{$chan}{bans}{$ex[1]};
+ }
+ elsif ( $ex[0] eq '+q') {
+ $::sc{$chan}{quiets}{$ex[1]} = { bannedBy => $event->{from}, bannedOn => time };
+ }
+ elsif ( $ex[0] eq '-q') {
+ delete $::sc{$chan}{quiets}{$ex[1]};
+ }
+ else {
+ my ($what, $mode) = split (//, $ex[0]);
+ if ($what eq '+') {
+ push @{$::sc{$chan}{modes}}, $mode . ' ' . $ex[1];
+ } else {
+ my @modes = grep {!/^$mode/} @{$::sc{$chan}{modes}};
+ $::sc{$chan}{modes} = \@modes;
+ }
+ if ( ($ex[0] eq '+r') && (! defined($::watchRegged{$chan})) ) {
+ $::watchRegged{$chan} = 1;
+ $conn->schedule(60*45, sub { checkRegged($conn, $chan); });
+ }
+ }
}
$::log->logg($event);
}
}
+sub checkRegged
+{
+ my ($conn, $chan) = @_;
+ if (grep {/r/} @{$::sc{$chan}{modes}}) {
+ my $tgt = $chan;
+ my $risk = "debug";
+ my $hilite=ASM::Util->commaAndify(ASM::Util->getAlert($tgt, $risk, 'hilights'));
+ my $txtz ="\x03" . $::RCOLOR{$::RISKS{$risk}} . "\u$risk\x03 risk threat [\x02$chan\x02] - channel appears to still be +r after 45 minutes; $hilite";
+ my @tgts = ASM::Util->getAlert($tgt, $risk, 'msgs');
+ ASM::Util->sendLongMsg($conn, \@tgts, $txtz)
+ }
+ delete $::watchRegged{$chan};
+}
+
+sub on_banlist
+{
+ my ($conn, $event) = @_;
+# 'args' => [
+# 'AntiSpamMetaBeta',
+# '#antispammeta',
+# 'test!*@*',
+# 'fn-troll!icxcnika@freenode/weird-exception/network-troll/afterdeath',
+# '1344235684'
+# ],
+ my ($me, $chan, $ban, $banner, $bantime) = @{$event->{args}};
+ $::sc{lc $chan}{bans}{$ban} = { bannedBy => $banner, bannedOn => $bantime };
+}
+
+sub on_quietlist
+{
+ my ($conn, $event) = @_;
+# 'args' => [
+# 'AntiSpamMetaBeta',
+# '#antispammeta',
+# 'q',
+# 'loltest!*@*',
+# 'fn-troll!icxcnika@freenode/weird-exception/network-troll/afterdeath',
+# '1344755722'
+# ],
+ my ($me, $chan, $mode, $ban, $banner, $bantime) = @{$event->{args}};
+ $::sc{lc $chan}{quiets}{$ban} = { bannedBy => $banner, bannedOn => $bantime };
+}
+
sub on_ctcp
{
my ($conn, $event) = @_;
@@ -481,6 +583,23 @@ sub on_whoxover
$::synced{$event->{args}->[1]} = 1;
if (defined($chan) ){
$conn->sl('who ' . $chan . ' %tcnuhra,314');
+ $conn->sl('mode ' . $chan);
+ $conn->sl('mode ' . $chan . ' bq');
+ } else {
+ my $size = `ps -p $$ h -o size`;
+ my $cputime = `ps -p $$ h -o time`;
+ chomp $size; chomp $cputime;
+ $conn->privmsg("#antispammeta", "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.");
+ my %x = ();
+ foreach my $c (@{$::settings->{autojoins}}) { $x{$c} = 1; }
+ foreach my $cx (keys %::sc) { delete $x{$cx}; }
+ if (scalar (keys %x)) {
+ $conn->privmsg("#antispammeta", "Syncing appears to have failed for " . ASM::Util->commaAndify(keys %x));
+ }
}
}
@@ -491,10 +610,6 @@ sub on_whofuckedup
print "on_whofuckedup called!\n";
}
}
-sub on_banlist
-{
- my ($conn, $event) = @_;
-}
sub on_bannedfromchan {
my ($conn, $event) = @_;
diff --git a/modules/inspect.pl b/modules/inspect.pl
index 6a4afec..02a6b35 100644
--- a/modules/inspect.pl
+++ b/modules/inspect.pl
@@ -25,7 +25,11 @@ sub inspect {
my $nick = lc $event->{nick};
my $xresult;
return if (index($nick, ".") != -1);
- return if (defined($::eline{$nick}) || defined($::eline{lc $event->{user}}) || defined($::eline{lc $event->{host}}));
+ return unless (ASM::Util->notRestricted($nick, "notrigger"));
+ if (defined($::eline{$nick}) || defined($::eline{lc $event->{user}}) || defined($::eline{lc $event->{host}})) {
+ print "Deprecated eline found for $nick / $event->{user} / $event->{host} !\n";
+ return;
+ }
if ( $event->{host} =~ /gateway\/web\// ) {
if ( $event->{user} =~ /([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/ ) {
$rev = sprintf("%d.%d.%d.%d.", hex($4), hex($3), hex($2), hex($1));
@@ -63,7 +67,7 @@ sub inspect {
$xresult = $dct{$id}{xresult};
my $nicereason = interpolate($dct{$id}{reason});
$::db->record($chan, $event->{nick}, $event->{user}, $event->{host}, $::sn{lc $event->{nick}}->{gecos}, $dct{$id}{risk}, $id, $nicereason);
- $txtz = "\x03" . $::RCOLOR{$::RISKS{$dct{$id}{risk}}} . "\u$dct{$id}{risk}\x03 risk threat [\x02$chan\x02]: ".
+ $txtz = "\x03" . $::RCOLOR{$::RISKS{$dct{$id}{risk}}} . "\u$dct{$id}{risk}\x03 risk threat [\x02$chan\x02] - ".
"\x02$event->{nick}\x02 - ${nicereason}; ping ";
$txtz = $txtz . ASM::Util->commaAndify(ASM::Util->getAlert(lc $chan, $dct{$id}{risk}, 'hilights')) if (ASM::Util->getAlert(lc $chan, $dct{$id}{risk}, 'hilights'));
$txtz = $txtz . ' !att-' . $chan . '-' . $dct{$id}{risk};
@@ -72,15 +76,7 @@ sub inspect {
}
unless (defined($::ignored{$chan}) && ($::ignored{$chan} >= $::RISKS{$dct{$id}{risk}})) {
my @tgts = ASM::Util->getAlert($chan, $dct{$id}{risk}, 'msgs');
-# foreach my $tgt (@tgts) { #unfortunately wikipedia has way too many ops, and it breaks things
- if (length($txtz) <= 380) {
- $conn->privmsg(\@tgts, $txtz);
- } else {
- my $splitpart = rindex($txtz, " ", 380);
- $conn->privmsg(\@tgts, substr($txtz, 0, $splitpart));
- $conn->privmsg(\@tgts, substr($txtz, $splitpart));
- }
-# }
+ ASM::Util->sendLongMsg($conn, \@tgts, $txtz);
$::ignored{$chan} = $::RISKS{$dct{$id}{risk}};
$conn->schedule(45, sub { delete($::ignored{$chan})});
}
diff --git a/modules/services.pl b/modules/services.pl
index aafe68e..d2b3d31 100644
--- a/modules/services.pl
+++ b/modules/services.pl
@@ -15,9 +15,9 @@ sub doServices {
if ($event->{from} eq 'NickServ!NickServ@services.')
{
print "NickServ: $event->{args}->[0]\n";
- if ( $event->{args}->[0] eq 'This nickname is registered' )
+ if ( $event->{args}->[0] =~ /^This nickname is registered/ )
{
- $conn->privmsg( 'NickServ', "identify $::settings->{pass}" );
+ $conn->privmsg( 'NickServ', "identify $::settings->{nick} $::settings->{pass}" );
}
elsif ( $event->{args}->[0] =~ /^You are now identified/ )
{
@@ -37,6 +37,10 @@ sub doServices {
print "Got kill/release successful from nickserv!\n" if $::debugx{services};
$conn->nick( $::settings->{nick} );
}
+ elsif ($event->{args}->[0] =~ /has been regained/ )
+ {
+ print "Got regain successful from nickserv!\n" if $::debugx{services};
+ }
elsif ($event->{args}->[0] =~ /Password Incorrect/ )
{
die("NickServ password invalid.")
diff --git a/modules/util.pl b/modules/util.pl
index 109882b..7a111b5 100644
--- a/modules/util.pl
+++ b/modules/util.pl
@@ -137,6 +137,18 @@ sub flood_process {
}
}
+# If $tgts="#antispammeta" that's fine, and if $tgts = ["#antispammeta", "##linux-ops"] that's cool too
+sub sendLongMsg {
+ my ($module, $conn, $tgts, $txtz) = @_;
+ if (length($txtz) <= 380) {
+ $conn->privmsg($tgts, $txtz);
+ } else {
+ my $splitpart = rindex($txtz, " ", 380);
+ $conn->privmsg($tgts, substr($txtz, 0, $splitpart));
+ $conn->privmsg($tgts, substr($txtz, $splitpart));
+ }
+}
+
sub getAlert {
my ($module, $c, $risk, $t) = @_;
my @disable = ();
@@ -183,4 +195,33 @@ sub dprint {
print $text if $::debug;
}
+sub notRestricted {
+ my ($module, $nick, $restriction) = @_;
+ $nick = lc $nick;
+ my $host = $::sn{$nick}{host};
+ my $account = lc $::sn{$nick}{account};
+ my $ret = 1;
+ if (defined($::restrictions->{nicks}->{nick}->{$nick})) {
+ if (defined($::restrictions->{nicks}->{nick}->{$nick}->{$restriction})) {
+ $ret= 0;
+ }
+ }
+ if ((defined($host)) && (defined($account))) {
+ if (defined($::restrictions->{accounts}->{account}->{$account})) {
+ if (defined($::restrictions->{accounts}->{account}->{$account}->{$restriction})) {
+ $ret= 0;
+ }
+ }
+ if (defined($::restrictions->{hosts}->{host}->{$host})) {
+ if (defined($::restrictions->{hosts}->{host}->{$host}->{$restriction})) {
+ $ret= 0;
+ }
+ }
+ }
+ if (($ret == 0) && ($::debugx{restrictions})) {
+ print "Restriction $restriction found for $nick\n";
+ }
+ return $ret;
+}
+
return 1;
diff --git a/modules/xml.pl b/modules/xml.pl
index 14f5826..a23df77 100644
--- a/modules/xml.pl
+++ b/modules/xml.pl
@@ -10,24 +10,26 @@ $::xs1 = XML::Simple->new( KeyAttr => ['id'], Cache => [ qw/storable memcopy/ ])
sub readXML {
my ( $p ) = $::cset;
my @fchan = ( 'event', keys %::RISKS );
- $::settings = $::xs1->XMLin( "$p/settings.xml", ForceArray => ['host'], 'GroupTags' => { altnicks => 'altnick', server => 'host', autojoins => 'autojoin' });
- $::channels = $::xs1->XMLin( "$p/channels.xml", ForceArray => \@fchan );
- $::users = $::xs1->XMLin( "$p/users.xml", ForceArray => 'person');
- $::commands = $::xs1->XMLin( "$p/commands.xml", ForceArray => [qw/command/]);
- $::mysql = $::xs1->XMLin( "$p/mysql.xml", ForceArray => []);
- $::dnsbl = $::xs1->XMLin( "$p/dnsbl.xml", ForceArray => []);
- $::rules = $::xs1->XMLin( "$p/rules.xml", ForceArray => []);
+ $::settings = $::xs1->XMLin( "$p/settings.xml", ForceArray => ['host'], 'GroupTags' => { altnicks => 'altnick', server => 'host', autojoins => 'autojoin' });
+ $::channels = $::xs1->XMLin( "$p/channels.xml", ForceArray => \@fchan );
+ $::users = $::xs1->XMLin( "$p/users.xml", ForceArray => 'person');
+ $::commands = $::xs1->XMLin( "$p/commands.xml", ForceArray => [qw/command/]);
+ $::mysql = $::xs1->XMLin( "$p/mysql.xml", ForceArray => []);
+ $::dnsbl = $::xs1->XMLin( "$p/dnsbl.xml", ForceArray => []);
+ $::rules = $::xs1->XMLin( "$p/rules.xml", ForceArray => []);
+ $::restrictions = $::xs1->XMLin( "$p/restrictions.xml", ForceArray => ['host', 'nick', 'account']);
}
sub writeXML {
- $::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("$::cset/settings.xml");
- $::xs1->XMLout($::channels, RootName => 'channels', KeyAttr => ['id'], NumericEscape => 2) > io("$::cset/channels.xml");
- $::xs1->XMLout($::users, RootName => 'people', KeyAttr => ['id']) > io("$::cset/users.xml");
- $::xs1->XMLout($::commands, RootName => 'commands', KeyAttr => ['id']) > io("$::cset/commands.xml");
+ $::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("$::cset/settings.xml");
+ writeChannels();
+ writeUsers();
+ writeRestrictions();
+ $::xs1->XMLout($::commands, RootName => 'commands', KeyAttr => ['id']) > io("$::cset/commands.xml");
}
sub writeChannels {
@@ -39,7 +41,13 @@ sub writeUsers {
}
sub writeSettings {
- $::xs1->XMLout($::settings, RootName => 'settings', GroupTags => { altnicks => 'altnick', server => 'host', autojoins => 'autojoin' }, NoAttr => 1) > io("$::cset/settings.xml");
+ $::xs1->XMLout($::settings, RootName => 'settings',
+ GroupTags => { altnicks => 'altnick', server => 'host', autojoins => 'autojoin' }, NoAttr => 1) > io("$::cset/settings.xml");
+}
+
+sub writeRestrictions {
+ $::xs1->XMLout($::restrictions, RootName => 'restrictions', KeyAttr => ['id'],
+ GroupTags => { hosts => "host", nicks => "nick", accounts => "account"}) > io("$::cset/restrictions.xml");
}
return 1;