diff options
| author | 2012-08-16 06:58:14 +0000 | |
|---|---|---|
| committer | 2012-08-16 06:58:14 +0000 | |
| commit | 6cf8963cfc8dc562f7ec8f3c5d538f05a0e7a0be (patch) | |
| tree | 2f54ae3d0f4a21ae6026e401832fffd053b79da6 /modules | |
| parent | 5154f3cac3c3537d94b748c365dce88f3805b4a7 (diff) | |
Disabled message-sending-throttling
Added a command to view uptime/cpu/ram usage statistics
The hilight/dehilight commands can accept a comma-separated list of nicks
The exempt command is now deprecated
A restriction-system has been added to replace the old exemption system, and also includes additional restrictions that can be added, such as "don't let this account use !ops"
Added a plugin command, so other bots can have ASM generate alerts
Updated users and flags
Updated channel hilights
Replaced code that shows up in several places to split a long privmsg into two parts with a utility function
Added a detection class for fuzzy-matching against a nick blacklist
Fixed some major memory leaks, the bot stays stable around 30MB rather than shooting up to 65MB after a couple of days
The bot now uses nickserv REGAIN instead of ghost/release/nick
Added channel modes, ban lists, and quiet lists to state tracking
Ignore chanserv in netsplit detections
Track time/setter when a topic is changed in a channel
Monitor if a channel is set +r and is still +r 45 minutes later, if so generate an alert
Print status and how long it took to sync once the bot has started and synced, and warn about failed syncings.
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/classes.pl | 49 | ||||
| -rw-r--r-- | modules/event.pl | 145 | ||||
| -rw-r--r-- | modules/inspect.pl | 18 | ||||
| -rw-r--r-- | modules/services.pl | 8 | ||||
| -rw-r--r-- | modules/util.pl | 41 | ||||
| -rw-r--r-- | modules/xml.pl | 40 |
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; |
