From 6cf8963cfc8dc562f7ec8f3c5d538f05a0e7a0be Mon Sep 17 00:00:00 2001 From: William Heimbigner Date: Thu, 16 Aug 2012 06:58:14 +0000 Subject: 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. --- modules/classes.pl | 49 ++++++++++++++++-- modules/event.pl | 145 ++++++++++++++++++++++++++++++++++++++++++++++------ modules/inspect.pl | 18 +++---- modules/services.pl | 8 ++- modules/util.pl | 41 +++++++++++++++ modules/xml.pl | 40 +++++++++------ 6 files changed, 254 insertions(+), 47 deletions(-) (limited to 'modules') 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; -- cgit v1.2.3