###################################################################
# 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].*
#
# For more information:
# /dronebl help
#
# Requires LWP::UserAgent (which you probably already have),
# HTTP::Request and Text::TabularDisplay
#
# 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 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::TabularDisplay;
$VERSION = '0.4';
%IRSSI = (
authors => 'Steve Church (rojo)',
contact => 'irc.atheme.org on #dronebl',
name => 'DroneBL RPC2 query',
description => 'query 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 $rpcKey = settings_get_str('dronebl_rpckey');
my $userAgent = LWP::UserAgent->new(agent => 'perl post');
my $DroneBL = "http://dronebl.org/RPC2";
my $classes_page = "http://dronebl.org/classes?format=txt";
my %classes;
if (!$rpcKey) {
&dronebl_nag(0);
}
command_bind dronebl => sub {
my ($data, $server, $channel) = @_;
my $query;
my $response;
my $xml;
my $error;
my $error_message;
my $error_query;
my $item;
my $line;
my @lines;
my @params;
my $matches = 0;
my $active = 0;
my $expired = 0;
if (index("help",$data) ne -1) {
&dronebl_help($channel);
return;
}
my $rpcKey = settings_get_str('dronebl_rpckey');
if (!$rpcKey) {
&dronebl_nag($channel);
return;
}
my $message = "\n\n";
# valid characters in queries include ?*%_[]-
foreach $query (split(/[^0-9\.\?\*\%\_\[\]\-]+/,$data)) {
$message .= " \n";
}
$message .= "";
$response = $userAgent->request(POST $DroneBL, Content_Type => 'text/xml', Content => $message);
if ($response->is_success) {
$xml = $response->as_string;
@lines = split(/\n/, $xml);
if (grep(/error/, $xml)) {
foreach $line (@lines) {
if (grep(//, $line)) { $error = $line; }
if (grep(//, $line)) { $error_message = $line; }
if (grep(//,$line)) { $error_query = $line; }
}
$error =~ s/\s*<\/*[^>]+>//g;
$error_message =~ s/\s*<\/*[^>]+>//g;
$error_query =~ s/\s*<\/*[^>]+>//g;
$error_query =~ s/\%/\%\%/g;
&dronebl_print("The server returned an error message ($error: $error_message)", $channel);
if ($error_query) { &dronebl_print("Extended information: $error_query", $channel); }
return;
}
my @columns = split(/\s+/, settings_get_str('dronebl_columns'));
my $table = Text::TabularDisplay->new(@columns);
foreach $line (@lines) {
if (grep(/add(@row);
}
}
}
my @table = split(/\n/, $table->render);
foreach my $row (@table) {
&dronebl_print("%8%#" . $row . "%#%8", $channel);
}
&dronebl_print("%W%Nquery: $data | matches: $matches | active: $active | expired: $expired", $channel);
}
else {
&dronebl_print($response->error_as_HTML, $channel);
}
};
sub dronebl_print {
my ($data, $channel) = @_;
if ($channel->{type} eq "CHANNEL") {
$channel->print($data, MSGLEVEL_CLIENTCRAP);
}
else {
print CLIENTCRAP $data;
}
};
sub dronebl_help {
my ($channel) = @_;
my $help = "
%W%8DroneBL RPC2 query for irssi%8 %N
%W%NThis script allows querying the DroneBL using RPC2 calls, which
%W%Nwill allow queries including wildcards and ranges. Examples:
%W/dronebl 127.0.0.?%N
%W/dronebl 10.0.*%N
%W/dronebl 192.168.[1-20].*%N
%W%NRequires LWP::UserAgent (which you probably already have),
%W%NHTTP::Request and Text::TabularDisplay
%W%NAlso requires an RPCKey. Set the key by typing the following:
%W/set dronebl_rpckey XXXXXXXXXXXXXXXXXXXXXXXXXXX%N
%W%Nreplacing the X's with your key, of course. If you are a network
%W%Nsecurity professional and you do not have an RPCKey, one can be
%W%Nrequested from %chttp://dronebl.org/rpckey_signup%n
%W%NYou can change the format of the output by setting the
%Wdronebl_columns%N variable. Valid columns are as follows:
%W ip id type comment listed timestamp%N
%W%NAdd / remove / rearrange columns as you see fit. Example:
%W/set dronebl_columns listed ip type timestamp%N
%W%NIf you have the screen real-estate, you can expand the \"type\"
%W%Ncolumn numbers into their canonical names by setting the
%Wdronebl_show_type_names%N variable to %WON%N.
%W%NFinally, you can ignore inactive entries by setting the
%Wdronebl_show_only_active%N variable to %WON%N.
";
&dronebl_print($help, $channel);
};
sub dronebl_nag {
my ($channel) = @_;
my $nag_message = "
Please store an RPCKey. You will be unable to make queries until
you have done so. Set the key by typing the following:
%W/set dronebl_rpckey XXXXXXXXXXXXXXXXXXXXXXXXXXX%N
replacing the X's with your key, of course. If you are a network
security professional and you do not have an RPCKey, one can be
requested from %W%N%chttp://dronebl.org/rpckey_signup%n
For more options, see %W/dronebl help%N
";
&dronebl_print($nag_message, $channel);
};
sub dronebl_class {
my ($category) = @_;
my $default = "Not yet implemented";
if (keys(%classes) < 1) {
&dronebl_print("Fetching up-to-date category descriptions...", $channel);
my $response = $userAgent->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 { $default = 0; }
}
if (defined $classes{$category}) {
return $classes{$category};
}
else {
return $default;
}
};