################################################################### # DroneBL RPC2 query for irssi # This script allows querying the DroneBL using RPC2 calls, which # will allow queries including wildcards and ranges. Examples: # /dronebl 127.0.0.? # /dronebl 10.0.* # /dronebl 192.168.[1-20].* # # It also allows addition of ip addresses via "add $type $ipaddr": # /dronebl add 1 10.10.2.20 # # To see a list of types, issue: # /dronebl types # # For more information: # /dronebl help # # Requires LWP (libwww-perl -- which you probably already have), # and Text::TabularDisplay (available via CPAN or your vendor). # # You pretty much have to install LWP via cpan(1) or your vendor's # package distribution system (such as apt-get(1)), but you can # download Text::TabularDisplay, untar, and mkdir # ~/.irssi/scripts/Text; cp TabularDisplay.pm ~/.irssi/scripts/Text # # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms of the Do What The Fuck You Want # To Public License, Version 2, as published by Sam Hocevar. See # http://sam.zoy.org/wtfpl/COPYING for more details. ################################################################### use vars qw($VERSION %IRSSI); use strict; use Irssi qw(command_bind settings_get_str settings_add_str settings_get_bool settings_add_bool); use LWP::UserAgent; use HTTP::Request::Common; use Text::ParseWords 'shellwords'; use Text::TabularDisplay; $VERSION = '0.5'; %IRSSI = ( authors => 'Steve Church (rojo), Jmax', contact => 'irc.atheme.org on #dronebl', name => 'DroneBL RPC2 query / host submission', description => 'query (and modify) the DroneBL with wildcards via irssi', license => 'WTFPLv2', url => 'http://headcandy.org/rojo/', changed => $VERSION, modules => 'LWP::UserAgent HTTP::Request Text::TabularDisplay', commands => 'dronebl' ); settings_add_str('DroneBL', 'dronebl_rpckey', ''); settings_add_str('DroneBL', 'dronebl_columns', 'listed ip type timestamp'); settings_add_bool('DroneBL', 'dronebl_show_only_active', 0); settings_add_bool('DroneBL', 'dronebl_show_type_names', 0); my $DroneBL = "https://dronebl.org/RPC2"; my $classes_page = "https://dronebl.org/classes?format=txt"; my $expired_after = 86400*90; # 90 days my $userAgent = LWP::UserAgent->new(agent => 'perl post'); my %classes; if (!settings_get_str('dronebl_rpckey')) { dronebl_nag(0); } command_bind dronebl => \&dronebl; sub dronebl { my ($data, $server, $channel) = @_; my @args = shellwords($data); if ($args[0] eq 'help') { dronebl_help($channel); return; } elsif ($args[0] eq 'types') { dronebl_update_classes($channel); dronebl_print('Types: '); my @types = sort { $a <=> $b } keys %classes; foreach my $type (@types) { dronebl_print( sprintf('%3s', $type) . ': ' . $classes{$type} ); } return; } elsif ($args[0] eq 'add' || $args[0] eq 'submit') { my ($type, $ipaddr) = @args[1..2]; if (!$type or $ipaddr !~ /^[0-9\.]+$/) { dronebl_print('Need type and IP address'); return; } my ($matches, $active, $expired) = dronebl_lookup($ipaddr); if ($matches && $active && !$expired) { dronebl_print("$ipaddr: Already listed"); } else { my ($line) = dronebl_request($channel, "" ); dronebl_print("$ipaddr: Success adding") if $line; } } elsif ($args[0] && $args[0] =~ /[0-9\.\?\*\%_\[\]-]+/) { my @clean; foreach (@args) { push @clean, $_ if /^[0-9\.\?\*\%_\[\]-]+$/; } my ($matches, $active, $expired, @results) = dronebl_lookup($channel, @clean); my @columns = split(/\s+/, settings_get_str('dronebl_columns')); my $table = Text::TabularDisplay->new(@columns); if ($matches) { foreach my $result (@results) { if ($result->{listed} || !settings_get_bool('dronebl_show_only_active')) { $result->{listed} = ($result->{listed}) ? 'yes' : 'no'; $result->{listed} .= ' (exp)' if $result->{expired}; $result->{timestamp} = scalar gmtime($result->{timestamp}); if (settings_get_bool('dronebl_show_type_names')) { my $type_name = dronebl_class($channel, $result->{type}); $result->{type} .= " ($type_name)" if $type_name; } my @row; foreach my $column (@columns) { push @row, $result->{$column}; } $table->add(@row); } } my @table = split /\n/, $table->render; dronebl_print("%8%#" . $_ . "%#%8", $channel) foreach @table; dronebl_print("%W%Nquery: $data | matches: $matches | active: $active | expired: $expired", $channel); } else { dronebl_print("%W%Nquery: $data | no match(es)"); } } else { dronebl_help($channel); } } sub dronebl_lookup { my ($channel, @queries) = @_; $_ = "" foreach @queries; my @lines = dronebl_request($channel, @queries) or return; my ($matches, $active, $expired) = (0, 0, 0); my @results; foreach my $line (@lines) { if ($line =~ /\n\n"; $message .= "\t$_\n" foreach @reqs; $message .= "\n"; my $response = $userAgent->request(POST $DroneBL, Content_Type => 'text/xml', Content => $message); if ($response->is_success) { my $xml = $response->as_string; my @lines = split(/\n/, $xml); if ($xml =~ /error/) { my ($error, $error_message, $error_query); foreach my $line (@lines) { if ($line =~ //) { $error = $line; } if ($line =~ //) { $error_message = $line; } if ($line =~ //) { $error_query = $line; } } s{\s*]+>}{}g for ($error, $error_message, $error_query); $error_query =~ s/%/%%/g; dronebl_print("The server returned an error message ($error: $error_message)", $channel); dronebl_print("Extended information: $error_query", $channel) if $error_query; return; } return @lines; } else { dronebl_print($response->error_as_HTML, $channel); return; } } sub dronebl_print { my ($data, $channel) = @_; if ($channel && $channel->{type} eq "CHANNEL") { $channel->print($data, MSGLEVEL_CLIENTCRAP); } else { print CLIENTCRAP $data; } } sub dronebl_help { my ($channel) = @_; dronebl_print(<request(GET $classes_page); if ($response->is_success) { foreach my $line (split(/\n/, $response->as_string)) { if (grep(/\t/, $line)) { my @parms = split(/\t/, $line); $classes{@parms[0]} = @parms[1]; } } } else { return; } } return 1; }